Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189157b1c7 | ||
|
|
64fd8addb2 | ||
|
|
4490ac303e | ||
|
|
666bd07411 | ||
|
|
0ff4624778 | ||
|
|
ab04b4afab | ||
|
|
ac226ddd3b | ||
|
|
0223e43aa2 | ||
|
|
73e5cfdada | ||
|
|
33b4b88af1 | ||
|
|
12f4b4b25b | ||
|
|
96cacb8bb9 | ||
|
|
300a1ed17f | ||
|
|
1a0836ca6d | ||
|
|
88908543e1 | ||
|
|
80c9b6a534 | ||
|
|
f6282c0676 | ||
|
|
02fe83f20b | ||
|
|
84d666c29c | ||
|
|
147e43b464 | ||
|
|
b909a6062e | ||
|
|
421b3af6a7 | ||
|
|
46a340d493 | ||
|
|
3cc7d76a1b | ||
|
|
5123b928f2 | ||
|
|
c2631e3257 | ||
|
|
685181ae28 | ||
|
|
929492c2f0 | ||
|
|
be023ff6cb | ||
|
|
a4d63a1421 | ||
|
|
2fd8c39888 | ||
|
|
eb97fd045a | ||
|
|
a07bc152d1 | ||
|
|
aef8e2dbb1 | ||
|
|
baac6a893d | ||
|
|
df681ca209 | ||
|
|
21b34434ef | ||
|
|
0c17dbb55b | ||
|
|
fd6c6a3055 | ||
|
|
259355d629 | ||
|
|
c6c2d3448d | ||
|
|
f860649996 | ||
|
|
05c3c97713 | ||
|
|
f3be277484 | ||
|
|
0ba311788c | ||
|
|
341eda5962 | ||
|
|
3398bb5b86 | ||
|
|
dc7cacc126 | ||
|
|
6d3d4e1f56 | ||
|
|
433b1abecd | ||
|
|
4544180c5f | ||
|
|
5722fcffc3 | ||
|
|
f1880615b1 | ||
|
|
e60f5678ec | ||
|
|
e9a70e51e4 | ||
|
|
e98d18a5aa | ||
|
|
513cc248b8 | ||
|
|
c024095f75 | ||
|
|
3e5eb80a7c | ||
|
|
942dc1640e | ||
|
|
b5c3abb782 | ||
|
|
3478d1ad52 | ||
|
|
d54e9233d1 | ||
|
|
e94759cd89 | ||
|
|
b58da4a20e | ||
|
|
8426399a23 | ||
|
|
9b2f41c805 | ||
|
|
15f96dda3e | ||
|
|
a9d5c17d63 | ||
|
|
715b629778 | ||
|
|
dd82d127ea | ||
|
|
f97e22d6eb | ||
|
|
f9e04ee28c | ||
|
|
1ce00ca13d | ||
|
|
3b9c2f5d70 | ||
|
|
31b2ba9165 | ||
|
|
2b1b1f1681 | ||
|
|
7e75a71898 | ||
|
|
7e47d7f8a5 | ||
|
|
90526a8bfd | ||
|
|
ebb2d5618c | ||
|
|
ab993423a6 | ||
|
|
4138ab0730 | ||
|
|
c0d097cb40 | ||
|
|
533e688d1a | ||
|
|
36d40dec70 |
No files matched your search
@@ -4,6 +4,32 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
|
||||
- Fixed Net/Absolute radio button in risk admin UI
|
||||
- Fixed double counting of spread legs in limits
|
||||
- Removed expired fills from the system
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace bts {
|
||||
window.Close();
|
||||
message.complete(select_result(vm)).execute(_bus);
|
||||
};
|
||||
vm.cancel_action = () => window.Close();
|
||||
window.move_to_mouse();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
@@ -117,8 +118,8 @@ namespace mbts.spark {
|
||||
// show fills
|
||||
var show_fills_cmd = new DelegateCommand(() => {
|
||||
var sc = _selected_security_criteria;
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsWindowFactory.StartupData(
|
||||
new FillsWindowFactory.FilterStates {
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
|
||||
new FillsFilterStates {
|
||||
security = sc.is_defined() ? sc : _state.security_criteria
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
@@ -92,8 +93,8 @@ namespace mbts.spark {
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
FillsWindowFactory.StartupData get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
|
||||
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
new FillsSettings(new FillsFilterStates {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
});
|
||||
void update_criteria(SecurityCriteria c) {
|
||||
|
||||
@@ -321,9 +321,12 @@ namespace mbts.spark {
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
synthetic.Synthetic.Legs
|
||||
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) {
|
||||
foreach (var l in synthetic.Synthetic.Legs) {
|
||||
if (strategy.LegParamsMap.TryGetValue(l.Security.Id, out var p))
|
||||
yield return new LegSpreaderParamsTuple(l, p);
|
||||
}
|
||||
}
|
||||
|
||||
void _close_new_leg_popup() {
|
||||
is_new_leg_popup_open = false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -18,7 +19,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
@@ -34,21 +34,19 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
namespace mbts.spark {
|
||||
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
|
||||
readonly OrdersModel _model;
|
||||
readonly DIYGridSortManager<Columns> _sort_manager;
|
||||
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
|
||||
readonly DIYGridRowsManager<FillTuple, Columns, FillsFilterStates> _rows_manager;
|
||||
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
|
||||
List<(ColumnBase col, Columns key, ListSortDirection direction)> _active_comparers = new List<(ColumnBase col, Columns key, ListSortDirection direction)>();
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
int _total_row_count;
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FilterStates>, Columns>(c => c.key);
|
||||
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FillsFilterStates>, Columns>(c => c.key);
|
||||
// security
|
||||
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(),
|
||||
DiscreteValuesFilterVM.Create<FillTuple, SecurityImmutable, SecurityCriteria>(
|
||||
@@ -85,25 +83,15 @@ namespace mbts.spark {
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
/// Rows
|
||||
rows = new OperableCollection<Row, FillTuple, string>(
|
||||
(ft, fk) => new Row(ft, model),
|
||||
ft => ft.fill.FillKey,
|
||||
new ComparerBuilder<FillTuple>(new[] {
|
||||
_get_column_comparer(Columns.FillTime),
|
||||
_get_column_comparer(Columns.FillKey)
|
||||
}).to_comparer());
|
||||
// row sort/filter managers
|
||||
_sort_manager = new DIYGridSortManager<Columns>(col_tups.WrapSelector(t => (t.col, t.key)), Columns.FillKey);
|
||||
_filters_manager = DIYGridFiltersManager.create(col_tups);
|
||||
var rows_manager = new DIYGridRowsManager<FillTuple, Columns, FilterStates>(_filters_manager, _sort_manager, _get_column_comparer, FillsWindow.window_type_name);
|
||||
// rows
|
||||
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => rows_manager.filter_changed += cb, cb => rows_manager.filter_changed -= cb);
|
||||
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => rows_manager.comparer_changed += cb, cb => rows_manager.comparer_changed -= cb);
|
||||
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
|
||||
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
|
||||
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
|
||||
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
|
||||
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
|
||||
@@ -125,13 +113,13 @@ namespace mbts.spark {
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
var fs = s.filters ?? new FilterStates {
|
||||
var fs = s.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
};
|
||||
rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
_rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
@@ -144,7 +132,7 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
}, filter);
|
||||
void add_col_expl<TFilter>(Columns col_key, ColumnBase col, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter) =>
|
||||
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
|
||||
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FillsFilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
|
||||
// discrete-values-filter factories
|
||||
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter<T>(IReadOnlyCollection<T> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
|
||||
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter_rx<T>(IObservable<IEnumerable<T>> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
|
||||
@@ -166,26 +154,26 @@ namespace mbts.spark {
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public FillsFilterStates get_filters() =>
|
||||
_rows_manager.get_filters();
|
||||
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
|
||||
_sort_manager.create();
|
||||
_rows_manager.get_sorting_criterion();
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
_rows_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
state.SelectManyInSeries(s => _get_observable(bus, model, s));
|
||||
|
||||
static IComparer<FillTuple> _get_column_comparer(Columns col) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -13,21 +14,20 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
class FillsWindow : SparkWindow<FillsSettings>, ISharedWindow {
|
||||
public const string window_type_name = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
FillsVM _vm;
|
||||
FillsView _view;
|
||||
StartupData _state;
|
||||
FillsSettings _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
@@ -35,15 +35,15 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
event Action<FillsSettings> _state_changed;
|
||||
|
||||
public override string window_type => window_type_name;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
@@ -53,7 +53,7 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace mbts.spark {
|
||||
};
|
||||
_subscriptions = Disposable.Create(() =>
|
||||
_state_changed?.Invoke(
|
||||
_state = new StartupData(_state.orders,
|
||||
_state = new FillsSettings(_state.orders,
|
||||
_vm.get_sorting_criterion().ToList(),
|
||||
_vm.get_filters(),
|
||||
_vm.grid_settings_manager.create()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -16,7 +17,7 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
|
||||
namespace bts.shell {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsSettings> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
@@ -52,13 +53,13 @@ namespace bts.shell {
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
public override Task<IShellWindow> load(FillsSettings state) =>
|
||||
Task.FromResult(_create(state));
|
||||
|
||||
public async void show(StartupData state) =>
|
||||
public async void show(FillsSettings state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
IShellWindow _create(FillsSettings state) {
|
||||
GridSettingsHelper.normalize_sorting_criterion(state.sorting_criterion, _sort_member_to_id);
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
@@ -66,162 +67,5 @@ namespace bts.shell {
|
||||
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
|
||||
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
public const int Version = 1;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public StartupData() { }
|
||||
public StartupData(uint[] orders) : this(orders, null, null) { }
|
||||
public StartupData(FilterStates filters) : this(null, null, filters) { }
|
||||
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static StartupData Default() =>
|
||||
new StartupData(null, null, new FilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
[DIYGridColumnID(nameof(Columns.FillTime))]
|
||||
public TimeFilterTuple fill_time { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[DIYGridColumnID(nameof(Columns.TimeInForce))]
|
||||
public TimeInForce[] time_in_force { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
[DIYGridColumnID(nameof(Columns.User))]
|
||||
public string[] user { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
[DIYGridColumnID(nameof(Columns.Exchange))]
|
||||
public Venue[] exchange { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[DIYGridColumnID(nameof(Columns.Security))]
|
||||
public SecurityCriteria security { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[DIYGridColumnID(nameof(Columns.Side))]
|
||||
public Side[] side { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
[DIYGridColumnID(nameof(Columns.Price))]
|
||||
public DoubleMinMaxState price { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderKey))]
|
||||
public string order_key { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
[DIYGridColumnID(nameof(Columns.SecurityKey))]
|
||||
public string security_key { get; set; }
|
||||
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using Columns = mbts.ui.spark.FillsView.Columns;
|
||||
|
||||
namespace bts.shell.fills {
|
||||
[ProtoContract]
|
||||
class FillsFilterStates {
|
||||
[ProtoMember(1)]
|
||||
[DIYGridColumnID(nameof(Columns.FillTime))]
|
||||
public TimeFilterTuple fill_time { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[DIYGridColumnID(nameof(Columns.TimeInForce))]
|
||||
public TimeInForce[] time_in_force { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
[DIYGridColumnID(nameof(Columns.User))]
|
||||
public string[] user { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
[DIYGridColumnID(nameof(Columns.Exchange))]
|
||||
public Venue[] exchange { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[DIYGridColumnID(nameof(Columns.Security))]
|
||||
public SecurityCriteria security { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[DIYGridColumnID(nameof(Columns.Side))]
|
||||
public Side[] side { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
[DIYGridColumnID(nameof(Columns.Price))]
|
||||
public DoubleMinMaxState price { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderKey))]
|
||||
public string order_key { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
[DIYGridColumnID(nameof(Columns.SecurityKey))]
|
||||
public string security_key { get; set; }
|
||||
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
public static Func<FillTuple, bool> build_predicate(this FillsFilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<FillTuple>();
|
||||
// fill time
|
||||
pb.add_time_filter(fs.fill_time, ft => ft.fill_time());
|
||||
// security
|
||||
pb.add_security_criteria(fs.security, ft => ft.security);
|
||||
// regex
|
||||
pb.add_regex(fs.fill_key, ft => ft.fill.FillKey);
|
||||
pb.add_regex(fs.order_key, ft => ft.order_key().ToString());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.user, ft => ft.user());
|
||||
pb.add_discrete_values(fs.account, ft => ft.account);
|
||||
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
|
||||
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
|
||||
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using Columns = mbts.ui.spark.FillsView.Columns;
|
||||
|
||||
namespace bts.shell.fills {
|
||||
[ProtoContract]
|
||||
class FillsSettings {
|
||||
public const int Version = 1;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FillsFilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public FillsSettings() { }
|
||||
public FillsSettings(uint[] orders) : this(orders, null, null) { }
|
||||
public FillsSettings(FillsFilterStates filters) : this(null, null, filters) { }
|
||||
public FillsSettings(uint[] orders, List<SortingCriteria> sorting_criteria, FillsFilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static FillsSettings Default() =>
|
||||
new FillsSettings(null, null, new FillsFilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,12 +390,16 @@ namespace mbts.spark {
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
var s = _settings_manager.current_settings();
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0))
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
|
||||
return;
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0))
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task send_order_async(Side side, uint size, double price) {
|
||||
|
||||
@@ -338,7 +338,12 @@ namespace mbts.spark {
|
||||
if (bs.HasValue) {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
show_mbo_popup(point).ignore();
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
show_mbo_popup(point).ignore();
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
}
|
||||
}
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
TextElement.FontSize="{x:Static u:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
@@ -382,7 +383,8 @@
|
||||
FontStyle="Italic"
|
||||
Foreground="{Binding can_choose_alternate_implied_format, Converter={StaticResource IsEnabledForegroundConverter}}"
|
||||
/>
|
||||
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3">
|
||||
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3"
|
||||
Visibility="{Binding DataContext.is_synthetic, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}">
|
||||
<GroupBox.Header>
|
||||
<CheckBox
|
||||
x:Name="show_name_change_chk"
|
||||
|
||||
@@ -149,11 +149,33 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _host_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
// When middle click over bid or ask columns:
|
||||
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
|
||||
// Parameterizable order: if qty is null/0, flash and No cursor
|
||||
var pos = e.GetPosition(_host);
|
||||
var column_type = _host._column_for_point(pos).column_type;
|
||||
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
|
||||
if (e.MiddleButton == MouseButtonState.Pressed &&
|
||||
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
|
||||
if (!selected_algo_can_expand) {
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
|
||||
_host._vm.size_calculator.flash_error();
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
}
|
||||
|
||||
_is_mouse_down = true;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
void _host_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
// This will reset the cursor appropriately if the middle mouse button
|
||||
// has been pressed while over the bid or ask columns
|
||||
var point = e.GetPosition(_host);
|
||||
_set_current_row_and_column(point);
|
||||
|
||||
_is_mouse_down = false;
|
||||
_is_mouse_dragging = false;
|
||||
InvalidateVisual();
|
||||
|
||||
@@ -3,12 +3,11 @@ using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
@@ -45,7 +44,7 @@ namespace mbts.spark {
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsWindowFactory.StartupData.MiniGridSettings, FillsWindowFactory.StartupData.MiniGridSettings);
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
readonly SelectedOrderParamsMon _selected_params_mon;
|
||||
readonly RightPanelModel _right_panel = new RightPanelModel();
|
||||
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
@@ -68,7 +69,7 @@ namespace mbts.spark {
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
var toggle_show_right_panel_command = new SparkCommandCommand(
|
||||
get_toggle_show_right_panel_cmd_title(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.shell.fills;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System.Collections.Generic;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
@@ -9,11 +10,11 @@ namespace bts.shell.orderbook {
|
||||
[ProtoMember(1)]
|
||||
public GridSettings general { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public FillsWindowFactory.FilterStates filters { get; set; }
|
||||
public FillsFilterStates filters { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public List<SortingCriteria> sorting_criterion { get; set; }
|
||||
|
||||
public FillsWindowFactory.StartupData to_startup_data() =>
|
||||
new FillsWindowFactory.StartupData(null, sorting_criterion, filters, general);
|
||||
public FillsSettings to_startup_data() =>
|
||||
new FillsSettings(null, sorting_criterion, filters, general);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
@@ -121,86 +119,36 @@ namespace bts.shell.orderbook {
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
|
||||
var predicates = new List<Func<OrderSecurity, bool>>();
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
if (fs.security_criteria?.is_defined() ?? false)
|
||||
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
add_time_filter(fs.time_created, o => o.order.created());
|
||||
add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
add_discrete_values(fs.users, o => o.order.user());
|
||||
add_discrete_values(fs.accounts, o => o.order.account());
|
||||
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
add_discrete_values(fs.sides, o => o.order.side());
|
||||
add_discrete_values(fs.statuses, o => o.order.status());
|
||||
add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
add_numeric(fs.qty, o => o.order.quantity());
|
||||
add_numeric(fs.price, o => o.order.price());
|
||||
add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
add_numeric(fs.last_price, o => o.order.last_price());
|
||||
add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return predicates.Count == 0 ? null : new Func<OrderSecurity, bool>(o => predicates.All(p => p(o)));
|
||||
// local methods (impl)
|
||||
void add_regex(string str, Func<OrderSecurity, string> selector) {
|
||||
var regex = RegexFilterUtils.build_regex(str);
|
||||
if (regex == null)
|
||||
return;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) => regex.IsMatch(selector(o) ?? string.Empty);
|
||||
}
|
||||
void add_time_filter(TimeFilterTuple t, Func<OrderSecurity, DateTime> selector) {
|
||||
if (!t.is_active)
|
||||
return;
|
||||
var dt_pred = TimeFilterUtils.build_predicate(t);
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) => dt_pred(selector(o));
|
||||
}
|
||||
void add_discrete_values<T>(T[] values, Func<OrderSecurity, T> selector) {
|
||||
if (values.is_null_or_empty())
|
||||
return;
|
||||
if (values.Length < 16) {
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) => values.Contains(selector(o));
|
||||
} else {
|
||||
var set = new HashSet<T>(values);
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) => set.Contains(selector(o));
|
||||
}
|
||||
}
|
||||
void add_numeric<T>(IMinMaxState<T> value, Func<OrderSecurity, T> selector) where T : struct, IComparable<T> {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) {
|
||||
var v = selector(o);
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) => min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurity o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
@@ -127,11 +128,11 @@ namespace bts.shell.orderbook {
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsWindowFactory.StartupData.Default();
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criterion = default_fills.sorting_criterion.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsWindowFactory.StartupData.MiniGridSettings
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
@@ -48,7 +49,7 @@ namespace bts.shell {
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new [] {
|
||||
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsWindowFactory.StartupData()),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
|
||||
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
|
||||
SparkCommands.PickLadder,
|
||||
SparkCommands.PickOrderTicket,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts.ui;
|
||||
@@ -52,7 +53,7 @@ namespace mbts.spark
|
||||
#endregion
|
||||
|
||||
#region Spark Window
|
||||
public static ITag<FillsWindowFactory.StartupData> ShowFillsTag { get; } = new SparkTag<FillsWindowFactory.StartupData>(nameof(ShowFillsTag));
|
||||
public static ITag<FillsSettings> ShowFillsTag { get; } = new SparkTag<FillsSettings>(nameof(ShowFillsTag));
|
||||
public static ITag<ManualFillsSettings> ShowManualFillsTag { get; } = new SparkTag<ManualFillsSettings>(nameof(ShowManualFillsTag));
|
||||
// TODO: maybe get rid of the singlular ShowLadderTag, and just use the plural ShowLaddersTag.
|
||||
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace mbts.spark {
|
||||
_ladder = LadderSettings.Default,
|
||||
_notification = NotificationSettings.Default,
|
||||
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
|
||||
_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings,
|
||||
_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings,
|
||||
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
|
||||
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
|
||||
_order_book_grid = bts.shell.orderbook.OrderBookStartupData.DefaultGrid
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
|
||||
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
|
||||
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
|
||||
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
|
||||
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using bts.api;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
class ColumnPredicateBuilder<TRow> {
|
||||
readonly List<Func<TRow, bool>> _predicates = new List<Func<TRow, bool>>();
|
||||
|
||||
public void add_discrete_values<T>(T[] values, Func<TRow, T> selector) {
|
||||
if (values.is_null_or_empty())
|
||||
return;
|
||||
var set = new HashSet<T>(values);
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => set.Contains(selector(o));
|
||||
}
|
||||
|
||||
public void add_numeric<T>(IMinMaxState<T> value, Func<TRow, T> selector) where T : struct, IComparable<T> {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var v = selector(o);
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void add_regex(string str, Func<TRow, string> selector) {
|
||||
var regex = RegexFilterUtils.build_regex(str);
|
||||
if (regex == null)
|
||||
return;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => regex.IsMatch(selector(o) ?? string.Empty);
|
||||
}
|
||||
|
||||
public void add_security_criteria(SecurityCriteria security_criteria, Func<TRow, SecurityImmutable?> selector) {
|
||||
if (security_criteria != null && security_criteria.is_defined())
|
||||
_predicates.Add(r => {
|
||||
var sec = selector(r);
|
||||
return sec.HasValue && ContractPickerModel.security_in_criteria(security_criteria, sec.Value);
|
||||
});
|
||||
}
|
||||
|
||||
public void add_time_filter(TimeFilterTuple t, Func<TRow, DateTime> selector) {
|
||||
if (!t.is_active)
|
||||
return;
|
||||
var dt_pred = TimeFilterUtils.build_predicate(t);
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => dt_pred(selector(o));
|
||||
}
|
||||
|
||||
public Func<TRow, bool> to_predicate() =>
|
||||
_predicates.Count == 0 ? null : new Func<TRow, bool>(o => _predicates.All(p => p(o)));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -7,13 +8,30 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
static class DIYGridRowsManager {
|
||||
public static DIYGridRowsManager<T, TColKey, TFiltersState> create<T, TColKey, TFiltersState>(
|
||||
string window_name,
|
||||
TColKey primary_key,
|
||||
ObservableKeyedCollection<DIYGridColumnData<T, TColKey, TFiltersState>, TColKey> columns,
|
||||
IReadOnlyObservableKeyedCollection<(DIYGrid.ColumnBase col, TColKey key), TColKey> map,
|
||||
Func<TColKey, IComparer<T>> get_comparer_for_column,
|
||||
Func<TFiltersState, Func<T, bool>> build_filter_from_state = null) where TColKey : struct where TFiltersState : new() =>
|
||||
//
|
||||
new DIYGridRowsManager<T, TColKey, TFiltersState>(
|
||||
DIYGridFiltersManager.create(columns),
|
||||
new DIYGridSortManager<TColKey>(map, primary_key),
|
||||
get_comparer_for_column,
|
||||
window_name,
|
||||
build_filter_from_state);
|
||||
}
|
||||
|
||||
class DIYGridRowsManager<T, TColKey, TFiltersState> where TColKey : struct {
|
||||
readonly IDIYGridFiltersManager<T, TColKey, TFiltersState> _filters;
|
||||
readonly DIYGridSortManager<TColKey> _sorting;
|
||||
readonly DeferrableActionWrapper _update;
|
||||
bool _filters_dirty, _sorting_dirty;
|
||||
|
||||
public DIYGridRowsManager(IDIYGridFiltersManager<T, TColKey, TFiltersState> filters_manager, DIYGridSortManager<TColKey> sort_manager, Func<TColKey, IComparer<T>> get_comparer_for_column, string window_name) {
|
||||
public DIYGridRowsManager(IDIYGridFiltersManager<T, TColKey, TFiltersState> filters_manager, DIYGridSortManager<TColKey> sort_manager, Func<TColKey, IComparer<T>> get_comparer_for_column, string window_name, Func<TFiltersState, Func<T, bool>> build_filter_from_state = null) {
|
||||
_filters = filters_manager;
|
||||
_sorting = sort_manager;
|
||||
_update = new DeferrableActionWrapper(() => {
|
||||
@@ -59,10 +77,15 @@ namespace mbts.ui.controls {
|
||||
desc = "(none)";
|
||||
return null;
|
||||
} else {
|
||||
// TODO: the actual IDataGridFilter implementations should be able to provide a concise description of their filter state/parameters (e.g., for a min/max filter: 'min=40, max=60')
|
||||
desc = string.Join(", ", col_filters.Select(f => f.col));
|
||||
var filters = Array.ConvertAll(col_filters, cf => cf.filter);
|
||||
return new Func<T, bool>(r => filters.All(f => f.filter(r)));
|
||||
if (build_filter_from_state != null) {
|
||||
var filter_state = filters_manager.create();
|
||||
return build_filter_from_state(filter_state);
|
||||
} else {
|
||||
// TODO: the actual IDataGridFilter implementations should be able to provide a concise description of their filter state/parameters (e.g., for a min/max filter: 'min=40, max=60')
|
||||
var filters = Array.ConvertAll(col_filters, cf => cf.filter);
|
||||
return new Func<T, bool>(r => filters.All(f => f.filter(r)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +93,10 @@ namespace mbts.ui.controls {
|
||||
public event Action<Func<T, bool>> filter_changed;
|
||||
public event Action<IComparer<T>> comparer_changed;
|
||||
|
||||
public TFiltersState get_filters() => _filters.create();
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() => _sorting.create();
|
||||
|
||||
public void on_sorting(DIYGridSortingEventArgs e) => _sorting.on_sorting(e);
|
||||
public void on_settings(TFiltersState filters, IEnumerable<SortingCriteria> sorting) {
|
||||
using (_update.defer_execution()) {
|
||||
_filters.load_from(filters);
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace mbts.spark {
|
||||
readonly MBOService _mbo;
|
||||
readonly SecurityImmutable _security;
|
||||
readonly MBOSecurityQueryImmutable _query;
|
||||
readonly Dictionary<string, FormattedText> _cache;
|
||||
readonly Dictionary<(string,bool), FormattedText> _cache;
|
||||
int _max_order_size = 1;
|
||||
double _height = 0;
|
||||
double _number_width = 0;
|
||||
@@ -60,17 +60,17 @@ namespace mbts.spark {
|
||||
Enumerable.Range(1, 1+(int)default_query.Limit)
|
||||
.AsParallel()
|
||||
.Select(i => TextUtils.GetFormattedText(i.ToString(), font_size))
|
||||
.ToDictionary(ft => ft.Text);
|
||||
.ToDictionary(ft => (ft.Text,false));
|
||||
}
|
||||
|
||||
FormattedText format_text(string s, bool bold=false) {
|
||||
// cache FormattedText for the life of the popup
|
||||
if (_cache.TryGetValue(s, out FormattedText ft)) {
|
||||
var key = (s, bold);
|
||||
if (_cache.TryGetValue(key, out FormattedText ft)) {
|
||||
return ft;
|
||||
}
|
||||
else
|
||||
ft = bold ? TextUtils.GetFormattedTextBold(s, font_size) : TextUtils.GetFormattedText(s, font_size);
|
||||
_cache.TryAdd(s, ft);
|
||||
_cache.TryAdd(key, ft);
|
||||
return ft;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ismine(OrderPositionImmutable op) => op.OrderId != 0;
|
||||
|
||||
public MBOPopupDrawParams compute_layout(MBOPopupData p) {
|
||||
var pdp = new MBOPopupDrawParams { data = p };
|
||||
pdp.header = format_text(
|
||||
@@ -106,9 +108,9 @@ namespace mbts.spark {
|
||||
var margin = MBOPopupDrawParams.margin;
|
||||
var text_margin = margin.Left + margin.Right;
|
||||
|
||||
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString())).ToList();
|
||||
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString(), ismine(o))).ToList();
|
||||
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
|
||||
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString())).ToList();
|
||||
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString() + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
|
||||
|
||||
var available_width = pdp.width - pdp.numbers_width - max_label_width;
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
static Brush brush_for(Side side, OrderPositionImmutable order) {
|
||||
if (order.OrderId != 0) return Brushes.Black;
|
||||
if (MBOPopupModel.ismine(order)) return Brushes.Black;
|
||||
switch (side) {
|
||||
case Side.Buy: return BTSColors.MarketData.bid_brush;
|
||||
case Side.Sell: return BTSColors.MarketData.ask_brush;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.ui.utility {
|
||||
@@ -37,16 +34,16 @@ namespace mbts.ui.utility {
|
||||
table_update_source.Subscribe(on_table));
|
||||
// on comparer
|
||||
void on_comp(IComparer<T> new_comp) {
|
||||
comp = new_comp;
|
||||
if (table != null) {
|
||||
target.Clear();
|
||||
target.InsertSorted(table.state.Values, comp);
|
||||
target.InsertSorted(table.state.Values, new_comp);
|
||||
}
|
||||
comp = new_comp;
|
||||
}
|
||||
// on table update
|
||||
void on_table(TableUpdate<T, TKey> new_table) {
|
||||
table = table == null ? new_table.as_snapshot() : new_table;
|
||||
if (comp != null) {
|
||||
table = table == null ? new_table.as_snapshot() : new_table;
|
||||
target.RemoveSorted(table.removed.Concat(table.changes.Old()), comp);
|
||||
target.InsertSorted(table.added.Concat(table.changed), comp);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace bts.tests
|
||||
);
|
||||
|
||||
var spreader_order1_response = await OrderUtilities.send_order(_order_service, order1_def, -0.30, 1);
|
||||
Assert.IsTrue(spreader_order1_response.success, "Failed to send first spreader order");
|
||||
Assert.IsTrue(spreader_order1_response.success, "Failed to send spreader order");
|
||||
|
||||
var (spreader_order_is_working, working_spreader_order1) = await verify_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(spreader_order_is_working, "Spreader order is not Working");
|
||||
@@ -120,6 +120,156 @@ namespace bts.tests
|
||||
|
||||
}
|
||||
|
||||
[Test(Timeout = 300000)]
|
||||
public async Task spreader_lean_qty_factor() {
|
||||
await setup_spreader_tests();
|
||||
|
||||
string test_symbol = "SI";
|
||||
var test_user = (name: "bts", id: 1u);
|
||||
var test_account = (name: "ACC A", id: 1u);
|
||||
|
||||
(SecurityImmutable s1, SecurityImmutable s2) test_securities = await SpreaderUtilities.get_securities_for_common_setup_one(_md_relay, test_symbol);
|
||||
|
||||
var setup_product = await SpreaderUtilities.common_setup_one(_order_service, _synthetic_service, test_securities, test_account, test_user);
|
||||
|
||||
var order1_def = OrderDefinitionUtilities.create_spreader_order_def(
|
||||
setup_product.synthetic.security,
|
||||
Side.Buy,
|
||||
setup_product.synthetic.strategy,
|
||||
test_account,
|
||||
test_user
|
||||
);
|
||||
|
||||
var spreader_order1_response = await OrderUtilities.send_order(_order_service, order1_def, -0.30, 1);
|
||||
Assert.IsTrue(spreader_order1_response.success, "Failed to send spreader order");
|
||||
|
||||
var (spreader_order_is_working, working_spreader_order1) = await verify_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(spreader_order_is_working, "Spreader order is not Working");
|
||||
|
||||
var (quoting_order_is_working, working_quoting_order) = await verify_child_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(quoting_order_is_working, "Order is not Working");
|
||||
Assert.IsTrue(working_quoting_order.Value.State.Price == 18.6, "Quoting order has the wrong price");
|
||||
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order1.Value}), "Failed to cancel order");
|
||||
|
||||
var (quoting_order_is_canceled, canceled_quoting_order) = await verify_child_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Canceled);
|
||||
Assert.IsTrue(quoting_order_is_canceled, "Quoting order may not have been canceled");
|
||||
|
||||
// change the synthetic
|
||||
var hedge_leg = setup_product.synthetic.strategy.LegParamsMap.Where(m => !m.Value.IsActiveQuoting).TryFirst();
|
||||
Assert.IsTrue(hedge_leg.HasValue, "you need more legs");
|
||||
|
||||
foreach(var key in setup_product.synthetic.strategy.LegParamsMap.Keys) {
|
||||
if(!setup_product.synthetic.strategy.LegParamsMap[key].IsActiveQuoting) {
|
||||
setup_product.synthetic.strategy.LegParamsMap[key].QuoteParams.LeanQuantityRatio = 2d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsTrue(await SpreaderUtilities.save_strategy(_synthetic_service, setup_product.synthetic.security.Id, (ExecutionStrategyImmutable) setup_product.synthetic.strategy), "Failed to save strategy");
|
||||
|
||||
var order2_def = OrderDefinitionUtilities.create_spreader_order_def(
|
||||
setup_product.synthetic.security,
|
||||
Side.Buy,
|
||||
setup_product.synthetic.strategy,
|
||||
test_account,
|
||||
test_user
|
||||
);
|
||||
|
||||
var spreader_order2_response = await OrderUtilities.send_order(_order_service, order2_def, -0.30, 1);
|
||||
Assert.IsTrue(spreader_order2_response.success, "Failed to send spreader order");
|
||||
|
||||
var (spreader_order2_is_working, working_spreader_order2) = await verify_order_status(spreader_order2_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(spreader_order2_is_working, "Spreader order is not Working");
|
||||
|
||||
var (quoting_order2_is_working, working_quoting_order2) = await verify_child_order_status(spreader_order2_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(quoting_order2_is_working, "Order is not Working");
|
||||
Assert.IsTrue(working_quoting_order2.Value.State.Price == 18.61, "Quoting order has the wrong price");
|
||||
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order2.Value}), "Failed to cancel order");
|
||||
|
||||
var (quoting_order2_is_canceled, canceled_quoting_order2) = await verify_child_order_status(spreader_order2_response.value.OrderKey, OrderStatus.Canceled);
|
||||
Assert.IsTrue(quoting_order2_is_canceled, "Quoting order may not have been canceled");
|
||||
|
||||
|
||||
// Order 3
|
||||
foreach(var key in setup_product.synthetic.strategy.LegParamsMap.Keys) {
|
||||
if(!setup_product.synthetic.strategy.LegParamsMap[key].IsActiveQuoting) {
|
||||
setup_product.synthetic.strategy.LegParamsMap[key].QuoteParams.LeanQuantityRatio = 4d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsTrue(await SpreaderUtilities.save_strategy(_synthetic_service, setup_product.synthetic.security.Id, (ExecutionStrategyImmutable) setup_product.synthetic.strategy), "Failed to save strategy");
|
||||
|
||||
var order3_def = OrderDefinitionUtilities.create_spreader_order_def(
|
||||
setup_product.synthetic.security,
|
||||
Side.Buy,
|
||||
setup_product.synthetic.strategy,
|
||||
test_account,
|
||||
test_user
|
||||
);
|
||||
|
||||
var spreader_order3_response = await OrderUtilities.send_order(_order_service, order3_def, -0.30, 1);
|
||||
Assert.IsTrue(spreader_order3_response.success, "Failed to send spreader order");
|
||||
|
||||
var (spreader_order3_is_working, working_spreader_order3) = await verify_order_status(spreader_order3_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(spreader_order3_is_working, "Spreader order is not Working");
|
||||
|
||||
var (quoting_order3_is_working, working_quoting_order3) = await verify_child_order_status(spreader_order3_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(quoting_order3_is_working, "Order is not Working");
|
||||
Assert.IsTrue(working_quoting_order3.Value.State.Price == 18.63, "Quoting order has the wrong price");
|
||||
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order3.Value}), "Failed to cancel order");
|
||||
|
||||
var (quoting_order3_is_canceled, canceled_quoting_order3) = await verify_child_order_status(spreader_order3_response.value.OrderKey, OrderStatus.Canceled);
|
||||
Assert.IsTrue(quoting_order3_is_canceled, "Quoting order may not have been canceled");
|
||||
|
||||
// Order 4
|
||||
foreach(var key in setup_product.synthetic.strategy.LegParamsMap.Keys) {
|
||||
if(!setup_product.synthetic.strategy.LegParamsMap[key].IsActiveQuoting) {
|
||||
setup_product.synthetic.strategy.LegParamsMap[key].QuoteParams.LeanQuantityRatio = 5d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsTrue(await SpreaderUtilities.save_strategy(_synthetic_service, setup_product.synthetic.security.Id, (ExecutionStrategyImmutable) setup_product.synthetic.strategy), "Failed to save strategy");
|
||||
|
||||
var order4_def = OrderDefinitionUtilities.create_spreader_order_def(
|
||||
setup_product.synthetic.security,
|
||||
Side.Buy,
|
||||
setup_product.synthetic.strategy,
|
||||
test_account,
|
||||
test_user
|
||||
);
|
||||
|
||||
var spreader_order4_response = await OrderUtilities.send_order(_order_service, order4_def, -0.30, 1);
|
||||
Assert.IsTrue(spreader_order4_response.success, "Failed to send spreader order");
|
||||
|
||||
var (spreader_order4_is_working, working_spreader_order4) = await verify_order_status(spreader_order4_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(spreader_order4_is_working, "Spreader order is not Working");
|
||||
|
||||
var no_quoting_order4 = await get_order(ord => ord.ParentKey == spreader_order4_response.value.OrderKey, TimeSpan.FromSeconds(10));
|
||||
Assert.IsFalse(no_quoting_order4.HasValue, "There should not have been a quoting order here...");
|
||||
|
||||
OrderRecordImmutable order_from_book = setup_product.book.Where(ordi => ordi.State.Price == 18.30).First();
|
||||
var mod_resp = await OrderUtilities.modify_order(_order_service, order_from_book, 2);
|
||||
Assert.IsTrue(mod_resp.success, "Order size was not changed");
|
||||
|
||||
var (quoting_order4_is_working, working_quoting_order4) = await verify_child_order_status(spreader_order4_response.value.OrderKey, OrderStatus.Working);
|
||||
Assert.IsTrue(quoting_order4_is_working, "Order is not Working");
|
||||
Assert.IsTrue(working_quoting_order4.Value.State.Price == 18.63, "Quoting order has the wrong price");
|
||||
|
||||
var mod_resp2 = await OrderUtilities.modify_order(_order_service, order_from_book, 1);
|
||||
Assert.IsTrue(mod_resp.success, "Order size was not changed");
|
||||
|
||||
var (quoting_order5_is_canceled, canceled_quoting_order5) = await verify_child_order_status(spreader_order4_response.value.OrderKey, OrderStatus.Canceled);
|
||||
Assert.IsTrue(quoting_order5_is_canceled, "Order was not canceld");
|
||||
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order4.Value}), "Failed to cancel order");
|
||||
|
||||
}
|
||||
|
||||
private async Task<(Boolean, OrderRecordImmutable?)> verify_child_order_status(uint parent_order_key, OrderStatus expected_status) {
|
||||
var order = await get_order(ordi => ordi.ParentKey == parent_order_key && ordi.State.Status == expected_status);
|
||||
return (order.HasValue && order.Value.State.Status == expected_status, order);
|
||||
@@ -140,13 +290,22 @@ namespace bts.tests
|
||||
return order.HasValue && (Math.Truncate(order.Value.State.Price * precision) == precision * expected_price);
|
||||
}
|
||||
|
||||
private async Task<OrderRecordImmutable?> get_order(Func<OrderRecordImmutable, bool> predicate)
|
||||
private async Task<OrderRecordImmutable?> get_order(Func<OrderRecordImmutable, bool> predicate, TimeSpan? timeout = null)
|
||||
{
|
||||
if(timeout == null){
|
||||
timeout = TimeSpan.FromSeconds(60);
|
||||
}
|
||||
var orders = await OrderUtilities.wait_for_filtered_orders(_order_service,
|
||||
o => o.Where(predicate).Count() > 0,
|
||||
ord => ord.Where(predicate),
|
||||
TimeSpan.FromSeconds(30));
|
||||
return orders.result.TryFirst();
|
||||
timeout);
|
||||
if(orders.result != null) {
|
||||
return orders.result.TryFirst();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@ namespace bts.tests
|
||||
});
|
||||
var tr = await tcs.Task.timeout(timeout.Value);
|
||||
sub.Dispose();
|
||||
if(tr.result == null) {
|
||||
Console.WriteLine("Timed out while retreving orders.");
|
||||
}
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
@@ -124,7 +128,7 @@ namespace bts.tests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We should replace with something like the OrderDataHeler from the Spark project.
|
||||
/// We should replace with something like the OrderDataHelper from the Spark project.
|
||||
/// </summary>
|
||||
public static class OrderDefinitionUtilities {
|
||||
|
||||
|
||||
@@ -27,8 +27,24 @@ namespace bts.tests
|
||||
Assert.IsNotNull(exps);
|
||||
Assert.IsTrue(exps.Count() > 2, "Need at least two expirations");
|
||||
|
||||
var exp1 = exps[0];
|
||||
var exp2 = exps[1];
|
||||
(uint, uint) preferred_expirations = (07,12);
|
||||
|
||||
FutureContract exp1;
|
||||
FutureContract exp2;
|
||||
|
||||
if(exps.Exists(expir => expir.ExpirationMonth == preferred_expirations.Item1)) {
|
||||
exp1 = exps.Where(ex => ex.ExpirationMonth == preferred_expirations.Item1).First();
|
||||
}
|
||||
else {
|
||||
exp1 = exps[0];
|
||||
}
|
||||
|
||||
if(exps.Exists(expir => expir.ExpirationMonth == preferred_expirations.Item2 && expir.ExpirationYear == exp1.ExpirationYear)) {
|
||||
exp2 = exps.Where(ex => ex.ExpirationMonth == preferred_expirations.Item2 && ex.ExpirationYear == exp1.ExpirationYear).First();
|
||||
}
|
||||
else {
|
||||
exp2 = exps[1];
|
||||
}
|
||||
|
||||
Func<ChannelUpdate, bool> sec1_match = ch =>
|
||||
ch.Security.Future.ExpirationYear == exp1.ExpirationYear &&
|
||||
@@ -103,13 +119,15 @@ namespace bts.tests
|
||||
new_synth.Synthetic.Legs.Add(legs.Select(l => l.contract));
|
||||
new_synth.Symbol = deduce_symbol(new_synth);
|
||||
new_synth.ContractSpec = new ContractSpecification();
|
||||
new_synth.ContractSpec.LowTick = 0.01;
|
||||
new_synth.ContractSpec.HighTick = 0.01;
|
||||
new_synth.ContractSpec.LowTick = 0.005;
|
||||
new_synth.ContractSpec.HighTick = 0.005;
|
||||
new_synth.ContractSpec.PriceBreak = double.MaxValue;
|
||||
new_synth.ContractSpec.MainFraction = 1u;
|
||||
new_synth.ContractSpec.PriceDisplayFormat = 3u;
|
||||
new_synth.ContractSpec.CashPerPoint = 1u;
|
||||
new_synth.ContractSpec.PriceMultiplier = 1u;
|
||||
new_synth.Exchange = Venue.Bts;
|
||||
new_synth.Synthetic.Name = "TEST_SYNTH" + DateTime.Now.ToString();
|
||||
new_synth.Synthetic.Name = "TEST_SYNTH" + DateTime.Now.TimeOfDay.ToString();
|
||||
|
||||
var result = await service.create_synthetic_async(new_synth);
|
||||
Assert.IsTrue(result.success, "Unable to create synthetic");
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
fi
|
||||
|
||||
./delete_dbs.sh
|
||||
|
||||
config=$sparkdata_dir/conf/spark.conf
|
||||
bak=$sparkdata_dir/conf/spark.conf.bak
|
||||
|
||||
[ ! -d "$sparkdata_dir/conf" ] && mkdir $sparkdata_dir/conf/
|
||||
[ -f "$conf" ] && mv $conf $bak
|
||||
|
||||
cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
ExchangeSim {
|
||||
value {
|
||||
merge_with_md:false
|
||||
}
|
||||
}
|
||||
Logging {
|
||||
value {
|
||||
console { default_level: "Error" }
|
||||
syslog { default_level: "Error" }
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
|
||||
SymbolList {
|
||||
value {
|
||||
symbol: "SI"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
nohup ./sparkd run &
|
||||
pid=$!
|
||||
echo "sparkd pid: " $pid
|
||||
|
||||
dotnet sparkcmd.dll test spreader_lean_qty_factor
|
||||
|
||||
exit_status=$?
|
||||
|
||||
pkill -P $$
|
||||
|
||||
[ -f "$bak" ] && mv $bak $conf
|
||||
[ $exit_status -eq 0 ] && echo "Test Finished" || echo "Test failed"
|
||||
exit $exit_status
|
||||
@@ -27,7 +27,8 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
syslog { default_level: "Error" }
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
}
|
||||
MarketDataConfig { value { use_onixs: false; } }
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
syslog { default_level: "Error" }
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
}
|
||||
MarketDataConfig { value { use_onixs: false; } }
|
||||
EOF
|
||||
|
||||
nohup ./sparkd run &
|
||||
|
||||
@@ -60,25 +60,28 @@ say_verbose() {
|
||||
fi
|
||||
}
|
||||
|
||||
is_merge_with_md_on() {
|
||||
# is_merge_with_md_on() {
|
||||
# eval $invocation
|
||||
|
||||
# if [ $(./sparkd configcheck ExchangeSim | grep merge_with_md: | cut -d " " -f 2) == "false" ]; then
|
||||
# return 0
|
||||
# else
|
||||
# return 1
|
||||
# fi
|
||||
# }
|
||||
|
||||
set_config() {
|
||||
eval $invocation
|
||||
|
||||
if [ $(./sparkd configcheck ExchangeSim | grep merge_with_md: | cut -d " " -f 2) == "false" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
set_merge_with_md_off() {
|
||||
eval $invocation
|
||||
[ ! -d "$sparkdata_dir/conf" ] && mkdir $sparkdata_dir/conf/
|
||||
[ -f "$spark_conf" ] && mv $spark_conf $spark_conf_bak
|
||||
|
||||
if [ -f $config_file_path ]; then
|
||||
say_verbose "config file exists ... removing"
|
||||
rm $config_file_path
|
||||
fi
|
||||
|
||||
echo "$merge_with_md_key" >> $config_file_path && test_config_file_created=true
|
||||
echo "$test_conf" >> $config_file_path && test_config_file_created=true
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -97,14 +100,6 @@ get_tests_in_class() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# Start sparkd
|
||||
nohup ./sparkd run &
|
||||
pid=$!
|
||||
echo "sparkd pid: " $pid
|
||||
echo "sleeping for 120 seconds..."
|
||||
sleep 120
|
||||
|
||||
|
||||
# args
|
||||
# class_name - $1
|
||||
run_bts_test() {
|
||||
@@ -125,27 +120,43 @@ run_bts_test() {
|
||||
|
||||
test_script_name=$(basename "$0")
|
||||
verbose=true
|
||||
spark_conf=$sparkdata_dir/conf/spark.conf
|
||||
spark_conf_bak=$sparkdata_dir/conf/spark.conf.bak
|
||||
config_file_path="$sparkdata_dir/conf/test_join_algo_tests.conf"
|
||||
test_config_file_created=false
|
||||
merge_with_md_key=$(/bin/cat <<'END_HEREDOC'
|
||||
test_conf=$(/bin/cat <<'END_HEREDOC'
|
||||
ExchangeSim {
|
||||
value {
|
||||
merge_with_md: false
|
||||
}
|
||||
}
|
||||
MarketDataConfig {
|
||||
value {
|
||||
use_onixs: false;
|
||||
}
|
||||
}
|
||||
END_HEREDOC
|
||||
)
|
||||
|
||||
if is_merge_with_md_on; then
|
||||
set_merge_with_md_off
|
||||
if is_merge_with_md_on; then
|
||||
say_err "Could not turn off merge_with_md"
|
||||
say_err "Please investigate manually"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
say_verbose "merge with md is already off"
|
||||
fi
|
||||
# Start sparkd
|
||||
set_config
|
||||
nohup ./sparkd run &
|
||||
pid=$!
|
||||
echo "sparkd pid: " $pid
|
||||
echo "sleeping for 60 seconds..."
|
||||
sleep 60
|
||||
|
||||
|
||||
# if is_merge_with_md_on; then
|
||||
# set_config
|
||||
# if is_merge_with_md_on; then
|
||||
# say_err "Could not turn off merge_with_md"
|
||||
# say_err "Please investigate manually"
|
||||
# exit 1
|
||||
# fi
|
||||
# else
|
||||
# say_verbose "merge with md is already off"
|
||||
# fi
|
||||
|
||||
run_bts_test "JoinAlgoTests"
|
||||
|
||||
@@ -153,6 +164,8 @@ if [ "$test_config_file_created" = true ]; then
|
||||
rm $config_file_path
|
||||
fi
|
||||
|
||||
[ -f "$spark_conf_bak" ] && mv $spark_conf_bak $spark_conf
|
||||
|
||||
# Kill sparkd
|
||||
pkill -P $$
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
syslog { default_level: "Error" }
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
}
|
||||
MarketDataConfig { value { use_onixs: false; } }
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
@@ -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 @@
|
||||
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)
|
||||
@@ -58,7 +58,7 @@ namespace interop {
|
||||
// Functions used for interop to other languages
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
std::string getbuffer_serialized_buffer;
|
||||
extern "C" BTS_EXPORT const char* get_buffer(const char* key_name, int& out_length)
|
||||
{
|
||||
@@ -69,7 +69,7 @@ extern "C" BTS_EXPORT const char* get_buffer(const char* key_name, int& out_leng
|
||||
|
||||
getbuffer_serialized_buffer.clear();
|
||||
message.SerializePartialToString(&getbuffer_serialized_buffer);
|
||||
|
||||
|
||||
out_length = getbuffer_serialized_buffer.size();
|
||||
return getbuffer_serialized_buffer.c_str();
|
||||
}
|
||||
@@ -89,10 +89,14 @@ namespace internal {
|
||||
// Functions used in testing
|
||||
//////////////////////////////////////////
|
||||
|
||||
extern "C" BTS_EXPORT void initialize_for_test(const std::string& values, bool print_errors)
|
||||
extern "C" BTS_EXPORT void initialize_for_test(const char* values, bool print_errors)
|
||||
{
|
||||
config_state.reset();
|
||||
config_state.load_string(values, print_errors);
|
||||
config_state.load_string(std::string(values), print_errors);
|
||||
}
|
||||
void initialize_for_test(const std::string& value, bool print_errors) {
|
||||
config_state.reset();
|
||||
config_state.load_string(value, print_errors);
|
||||
}
|
||||
|
||||
extern "C" BTS_EXPORT void reset()
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace config {
|
||||
// Load and initialize config.
|
||||
// This will be automatically called on the first key request but since loading config
|
||||
// can result in network requests this function is available for applications that want to
|
||||
// manage pauses in execution.
|
||||
// manage pauses in execution.
|
||||
// Ex: initialize() on a background thread so that config::get<T>() is fast on a UI thread
|
||||
extern "C" BTS_EXPORT void initialize();
|
||||
|
||||
@@ -56,7 +56,8 @@ namespace config {
|
||||
{
|
||||
// Initialize configuration state from a string instead of from the filesystem. defaults.conf will still be loaded.
|
||||
// Calling this a second time clears all values from previous initialization
|
||||
extern "C" BTS_EXPORT void initialize_for_test(const std::string& configuration, bool print_errors=true);
|
||||
extern "C" BTS_EXPORT void initialize_for_test(const char* configuration, bool print_errors=true);
|
||||
void initialize_for_test(const std::string& configuration, bool print_errors=true);
|
||||
|
||||
// Releases all resources and returns config to factory defaults
|
||||
extern "C" BTS_EXPORT void reset();
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -348,8 +348,8 @@ let clipLimitsView model dispatch =
|
||||
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus-circle" Button.Enabled addRow)
|
||||
]
|
||||
|
||||
let lazyClipLimitsView model dispatch =
|
||||
lazyView2With (fun a b ->
|
||||
let lazyClipLimitsView model dispatch =
|
||||
lazyView2With (fun a b ->
|
||||
LanguagePrimitives.PhysicalEquality a.SelectedClipLimits b.SelectedClipLimits &&
|
||||
LanguagePrimitives.PhysicalEquality a.scratch.securityClipLimits b.scratch.securityClipLimits)
|
||||
clipLimitsView
|
||||
@@ -542,10 +542,11 @@ let productLimits model dispatch =
|
||||
br []
|
||||
]
|
||||
|
||||
let lazyProductLimitsView model dispatch =
|
||||
lazyView2With (fun a b ->
|
||||
let lazyProductLimitsView model dispatch =
|
||||
lazyView2With (fun a b ->
|
||||
LanguagePrimitives.PhysicalEquality a.SelectedPositionLimits b.SelectedPositionLimits &&
|
||||
LanguagePrimitives.PhysicalEquality a.scratch.productPositionLimits b.scratch.productPositionLimits)
|
||||
LanguagePrimitives.PhysicalEquality a.scratch.productPositionLimits b.scratch.productPositionLimits &&
|
||||
a.scratch.riskLimitEnforcement = b.scratch.riskLimitEnforcement)
|
||||
productLimits
|
||||
model dispatch
|
||||
|
||||
@@ -764,7 +765,7 @@ let tabContent model dispatch =
|
||||
div [Key (string model.scratch.id); ClassName "is-tab-content"] [
|
||||
// (match model.tab with
|
||||
(match model.tab, (accountIsSelected model.scratch Access.Accounts.empty) with
|
||||
| _ , false -> p [Style [ Color "Red" ]] [ str "You are not authorized to view this data" ]
|
||||
| _ , false -> p [Style [ Color "Red" ]] [ str "You are not authorized to view this data" ]
|
||||
| General, _ -> generalView model dispatch
|
||||
| LimitsTab, _ -> LimitsView model dispatch
|
||||
| ConnectionsTab, _ -> ConnectionsView model dispatch
|
||||
@@ -871,7 +872,7 @@ let menuItem account selectedAccount model dispatch =
|
||||
]
|
||||
]
|
||||
]
|
||||
else span[][])
|
||||
else span[][])
|
||||
]
|
||||
|
||||
let NavRoot (accounts:BTSAccount[]) selectedAccount model dispatch =
|
||||
@@ -881,10 +882,10 @@ let NavRoot (accounts:BTSAccount[]) selectedAccount model dispatch =
|
||||
span [Class "is-size-4"] [str "Accounts"]
|
||||
span [ Class "is-pulled-right circle-icon" ]
|
||||
(if model.can_create then
|
||||
[ Button.fabutton
|
||||
[ Button.fabutton
|
||||
"Add Account"
|
||||
"plus-circle"
|
||||
""
|
||||
""
|
||||
Button.Enabled
|
||||
(fun _ -> dispatch AddAccount) ]
|
||||
else [])
|
||||
@@ -929,7 +930,7 @@ let root (model:Model) dispatch =
|
||||
[ NavRoot data.accounts (selectedAccount model) model dispatch
|
||||
div [ Class "column is-main-content" ]
|
||||
[
|
||||
|
||||
|
||||
div [ ClassName "tabs is-boxed" ] [
|
||||
ul [] [
|
||||
(if (Array.length data.accounts > 0) then (tabHeader General model.tab dispatch) else span [][] )
|
||||
|
||||
@@ -2,9 +2,9 @@ This test verifies that fractional hedges work. That is, when a fill in a quotin
|
||||
not equate to one full contract in the hedging leg, we accumulate the unhedged
|
||||
delta, until we can send one or more hedging contracts.
|
||||
|
||||
1. Create a new synthetic spread. For the legs choose SIF9 and SIH9 (if you dont
|
||||
1. Create a new synthetic spread. For the legs choose SIF9 and SIH9 (if you don't
|
||||
have these contracts, other contracts can be chosen too) and for the quantities choose 1 and -1.5
|
||||
respectively (and same for the Price multiplier).
|
||||
respectively (and same for the Price multiplier).
|
||||
Enable quoting for SIH9, and disable all quoting, pre-hedge and post-hedge parameters.
|
||||
Name the synthetic SIF9-1.5SIH9.
|
||||
|
||||
@@ -18,13 +18,13 @@ to enter orders). The quoting order will lean against this.
|
||||
4. Verify that a 15 lot quoting order is sent for SIH9 at 19.935.
|
||||
|
||||
5. Now we will start filling the quoting order and verify the hedges. A 1 lot fill in the
|
||||
quoting leg means a 2/3 contract in the hedging leg. Since we can not send 2/3 constracts,
|
||||
quoting leg means a 2/3 contract in the hedging leg. Since we can not send 2/3 contracts,
|
||||
we will add it to the "unhedged delta" till the unhedged delta > 1, and then we will send one
|
||||
or more orders to get the unhedged delta < 1.
|
||||
|
||||
Force quoting fills based on the left most column (by lifting the quoting offer)
|
||||
and verify that hedge orders of size "hedge_order_sent" (3rd column) are sent as a result.
|
||||
The "unhedged_delta" column shows the unhedged delta immediatly after the quoting fill
|
||||
The "unhedged_delta" column shows the unhedged delta immediately after the quoting fill
|
||||
and the "remaining_unhedged_delta" column is the unhedged delta after the hedge order
|
||||
has been sent.
|
||||
You can check the hedge orders being sent easily by looking at the order reduction
|
||||
|
||||
@@ -10,7 +10,7 @@ ExchangeSim {
|
||||
}
|
||||
}
|
||||
|
||||
1. Open up the mview and find a security with with both direct and implied markets, on the ask side.
|
||||
1. Open up the mview and find a security with with both direct and implied markets, on the ask side.
|
||||
Make sure those markets are stable and you can rely on them.
|
||||
|
||||
2. Create a synthetic with the security in 1. being the hedge leg (buy) and choose a quoting
|
||||
@@ -19,11 +19,11 @@ use "Merged"
|
||||
|
||||
3. Disable all spreader params.
|
||||
|
||||
4. Now enter a spreader order so that it doesnt get filled immediatly. Note the quoting order.
|
||||
4. Now enter a spreader order so that it doesn't get filled immediately. Note the quoting order.
|
||||
quoting leg price = hedge_price - spreader_price.
|
||||
hedge_price is where it can buy the hedge order (which since its a buy will be on the ask side of
|
||||
the hedging market).
|
||||
Make sure thta its correct and its using the merged book in the hedging market.
|
||||
Make sure that its correct and its using the merged book in the hedging market.
|
||||
|
||||
5. Cancel the order and change the spreader so its using Direct prices.
|
||||
Repeat 4, and make sure its using direct price on the hedging market.
|
||||
@@ -5,8 +5,8 @@ with 1 quoting leg.
|
||||
|
||||
1. Create a 4 legged synthetic spread with legs SIF9, SIH9, SIK9, SIN9, with leg quantities and
|
||||
price multipliers of 1,1,1 and -1 respectively. Leg SIN9 is the only quoting leg.
|
||||
Disable all Quote, Pre-Hedge and Post-Hedge paramaters (leg contracts are arbitrary, so if you
|
||||
dont have those contracts, choose any set of 4 contracts).
|
||||
Disable all Quote, Pre-Hedge and Post-Hedge parameters (leg contracts are arbitrary, so if you
|
||||
don't have those contracts, choose any set of 4 contracts).
|
||||
|
||||
2. Enter a synthetic buy order of 10 lots at 40.00. The ladders will most likely be empty, so
|
||||
use the market view to enter orders.
|
||||
@@ -16,7 +16,7 @@ Now enter:
|
||||
SIH9 10 lot sell @ 19.950
|
||||
SIK9 10 lot sell @ 20.200
|
||||
|
||||
There should still be no quoting order for SIN9 since the SIF9 hedge leg still doesnt have a market.
|
||||
There should still be no quoting order for SIN9 since the SIF9 hedge leg still doesn't have a market.
|
||||
Now Enter:
|
||||
SIF9 1 lot sell @ 20.000
|
||||
|
||||
|
||||
Reference in new issue
Block a user