Compare commits

...
37 Commits
Author SHA1 Message Date
Jake Norbut 845fa99bd0 swapping these statements so no NullRef 2019-08-21 10:48:48 -05:00
John Colvin 2b5403bbd5 Merged in sorted-wrapper-crash (pull request #324)
SP-188 DiscreteValuesFilterVM SortedWrapper crash
2019-08-21 15:34:50 +00:00
John Colvin c9ff3e1bb3 SP-188 DiscreteValuesFilterVM SortedWrapper crash 2019-08-21 10:21:18 -05:00
Taha Afzal cdbda8250f 'Fixing' the test (the test is broken, fill really fix it in the subsequent push) 2019-08-14 22:14:54 -04:00
John Colvin 43fd07dcfa Merged in SOM-ticks (pull request #320)
SPARK-1715 Handle fractional numerator tick sizes

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-14 20:43:00 +00:00
John Colvin efcb4f471c fix config toggle for som ticks 2019-08-14 15:40:23 -05:00
John Colvin d2f2e03e64 [SPARK-1715] config toggle for som ticks fix 2019-08-14 15:31:30 -05:00
John Colvin cfeba74900 SPARK-1715 Handle fractional numerator tick sizes 2019-08-14 14:50:42 -05:00
Kevin Darby b080d4a9c4 Merged in master (pull request #318)
Spark 2.8

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-13 19:19:47 +00:00
pfisher 282e10cba3 fixed update.txt test 2019-08-13 12:04:11 -04:00
Kevin Darby fa1b436fb3 added sticky button release notes 2019-08-09 10:53:02 -04:00
Kevin Darby 191518d8bc Merged in fix_algo_docs (pull request #316)
removing duplicate mark down describing algos

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
2019-08-08 20:26:11 +00:00
Kevin Darby 50768750d6 removing duplicate mark down describing algos 2019-08-08 16:14:49 -04:00
Jake Norbut 450ef7dd56 Merged in SP-128 (pull request #312)
SP-128 - Allow for configurable stickyness on Algo Favorites

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
2019-08-08 19:26:03 +00:00
Jake Norbut 89a4f9264a LadderSaveState now at version 2; implementing upgrade mechanism for migrating existing favorite algos to stickied algos 2019-08-08 13:03:26 -05:00
Jake Norbut 55ade15574 provide upgrade mechanism when loading IShellWindow 2019-08-08 13:01:19 -05:00
Kevin Darby 36b8b35e47 Merged in fix_tooltips2 (pull request #315)
fix non-dynamic and non-generic tooltips

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-08-08 16:18:29 +00:00
Kevin Darby 23bb872052 fix non-dynamic and non-generic tooltips 2019-08-08 12:09:48 -04:00
Jake Norbut bf8df1d396 always push Limit onto the algo stack; henceforth known as the anchor 2019-08-06 10:24:59 -05:00
John Colvin d064af817c remove unreferenced '_sticky_algos' field 2019-08-05 15:51:21 -05:00
John Colvin 7b3c4b76bc LadderVM/OrderPropertiesEditorVM sticky/stack behavior 2019-08-05 15:36:57 -05:00
John Colvin 5de431f279 make LadderVM.send_order_command an IAsyncCommand 2019-08-05 15:36:57 -05:00
John Colvin 7b63d951f0 ShellSettingsDialogVM profilevm resets 'is_dirty' whenever the internal vm's resultant value equals the target value 2019-08-05 15:36:57 -05:00
John Colvin 018887980b LadderSettingsVM 'sticky buttons' impl reacts to FavoriteAlgoItem's value (algo) changes 2019-08-05 15:06:39 -05:00
John Colvin 8b4d649bd4 ExecuteAsync should return the Task directly (no need to await internally) 2019-08-05 15:06:39 -05:00
Jake Norbut 441d3c886b fixing improper use of is 2019-08-05 15:06:39 -05:00
Jake Norbut 472e32f558 removing empty new lines; leftovers from Dispatcher debugging 2019-08-05 15:06:39 -05:00
Jake Norbut 472c9252f9 adding Stay Enabled (sticky) checkbox to Favorites UI 2019-08-05 15:06:39 -05:00
Jake Norbut 98a9ad3ef2 reset order type after non-sticky order entry 2019-08-05 15:06:39 -05:00
Jake Norbut 7b237fedea adding sticky-related bits 2019-08-05 15:06:39 -05:00
Jake Norbut 7281aa8ff1 adding is_sticky property 2019-08-05 15:06:39 -05:00
Jake Norbut d915e2f064 setting algo_def_vm.is_sticky via CollectionChanged event (hmm) 2019-08-05 15:06:39 -05:00
Jake Norbut beb5d855b1 Dispatcher debuggering with John; think does not appear to work since the if condition is never met, but commiting this part of the pair-debugging experience 2019-08-05 15:06:39 -05:00
Jake Norbut 120b86aaae popuplating stickied algo keys for persistence/restoration state 2019-08-05 15:06:39 -05:00
Jake Norbut 44eeff98e7 adding is_sticky member to which the view will Databind 2019-08-05 15:06:39 -05:00
Jake Norbut dc420a097a adding stickied_algos ProtoMember for persistence 2019-08-05 15:06:39 -05:00
Jake Norbut 69ee85dea9 misc - adding OnDispatcher after discussing with John 2019-08-05 15:06:39 -05:00
22 changed files with 201 additions and 109 deletions

No files matched your search

+1
View File
@@ -16,6 +16,7 @@ insert_anchor_links = "right"
selected by default
- Increased width of login panel to better show login error messages
- Added optional 'Maker/Taker' pricing to Spreader algo
- Algo buttons in ladder can now be made to deactivate after launching an algo or remain 'sticky'
### Internal changes
- Made it easier to generate simple UI's for new order types
### API Changes
@@ -10,7 +10,7 @@ using System.Reactive.Disposables;
namespace mbts.spark {
static class AlgoParamRowsHelper {
public static IDisposable connect(AlgoDefinitionVM algo, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) =>
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().SubscribeEach(a => {
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().OnDispatcher().SubscribeEach(a => {
rows.clear_collections();
if (a == null)
return Disposable.empty;
@@ -77,7 +77,7 @@ namespace mbts.spark {
include_algo_type_rows ?
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
child_rows,
s.subscribe_selected_algo().SubscribeEach(a => {
s.subscribe_selected_algo().OnDispatcher().SubscribeEach(a => {
s.value = a;
child_rows.clear_collections();
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
@@ -194,20 +194,27 @@ namespace mbts.spark {
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
[ProtoMember(2)]
public readonly int column_count;
[ProtoMember(3)]
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)), column_count) { }
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
column_count,
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count) {
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
_favorite_algos = favorite_algos;
this.column_count = column_count;
_stickied_algos = stickied_algos;
}
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
public bool Equals(LadderFavoriteAlgosSettings other) =>
column_count == other.column_count &&
favorite_algos.SequenceEqual(other.favorite_algos);
favorite_algos.SequenceEqual(other.favorite_algos) &&
stickied_algos.SequenceEqual(other.stickied_algos);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
@@ -215,7 +222,8 @@ namespace mbts.spark {
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
column_count,
EqualityComparer.aggregate_hash_limited(favorite_algos));
EqualityComparer.aggregate_hash_limited(favorite_algos),
EqualityComparer.aggregate_hash_limited(stickied_algos));
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
@@ -273,7 +281,7 @@ namespace mbts.spark {
order_entry_panel_initial_width = _default_order_entry_panel_width;
zoom_factor = _default_zoom_factor;
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
show_warn_on_cross = _default_show_warn_on_cross;
}
@@ -11,6 +11,7 @@ using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
@@ -22,6 +23,7 @@ using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
class LadderWindowFactory : ShellWindowFactory<LadderSaveState> {
const string _window_type = "ladder";
const int _version_where_stickied_algos_introduced = 2;
readonly LadderConfig _ladder_conf;
readonly IBus _bus;
readonly IWindowManager _wm;
@@ -38,7 +40,7 @@ namespace bts.shell {
OrdersModel orders_model,
MBOService mbo)
: base(owner, "SparkLadder") {
: base(owner, "SparkLadder", 2) {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
@@ -60,6 +62,16 @@ namespace bts.shell {
return null;
}
protected override Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) {
var state = ProtoBuf.Serializer.DeserializeWithLengthPrefix<LadderSaveState>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
if (version < _version_where_stickied_algos_introduced) {
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
}
return load(state);
}
LadderSaveState _get_state(LadderVM vm, uint id) {
LadderSettings s = vm.get_save_state();
if (id != 0) {
@@ -2,6 +2,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.persist.algo;
using bts.shell.orderbook;
using bts.utils;
using mbts.collections;
@@ -723,12 +724,15 @@ namespace mbts.spark {
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
int _column_count = 2;
HashSet<AlgoPersistedKey> _sticky_algo_keys;
IDisposable _sticky_algos_hook;
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
_algo_definitions = algo_definitions;
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
}
public ObservableCollection<FavoriteAlgoItem> items { get; }
@@ -745,6 +749,17 @@ namespace mbts.spark {
fai.selector.try_select(k);
return fai;
}));
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
Disposable.replace(ref _sticky_algos_hook,
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
x.selector.subscribe_value().Subscribe(algo => {
if (algo != null &&
algo.key.is_persisted &&
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
_sticky_algo_keys.Count == 0)
//
Disposable.dispose(ref _sticky_algos_hook);
})));
}
public LadderFavoriteAlgosSettings create() =>
@@ -753,7 +768,13 @@ namespace mbts.spark {
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray(),
column_count);
column_count,
_raw_items
.Where(fai => fai.is_sticky)
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
.Where(k => k.HasValue && k.Value.is_persisted)
.Select(k => k.Value)
.ToArray());
FavoriteAlgoItem _item_factory() =>
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
@@ -766,6 +787,7 @@ namespace mbts.spark {
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
bool _is_sticky;
protected FavoriteAlgoItem() { }
@@ -776,6 +798,7 @@ namespace mbts.spark {
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
public string key => LadderSettingsView.FavoriteAlgoAddButtonTemplateKey;
@@ -1,9 +1,11 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
using mbts.co2;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.utils;
@@ -32,6 +34,7 @@ namespace mbts.spark {
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
_show_cancel_sells, _show_cancel_all, _show_zoom;
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
double _position, _day_position;
bool _show_account_position, _show_day_position;
ulong _misc_time = 0;
@@ -133,7 +136,7 @@ namespace mbts.spark {
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
send_order_command = new AsyncCommand<(double price, Side? side, MouseButton button)>(send_order_async);
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
show_book_command = new DelegateCommand(_show_book);
show_ticket_command = new DelegateCommand(_show_ticket);
@@ -223,7 +226,10 @@ namespace mbts.spark {
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
return Disposable.Create(
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
settings.OnDispatcher().Subscribe(s => {
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
order_properties_vm.on_favorite_algos(s.favorite_algos);
}),
order_properties_vm,
position_src.OnDispatcher().Subscribe(pos => {
double total, day;
@@ -400,20 +406,19 @@ namespace mbts.spark {
settings_invalidated?.Invoke();
}
public void send_order((double price, Side? side, MouseButton button) args) {
if (order_properties_vm == null || args.button == MouseButton.Middle)
return;
var size_from_alternate = args.button == MouseButton.Right;
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
return;
}
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
}
else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
send_order_async(args.side.Value, size, args.price).ignore();
public Task send_order_async((double price, Side? side, MouseButton button) args) {
if (order_properties_vm != null && args.button != MouseButton.Middle) {
var size_from_alternate = args.button == MouseButton.Right;
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
} else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
return send_order_async(args.side.Value, size, args.price);
}
}
}
return Task.CompletedTask;
}
public async Task send_order_async(Side side, uint size, double price) {
@@ -433,6 +438,13 @@ namespace mbts.spark {
await _model.order_gateway.send_order(order, price, size);
if (auto_clear)
size_calculator.value = 0;
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
order_properties_vm.selected_algo_definition = r;
}
}
}
void _update_header() {
@@ -805,6 +805,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
</Grid.ColumnDefinitions>
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
@@ -814,6 +815,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
</Grid>
</DataTemplate>
</u:KeyedTemplateSelector.Default>
@@ -823,6 +825,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" HorizontalAlignment="Stretch" Command="{Binding DataContext.add_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}">Add</Button>
</Grid>
@@ -60,6 +60,7 @@ namespace mbts.spark {
readonly ListCollectionView<AlgoDefinitionVM> _algo_definitions_view, _favorite_algo_definitions_view;
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(d => d.key);
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
readonly AlgoDefinitionVM the_anchor_algo_def_vm;
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
IDisposable _algo_param_rows_hook;
@@ -68,6 +69,7 @@ namespace mbts.spark {
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineOrderProperties> order_properties) {
_model = model;
the_anchor_algo_def_vm = AlgoDefinitionVM.create(_model, OrderDataHelper.Defaults.AlgoData(CustomOrderDefinition.CustomOneofCase.Limit));
var security_source = security_md_source.Select(smd => smd.security);
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
security_source = _security.observe_current(true);
@@ -237,11 +239,15 @@ namespace mbts.spark {
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
Disposable.dispose(ref _algo_param_rows_hook);
if (old_value != null)
// Taha feels that we should always revert to a Limit order either when the user explicity unchecks a sticky button
// or when a non-sticky button is pressed. Rather than completely remove the stack at this time (b/c it may be come
// back in the future), let's just always push a specfic def_vm.key onto it [Limit] ;)
//_selected_algo_stack.Push(old_value.key);
_selected_algo_stack.Push(the_anchor_algo_def_vm.key);
if (new_value != null) {
if (use_algo_param_grid)
_algo_param_rows_hook = _connect_algo_param_rows(new_value, _algo_param_rows);
if (reason != SelectionReason.Explicit)
_selected_algo_stack.Push(new_value.key);
}
if (open_algo_popup_on_select) {
if (reason == SelectionReason.UserSelectedNewValue && new_value?.algo_root is IAlgoVM ar && ar.can_expand)
@@ -277,6 +283,18 @@ namespace mbts.spark {
get => _selected_algo_definition.selected_item;
set => _selected_algo_definition.selected_item = value;
}
public bool try_get_algo_definition(AlgoKey a, out AlgoDefinitionVM result) =>
_algo_definitions.TryGetValue(a, out result);
public bool try_pop_selected_algo_definition() {
if (_selected_algo_stack.TryPop(out AlgoKey k) && _algo_definitions.TryGetValue(k, out AlgoDefinitionVM vm)) {
_selected_algo_definition.select_explicit(vm);
return true;
}
return false;
}
public AlgoDefinitionVM selected_favorite_algo_definition {
get => selected_algo_definition?.is_favorite ?? false ? selected_algo_definition : null;
set {
@@ -353,6 +371,7 @@ namespace mbts.spark {
d.favorite_index = _get_favorite_algo_index(d.key);
}
favorite_algos_column_count = value.column_count;
RaisePropertyChanged(nameof(selected_favorite_algo_definition));
}
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
@@ -371,4 +390,4 @@ namespace mbts.spark {
algo_definitions_view.Refresh();
}
}
}
}
@@ -34,13 +34,14 @@ namespace bts.shell {
Disposable.dispose(ref _dispose);
}
public abstract Task<IShellWindow> load(T state);
protected virtual Task<IShellWindow> try_upgrade_state(Stream load_stream, int version) => null;
public Task<IShellWindow> load_window(Stream load_stream, int version) {
if (version == this.version) {
var t = ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(load_stream, ProtoBuf.PrefixStyle.Fixed32);
return load(t);
}
return null;
return try_upgrade_state(load_stream, version);
}
public void save(T data, Stream output) {
var header = new ShellSerializationHeader() {
@@ -110,10 +110,11 @@ namespace mbts.spark.filters {
class DynamicItemsFilter<TRow, TValue, TState> : FilterVMBase<TRow, TValue, TState>, IDisposable {
readonly IEqualityComparer<TValue> _equality_comparer;
readonly IComparer<TValue> _comparer;
readonly Func<ISet<TValue>, TState> _get_state;
readonly Func<TState, IObservable<IEnumerable<TValue>>> _load_state;
readonly HashSet<TValue> _items_set, _selected_items_set;
readonly ObservableCollection<TValue> _unsorted_items;
readonly ObservableCollection<TValue> _items = new ObservableCollection<TValue>();
bool _override_desired_selection;
IDisposable _desired_items_sub, _items_sub;
@@ -129,7 +130,7 @@ namespace mbts.spark.filters {
//
_items_sub = items.OnDispatcher().Subscribe(new_items => {
_items_set.SetItems(new_items.Concat(_selected_items_set));
_unsorted_items.sync_with(_items_set);
_sync_items();
});
}
@@ -148,7 +149,7 @@ namespace mbts.spark.filters {
_items_set.Remove(v);
foreach (var v in update.added)
_items_set.Add(v);
_unsorted_items.sync_with(_items_set);
_sync_items();
});
}
@@ -164,11 +165,11 @@ namespace mbts.spark.filters {
_get_state = get_state;
_load_state = load_state;
_equality_comparer = equality_comparer;
_comparer = comparer;
_selected_items_set = new HashSet<TValue>(_equality_comparer);
_items_set = new HashSet<TValue>(_equality_comparer);
_unsorted_items = new ObservableCollection<TValue>();
items = comparer != null ? ObservableAutoWrapper.create_sorted_wrapper(_unsorted_items, comparer) : ObservableAutoWrapper.create_read_only_wrapper(_unsorted_items);
selected_items.CollectionChanged += _on_selected_items_CollectionChanged;
_sync_items();
}
public IReadOnlyObservableCollection<TValue> items { get; }
@@ -238,9 +239,11 @@ namespace mbts.spark.filters {
var copy = new HashSet<TValue>(_selected_items_set, _equality_comparer);
_selected_items_set.SetItems(dsi);
using (selected_items.SuspendHandler(_on_selected_items_CollectionChanged)) {
foreach (var item in _selected_items_set) {
if (_items_set.Add(item))
_unsorted_items.Add(item);
var to_add = _selected_items_set.Where(_items_set.Add);
if (_comparer != null) {
_items.InsertSorted(to_add, _comparer);
} else {
_items.AddRange(to_add);
}
selected_items.sync_with(_selected_items_set);
}
@@ -248,6 +251,14 @@ namespace mbts.spark.filters {
_raise_filter_changed();
}
}
void _sync_items() {
if (_comparer != null) {
_items.sync_with(_items_set.Ordered(_comparer));
} else {
_items.sync_with(_items_set);
}
}
}
class TableUpdateItemsFilter<TRow, TValue, TKey, TState> : FilterVMBase<TRow, TValue, TState>, IObserver<TableUpdate<TValue, TKey>> where TKey : struct, IEquatable<TKey> {
@@ -687,7 +687,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Tick Offset</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" >Tick Offset</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="How many ticks off the inside market to deploy the order, this guards against crossing the market inadventently" />
</StackPanel>
<c:TickUpDownUInt x:Name="PART_TickOffsetOnDeploy_TickUpDownUInt" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
@@ -905,7 +908,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on GLBX, this is the exchange provided iceberg" />
</StackPanel>
<c:TickUpDownNullableUInt x:Name="PART_MaxShowQuantity_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_show_quantity}"/>
</Grid>
</DataTemplate>
@@ -944,7 +950,10 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="Maximum size to show on exchange, this is the BTS provided iceberg" />
</StackPanel>
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding show_quantity}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
@@ -1022,9 +1031,16 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
<StackPanel Orientation="Horizontal" Grid.Row="0" >
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Expose Quantity</TextBlock>
<c:HelpButton Margin="4,0,0,0" ToolTip="The quantity we want visible on the exchange" />
</StackPanel>
<c:TickUpDownUInt Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding exposed}"/>
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
<StackPanel Orientation="Horizontal" Grid.Row="1" >
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Max Order Quantity</TextBlock>
<c:HelpButton Margin="{StaticResource Margin.LP3}" ToolTip="No single order will have a quantity greater than this quantity (Clip Size)" />
</StackPanel>
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding max_order_qty}"/>
<uag:AlgoPickerNodeView
x:Name="PART_Child_AlgoPickerNodeView"
@@ -236,12 +236,12 @@ namespace mbts.ui {
throw new NotImplementedException("This ICommand does not implement IRaiseCanExecuteChanged");
}
public static async Task ExecuteAsync(this ICommand command, object parameter) {
public static Task ExecuteAsync(this ICommand command, object parameter) {
ExceptionAssert.Argument.NotNull(command, nameof(command));
if (command is IAsyncCommand acmd)
await acmd.Execute(parameter);
return acmd.Execute(parameter);
else
await Task.Run(() => command.Execute(parameter));
return Task.Run(() => command.Execute(parameter));
}
public static void TryExecuteIfCan(this ICommand command, object parameter) {
@@ -136,8 +136,8 @@ namespace mbts.spark {
var skip_local = profiles.WrapSkip(1);
var skip_local_wrapper = skip_local.WrapSelector(
p => new SettingsProfile<T>(p.id, p.name, p.value.create(), p.security_criteria),
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria));
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey));
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria, target_value: ns.value));
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey, target_value: init));
_selected_profile = backing(nameof(selected_profile), _local_profile);
// dialog skirt items
_apply_item = new DialogSkirtItem("Apply", new DelegateCommand(apply));
@@ -325,26 +325,37 @@ namespace mbts.spark {
//
SecurityCriteria _security_criteria;
SettingsProfile<T> _committed, _last_result;
T _target_value;
string _name, _template_key;
bool _is_selected;
IDisposable _dispose;
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null) {
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null, T target_value = null) {
this.id = id;
this.value = value;
_name = name;
_security_criteria = security_criteria;
_template_key = template_key;
_target_value = target_value;
_committed = _last_result = create();
_is_dirty = backing(nameof(is_dirty), () => !_last_result.Equals(_committed));
_refresh_is_dirty = new DeferrableActionWrapper(_is_dirty.refresh);
var sub = value.Subscribe(v => {
_last_result = _last_result.with(value: v);
_refresh_is_dirty.execute();
_on_value_changed();
});
_dispose = value is IDisposable d ? Disposable.Create(sub, d) : sub;
}
void _on_value_changed() {
if (_target_value != null && is_dirty && _last_result.value.Equals(_target_value)) {
_target_value = null;
_committed = _last_result;
_refresh_is_dirty.execute();
}
}
public uint id { get; }
public TVM value { get; }
public string name {
@@ -380,21 +391,26 @@ namespace mbts.spark {
/// assign directly to <see cref="_last_result"/>, to capture props that aren't visible in the dialog (i.e., props that are manipulated in the main window)
_last_result = data;
}
_on_value_changed();
}
public void load_value_from(T value) {
using (_refresh_is_dirty.defer_execution())
this.value.load_from(value);
_on_value_changed();
}
public void commit(SettingsProfile<T> data) {
using (_refresh_is_dirty.defer_execution())
_load_from(_committed = data);
_on_value_changed();
}
public void commit_current() {
_committed = _last_result;
_target_value = _committed.value;
_refresh_is_dirty.execute();
_on_value_changed();
}
public void revert() {
@@ -402,6 +418,7 @@ namespace mbts.spark {
_load_from(_committed);
_last_result = _committed;
}
_on_value_changed();
}
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
@@ -414,6 +431,7 @@ namespace mbts.spark {
public override string ToString() => _name;
void _load_from(SettingsProfile<T> data) {
_target_value = data.value;
name = data.name;
security_criteria = data.security_criteria;
value.load_from(data.value);
+17 -1
View File
@@ -197,6 +197,7 @@ namespace mbts.spark {
static class TickStore {
public static readonly RegionalTickFactors default_rtf = new RegionalTickFactors(new TickFactors(1000, 1), new TickFactors(1000, 1), double.MaxValue);
static readonly ConcurrentDictionary<SpecKey, RegionalTickFactors> _exchange_ticks = new ConcurrentDictionary<SpecKey, RegionalTickFactors>();
static readonly bool _som_ticks_fix_enabled = config.Configuration.get<bts.config.toggles.SOMTicksFix>().enabled;
public static RegionalTickFactors get(ContractSpecification spec) =>
_exchange_ticks.GetOrAdd(new SpecKey(spec), _create_rtf);
@@ -249,8 +250,23 @@ namespace mbts.spark {
var tf = _try_create_tf(tick_size, tick_base);
if (tf != null && display_digits_override > 0) {
int display_digit_diff = (int)display_digits_override - tf.display_digits();
if (display_digit_diff != 0)
if (_som_ticks_fix_enabled) {
// we can always add display_digits (i.e. dislpay_digit_diff > 0), of course the ticks can be expressed precisely with extra 0s.
// but if we are going to remove display_digits (i.e display_digit_diff < 0), we must make sure the tick_unit is large enough to be expressed precisely with that many fewer digits
// - "-display_digit_diff <= Math.Ceiling(Math.Log10(tf.tick_unit))" will tell us if the digits can be removed without compromising precision
// - unfortunately, we cannot rely on the exchange's ContractSpec for this, there is 1 known case where the data they give us is incorrect
// SOM spreads ContractSpec (from the exchange):
// LowTick: 0.0025 (aka 1/400, or 2/800)
// MainFraction: 8
// SubFraction: 0
// PriceDisplayFormat: 1
// - as you can see, this cannot possibly be formatted with 1 display digit, it ticks in increments of 1/400
// - in practice, to maintain the '8' base, we format this in increments of 2/800 (e.g. 1-000, 1-002, 1-004, and so on up to 1-778, 2-000)
if (display_digit_diff > 0 || (display_digit_diff < 0 && -display_digit_diff <= Math.Ceiling(Math.Log10(tf.tick_unit))))
tf = _inflate_to_base(tf, tf.tick_base * Math.Pow(10, display_digit_diff));
} else if (display_digit_diff != 0) {
tf = _inflate_to_base(tf, tf.tick_base * Math.Pow(10, display_digit_diff));
}
}
return tf;
}
+4
View File
@@ -48,6 +48,10 @@ message RejectStopAndStopLimitOrders {
optional bool enabled = 1 [default = true];
}
message SOMTicksFix {
optional bool enabled = 1 [default = true];
}
message EnsureOnScreenWithWorkAreaRect {
// Defaulting to false bc this is near the end of the cycle (04/27/2019),
// and the new ensure_onscreen code has only been tested with a single monitor arrangement.
@@ -3065,7 +3065,7 @@ TEST_CASE ("spreader_perf_2legs", "[spreader]")
}
TEST_CASE ("spreader_perf_4legs", "[spreader]")
{
bool recomp = gen_rand(0,10) > 5;
bool recomp = false;//gen_rand(0,10) > 5;
bts::orderman::OrderKey order_key(0);
Price spreader_price = Price (40);
Quantity spreader_quantity = Quantity (250);
-5
View File
@@ -150,11 +150,6 @@ In gdb:
```
(gdb) break order_plugin_example.cpp:366
```
#### In Depth Algo Descriptions
* [Spark Algo Descriptions](doc/algos.md)
* Send us an email for help: api_support@bluetradesys.com
#### Spark windows client
* Download spark windows executable here: <spark_client>
-46
View File
@@ -1,46 +0,0 @@
+++
title = "BTS Basic Algos"
weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## Lean
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
##### Parameters
* Cross Thresh - size thresh in absolute or ratio terms
* Max Chase Ticks - max levels to pay up, in ticks
## Join
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
##### Parameters
* Join Thresh - size thresh in absolute or ratio terms
## Top
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
##### Parameters
* Top Thresh - size thresh in absolute or ratio terms
## Cover
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
##### Parameters
* Profit Ticks - ticks above or below fill price to place profit / cover order
## OCO
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
Composing this order with Cover simulates scalping functionality
##### Parameters
* Stop Ticks - ticks below or above main order to place stop / other order
Regular → Executable
View File
File mode changed.
+5 -5
View File
@@ -1,14 +1,14 @@
qa_sprint_id=18
qa_sprint_id=20
testers=(
#'pedro.pinto'
# 'eric.harding'
'jake.norbut'
'michael.harding'
# 'jake.norbut'
# 'michael.harding'
'karl.ellefsen'
'pete.fisher'
'taha.afzal'
'john.colvin'
# 'taha.afzal'
# 'john.colvin'
'kevin.darby'
'brian.muhr'
)
+2 -3
View File
@@ -20,10 +20,9 @@ On Toward=No Change
5. Select all the lean orders and change the Trade Toward Behavior to Cancel (don't commit yet).
As the parameter is changed its name should turn Green along with a *. Revert back and it should go to
No Change. Select a few of the leans and make sure Trade Toward Behavior is No Change. Now Change it
to Cancel and commit. Make sure it is set to Cancel for all the lean orders.
to Cancel and commit. Make sure it is set to Cancel for all the lean orders. This should cancel all the lean orders.
6. Move the ask book to 20.040. This should cancel all the leans (verifies the change
in 5 made through). Move the market back to 20.050
6. Move the market back to 20.050
7. Enter 2 leans, 4 and 6 lots (configured like 3) at 20.025 and 20.020 respectively. In the orderbook
select both and make sure the Price and Size show as - (since they are different). Change the Trade Toward
Regular → Executable
View File
File mode changed.