Compare commits

..
Author SHA1 Message Date
Jake Norbut 26b5fe5699 updating release notes 2019-11-20 09:47:48 -06:00
Jake Norbut 2fccceb91d Merged in ladder_recenter_no_kb (pull request #379)
Ladder recenter no kb
2019-11-20 15:47:09 +00:00
Jake Norbut fa49a0de07 adding new test 2019-11-20 09:45:51 -06:00
Jake Norbut bd28a0fa54 delete old test 2019-11-20 09:45:27 -06:00
Jake Norbut 14ebe5ef51 (shift or alt) + double click to recenter all ladders 2019-11-20 09:14:20 -06:00
Jake Norbut a9b4049307 relocating bus.post of recenter all to common public ladder function 2019-11-20 09:13:58 -06:00
Jake Norbut 234d499731 adding ladder spacebar test 2019-11-19 08:51:38 -06:00
Eric Harding 324b9c1cc7 Merge branch 'prod' of bitbucket.org:bluetradesys/spark 2019-11-19 09:04:33 -05:00
Eric Harding 6aad329e09 Include ice symbol list in defaults for windows 2019-11-18 13:04:00 -05:00
Jake Norbut 0641cf45d9 Merged in SP-261_bye_bye_butterfly (pull request #366)
RTM - no more butterfly defaults

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-11-18 16:34:35 +00:00
Jake Norbut 1fe0efe5c3 Merged in SP-264_price_display_format (pull request #374)
RTM - SP-264 drop tick separator in ladder prices

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-11-18 16:17:31 +00:00
Jake Norbut 93938b6ef2 fix merge 2019-11-18 10:15:19 -06:00
Jake Norbut 818806a4e1 Merged in SP-263_recenter_on_spacebar (pull request #371)
RTM - SP-263 recenter on spacebar

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-11-18 15:35:59 +00:00
Jake Norbut 261bf3e475 Merged in SP-260_local_max_clip (pull request #370)
RTM SP-260 local max clip

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-11-18 15:34:30 +00:00
John Colvin dbe2194ed3 Merged in SP-265-pnl-cast-exception (pull request #376)
SP-265 pnl cast exception

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-11-18 15:26:06 +00:00
John Colvin 45e10ca377 SP-265 use 'is' instead of explicit cast to IPNLCellData (the cast should work; this is defensive) 2019-11-14 21:09:31 -06:00
John Colvin 4de77ce339 pnl sometimes duplicates grouping columns when they are changed in settings dialog 2019-11-14 16:12:58 -06:00
John Colvin 96f71290a4 SP-265 pnl crash due to CastException 2019-11-14 16:12:19 -06:00
Jake Norbut e2b80942df adding manual test 2019-11-14 15:58:59 -06:00
Jake Norbut 51fb068469 adding manual test 2019-11-14 15:51:40 -06:00
Jake Norbut 58d39c4833 rendering ladder prices approparitely based on price_display_format 2019-11-13 10:38:26 -06:00
Jake Norbut 367d9be724 adding price_display_format to settings view 2019-11-13 10:38:04 -06:00
Jake Norbut 99f2ab7c8a adding price_display_format to vm 2019-11-13 10:37:46 -06:00
Jake Norbut 8b091b39eb adding PriceDisplayFormat enum to proto 2019-11-13 10:37:24 -06:00
Jake Norbut a357d978ca misc string formatting fix; this is only used for debugging focus issues 2019-11-08 13:45:03 -06:00
Jake Norbut dfb673bd63 recenters all ladders via posting to appropriate tag to bus when spacebar is pressed over a ladder 2019-11-08 13:43:29 -06:00
Jake Norbut 24f366491b update view with appropriate controls for enforce_max_clip and max_clip 2019-11-08 10:44:03 -06:00
Jake Norbut 55134bf172 check max clip for crossing market dialog as well 2019-11-08 10:42:23 -06:00
Jake Norbut b6b33f14ef enforce max clip at order entry 2019-11-08 10:40:43 -06:00
Jake Norbut 12d532a17f adding enforce_max_clip and max_clip to settings vm 2019-11-08 10:39:38 -06:00
Jake Norbut 09679ff494 adding enforce_max_clip and max_clip to proto 2019-11-08 10:38:10 -06:00
Jake Norbut 4180b9a8ec removing price and quantity special case when legs = 3 2019-11-06 11:45:42 -06:00
22 changed files with 483 additions and 478 deletions

No files matched your search

+5
View File
@@ -6,9 +6,14 @@ insert_anchor_links = "right"
+++
## 2.14.0
### User Visible Changes
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
- Added a Max Order Quantity setting to the Safety Checks tab on the Ladder settings. This setting allows for a user-defined maximum order quantity.
- Added a Price Display Format setting to the Market Price and Size Display tab on the Ladder settings. This setting allows for price levels in the ladder to be displayed without a decimal separator.
- Synthetic instrument creation with 3 legs no longer defaults to butterfly quantites
### Bug Fixes
- Fixed an issue where market transition were not observed by IAlgoOrder
- Fixed a bug in error reporting
- Fixed a bug in the PnL window where specifying a certain grouping order would lead to a UI failure
## 2.13.0
### User Visible Changes
@@ -357,19 +357,13 @@ namespace mbts.spark {
if (leg_tuples.Count == 2) {
_is_setting_price_mults = true;
_price_mult_model.override_value(leg_tuples[1], -1);
} else if (leg_tuples.Count == 3) {
_is_setting_price_mults = true;
_price_mult_model.override_value(leg_tuples[1], -2);
}
}
}
void _deduce_quantities() {
if (leg_tuples.Count == 2) {
_is_setting_quantities = true;
_quantity_model.override_value(leg_tuples[1], -1);
} else if (leg_tuples.Count == 3) {
_is_setting_quantities = true;
_quantity_model.override_value(leg_tuples[1], -2);
}
}
@@ -249,6 +249,7 @@ namespace mbts.spark {
const double _default_order_entry_panel_width = 110.0;
const double _default_zoom_factor = 1.0;
const bool _default_show_warn_on_cross = true;
const bool _default_enforce_max_clip = false;
public static readonly IEqualityComparer<LadderSettings> profile_equality_comparer =
EqualityComparer.Create<LadderSettings>(profile_equals);
// this class is 100% mutable (there are literally 0 readonly members),
@@ -282,6 +283,7 @@ namespace mbts.spark {
use_defaults = true;
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
show_warn_on_cross = _default_show_warn_on_cross;
enforce_max_clip = _default_enforce_max_clip;
}
public static LadderSettings Default {
@@ -347,6 +349,8 @@ namespace mbts.spark {
zoom_factor = _default_zoom_factor,
net_change = NetChangeTerms.None,
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
max_clip = 0,
price_display_format = PriceDisplayFormat.Default,
};
rv.row_height = LadderRender.measure_row_height(rv);
return rv;
@@ -365,6 +369,13 @@ namespace mbts.spark {
Ticks
}
public enum PriceDisplayFormat {
[Description("Default")]
Default,
[Description("Integer")]
Integer
}
[ProtoMember(1)]
public double row_height { get; set; }
[ProtoMember(2)]
@@ -499,6 +510,15 @@ namespace mbts.spark {
[ProtoMember(57)]
public double left_panel_scroll_position { get; set; }
[ProtoMember(58), DefaultValue(_default_enforce_max_clip)]
public bool enforce_max_clip { get; set; }
[ProtoMember(59)]
public uint max_clip { get; set; }
[ProtoMember(60)]
public PriceDisplayFormat price_display_format { get; set; }
public void sanitize() =>
sanitize(this);
@@ -600,7 +620,10 @@ namespace mbts.spark {
a.favorite_algos == b.favorite_algos &&
a.net_change == b.net_change &&
a.show_warn_on_cross == b.show_warn_on_cross &&
a.mini_ob == b.mini_ob;
a.mini_ob == b.mini_ob &&
a.enforce_max_clip == b.enforce_max_clip &&
a.max_clip == b.max_clip &&
a.price_display_format == b.price_display_format;
public bool Equals(LadderSettings other) =>
profile_equals(this, other) &&
@@ -29,7 +29,7 @@ using TextUtils = mbts.ui.utility.text.TextUtils;
namespace mbts.spark {
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
readonly Property<LadderSettings.BadgeStyle> _badge_style;
readonly Property<uint> _alternate_order_size;
readonly Property<uint> _alternate_order_size, _max_clip;
readonly DelegateProperty<double> _row_height;
readonly UserProperty<AccountTuple?> _default_account;
readonly UserProperty<AlgoDefinitionVM> _default_algo;
@@ -41,8 +41,9 @@ namespace mbts.spark {
AlgoID? _desired_algo;
TimeInForce _default_time_in_force;
LadderSettings.NetChangeTerms _price_format;
LadderSettings.PriceDisplayFormat _price_display_format;
double _order_entry_panel_width, _zoom_factor;
bool _auto_clear, _show_warn_on_cross;
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip;
LadderSettings _current;
LadderColumnSettingsVM
_orders = new OrdersColumnSettingsVM(),
@@ -91,6 +92,7 @@ namespace mbts.spark {
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
_default_account = user_backing<AccountTuple?>(nameof(default_account));
_default_algo = user_backing<AlgoDefinitionVM>(nameof(default_algo));
_max_clip = backing<uint>(nameof(max_clip));
// be sure to initialize backings before this measure_row_height call
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
_orders.font_size_changed += on_font_size_changed;
@@ -166,7 +168,9 @@ namespace mbts.spark {
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
// price format
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
public LadderSettings.PriceDisplayFormat price_display_format { get => _price_display_format; set => Set(ref _price_display_format, value); }
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
public IEnumerable<LadderSettings.PriceDisplayFormat> price_display_format_options => EnumHelper.get_values<LadderSettings.PriceDisplayFormat>();
// time in force
public TimeInForce default_time_in_force { get => _default_time_in_force; set => Set(ref _default_time_in_force, value); }
public IEnumerable<TimeInForce> time_in_force_options => EnumHelper.get_values_except(TimeInForce.Undefined);
@@ -182,6 +186,8 @@ namespace mbts.spark {
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
// safety checks
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
public bool enforce_max_clip { get => _enforce_max_clip; set => Set(ref _enforce_max_clip, value); }
public uint max_clip { get => _max_clip.Value; set => _max_clip.Value = value; }
// favorites
public FavoriteAlgosVM favorite_algos_vm { get; }
@@ -354,8 +360,11 @@ namespace mbts.spark {
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
price_format = settings.net_change;
price_display_format = settings.price_display_format;
// safety checks
show_warn_on_cross = settings.show_warn_on_cross;
enforce_max_clip = settings.enforce_max_clip;
max_clip = settings.max_clip;
}
}
@@ -428,8 +437,11 @@ namespace mbts.spark {
clone.cancel_all_visible = _cancel_all_visibility.show;
clone.zoom_slider_visible = _zoom_slider_visbility.show;
clone.net_change = price_format;
clone.price_display_format = price_display_format;
// safety checks
clone.show_warn_on_cross = show_warn_on_cross;
clone.enforce_max_clip = enforce_max_clip;
clone.max_clip = max_clip;
return clone;
}
@@ -397,6 +397,8 @@ namespace mbts.spark {
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
public bool enforce_max_clip_from_settings => current_settings.enforce_max_clip;
public uint max_clip_from_settings => current_settings.max_clip;
internal LadderModel model => _model;
internal IObservable<LadderSettings> settings { get; }
@@ -447,6 +449,9 @@ namespace mbts.spark {
public void key_down(KeyEventArgs e) {
if (order_properties_vm == null)
return;
if (KeyboardExtensions.IsDown(Key.Space)) {
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
if (!KeyboardExtensions.IsCtrlDown()) return;
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
@@ -462,6 +467,10 @@ namespace mbts.spark {
settings_invalidated?.Invoke();
}
public void recenter_all_ladders() {
_bus?.post(SparkTags.RequestRecenterLadder, null);
}
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;
@@ -470,6 +479,10 @@ namespace mbts.spark {
size_calculator.flash_error();
} else {
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
// enforce max clip
if (enforce_max_clip_from_settings && size > max_clip_from_settings) {
size = max_clip_from_settings;
}
return send_order_async(args.side.Value, size, args.price);
}
}
@@ -350,6 +350,17 @@ namespace mbts.spark {
}
} else if (_vm != null) {
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
// does the size violate the local max clip
if (_vm.enforce_max_clip_from_settings && size > _vm.max_clip_from_settings) {
// note this size is only used for the cross market message, not the actual order command
size = _vm.max_clip_from_settings;
// only update the calculator value when it's a standard left-click order
if (e.ChangedButton == MouseButton.Left) {
_vm.size_calculator.value = _vm.max_clip_from_settings;
}
}
// if warning enabled && (buy above opposite inside market || ask below opposite inside market)
// when 1-sided market, _get_top_price will return NaN, and so the price comparison is false
// meaning show no warning == correct
@@ -479,8 +490,14 @@ namespace mbts.spark {
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
var column = _column_for_point(e.GetPosition(this));
if (column.column_type == ColumnType.Price)
_scroll_to_center();
if (column.column_type == ColumnType.Price) {
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
_vm?.recenter_all_ladders();
} else {
_scroll_to_center();
}
}
}
#region Row Utility Methods
@@ -588,7 +605,8 @@ namespace mbts.spark {
_settings.row_height,
_price_list,
column.settings,
security.ContractSpec);
security.ContractSpec,
_settings.price_display_format);
}
break;
case ColumnType.Ask: render_market_col(_asks); break;
@@ -911,7 +929,7 @@ namespace mbts.spark {
var prev_price_list = _price_list;
_price_list = (!(_settings.net_change == LadderSettings.NetChangeTerms.None)) ?
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec) :
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec);
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec, _settings.price_display_format);
_ordershost.update_settings(_price_list, _price_comparer, _settings.row_height, _settings.orders_column);
if (prev_price_list != null) {
// maintain previous visible price range if possible
@@ -40,6 +40,8 @@ namespace mbts.spark {
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
// (see how it is incorporated in measure_row_height() below)
public static readonly Thickness row_padding = new Thickness(6);
const string tick_separator_period = ".";
const string tick_separator_dash = "-";
// IMPORTANT: this method ONLY WORKS if Badges use the BadgeRender.margin/border_thickness/padding values.
// TODO: elminate the dependency on BadgeRender.margin/border_thickness/padding by integrating them into settings.
@@ -52,8 +54,11 @@ namespace mbts.spark {
public static int index_of(double[] prices, double price, IComparer<double> comparer) =>
Array.BinarySearch(prices, price, comparer);
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) =>
glyph_context.get_glyph_run(text, settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) =>
glyph_context.get_glyph_run(
// long term there is probably a better way to get rid of the separator, such as in the TickExtensions._format_tick
price_display_format == LadderSettings.PriceDisplayFormat.Integer ? text.Replace(tick_separator_period, string.Empty).Replace(tick_separator_dash, string.Empty) : text,
settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
public static double[] gen_prices(double mid, int row_count, ContractSpecificationImmutable spec) {
// require odd number
@@ -79,10 +84,10 @@ namespace mbts.spark {
result[i] = format_text(spec.format(prices[i], reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
return result;
}
public static GlyphRun[] format_prices(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
public static GlyphRun[] format_prices(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
var result = new GlyphRun[prices.Length];
Parallel.For(0, prices.Length, i =>
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context));
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
return result;
}
@@ -104,9 +109,9 @@ namespace mbts.spark {
}
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, int row_count, double row_height,
LadderColumnSettings settings, ContractSpecificationImmutable spec) {
LadderColumnSettings settings, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
var dprices = gen_prices(mid, row_count, spec);
render_prices(price, glyph_context, row_count, row_height, dprices, settings, spec);
render_prices(price, glyph_context, row_count, row_height, dprices, settings, spec, price_display_format);
return dprices;
}
@@ -122,10 +127,10 @@ namespace mbts.spark {
}
}
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, double[] dprices, LadderColumnSettings settings, ContractSpecificationImmutable spec) {
public static void render_prices(DrawingVisual price, GlyphContext glyph_context, int row_count, double row_height, double[] dprices, LadderColumnSettings settings, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
var foreground = settings.foreground.rgb_brush();
//var background = new SolidColorBrush(settings.background.rgb_color());
var fprices = format_prices(dprices, row_height, settings, glyph_context, spec);
var fprices = format_prices(dprices, row_height, settings, glyph_context, spec, price_display_format);
using (var dc = price.RenderOpen())
using (_draw_with_clip(dc, settings, row_height, row_count)) {
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
@@ -178,6 +178,14 @@
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="EnableDisableFeatureText" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{StaticResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
<u:KeyedTemplateSelector x:Key="LadderColumnTemplateSelector">
<u:KeyedTemplateSelector.Default>
<DataTemplate>
@@ -630,7 +638,11 @@
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<GroupBox Header="Size Display" DataContext="{Binding size_display}" Padding="4" Grid.Row="0" Grid.RowSpan="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -710,6 +722,7 @@
</GroupBox>
<GroupBox
Grid.Column="2"
Grid.Row="0"
VerticalAlignment="Top"
Header="Price Display"
Visibility="{Binding is_synthetic, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}"
@@ -717,6 +730,15 @@
>
<c:RadioButtonsControl ItemsSource="{Binding price_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_format}"/>
</GroupBox>
<GroupBox
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Stretch"
Header="Price Display Format"
Padding="4"
>
<c:RadioButtonsControl ItemsSource="{Binding price_display_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_display_format}"/>
</GroupBox>
</Grid>
</TabItem>
<!-- Size Presets Tab-->
@@ -991,21 +1013,45 @@
<Grid HorizontalAlignment="Left" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox Content="Warn on Market Cross" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
<!-- market cross -->
<CheckBox Name="warn" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</CheckBox.Resources>
</CheckBox>
<TextBlock Grid.Column="1"
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding ElementName=warn, Path=IsChecked}"
Text="Warn on Market Cross" />
<!-- max order qty -->
<CheckBox Name="enforce_max_clip" Grid.Row="1" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding enforce_max_clip}">
<CheckBox.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="FlowDirection" Value="LeftToRight"/>
</Style>
</CheckBox.Resources>
</CheckBox>
<TextBlock Grid.Row="1" Grid.Column="1"
Style="{StaticResource EnableDisableFeatureText}"
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
MinWidth="{Binding ElementName=warn, Path=ActualWidth}"
Text="Max Order Quantity"/>
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2"
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
Value="{Binding max_clip}" />
</Grid>
</TabItem>
<TabItem Header="Mini Order Book">
@@ -1,220 +1,223 @@
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
var new_cols = new Lazy<List<GridColumnSettings>>(() => s.grid.columns.ToList());
for (int i = 0; i < s.grid.columns.Count; i++) {
var key = s.grid.columns[i].strong_id<ColumnKeys>();
if (_anchor_cache.TryGetValue(key, out var a) && a.anchor_index.HasValue) {
// anchor index is 1 based, subtract 1 to get 0 based
int anchor_index = a.anchor_index.Value - 1;
if (anchor_index != i && anchor_index >= 0 && anchor_index < s.grid.columns.Count) {
new_cols.Value.RemoveAt(i);
new_cols.Value.Insert(anchor_index, s.grid.columns[i]);
}
using bts.collections;
using bts.utils;
using mbts;
using mbts.collections;
using mbts.expression;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Dynamic;
using System.Linq;
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
namespace bts.shell.pnl {
class PNLSettingsVM : DisposableViewModelBase, IWindowSettingsVM<PNLSettings>, IValueFactory<PNLSettings>, IObservable<PNLSettings>, ITemplateSelectorKey {
readonly IReadOnlyDictionary<ColumnKeys, AnchorItemVM> _anchor_cache;
readonly DeferrableActionWrapper _sync_anchors;
PNLCalculationType _calculation_type;
PNLHeaderRowDisplay _header_row_display;
PNLSettings _last_loaded;
public PNLSettingsVM(PNLSettings settings) {
// init anchor cache, deferrable resync
_anchor_cache = PNLSettings.anchorable_columns.ToDictionary(k => k, k => new AnchorItemVM(this, k));
_sync_anchors = new DeferrableActionWrapper(() => {
for (int i = 0; i < anchor_items.Count; i++) {
anchor_items[i].anchor_index = i + 1;
anchor_items[i].refresh();
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols.IsValueCreated ? s.grid.with(columns: new_cols.Value) : null,
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
});
core_settings_vm = new SimpleGridSettingsVM(column_factory: col_settings => {
var col_vm = new PNLColumnVM(core_settings_vm, EnumHelper.parse<ColumnKeys>(col_settings.id));
col_vm.load_from(col_settings);
return col_vm;
});
core_settings_vm.selection_highlight.bind_opacity_to_alpha = true;
_dispose = Disposable.Create(
anchor_items.Subscribe((s, e) => _sync_anchors.execute()));
load(settings);
}
public IEnumerable<AnchorItemVM> anchor_item_headers => _anchor_cache.Values;
public ObservableCollection<AnchorItemVM> anchor_items { get; } = new ObservableCollection<AnchorItemVM>();
public SimpleGridSettingsVM core_settings_vm { get; }
public PNLGroupRowAppearanceVM group_row_defaults { get; } = new PNLGroupRowAppearanceVM();
public PNLCellAppearanceVM header_row_defaults { get; } = new PNLCellAppearanceVM();
public PNLCalculationType calculation_type { get => _calculation_type; set => Set(ref _calculation_type, value); }
public IEnumerable<PNLCalculationType> calculation_types => EnumHelper.get_values_except(PNLCalculationType.None, PNLCalculationType.Same, PNLCalculationType.Opposite);
public PNLHeaderRowDisplay header_row_display { get => _header_row_display; set => Set(ref _header_row_display, value); }
public IEnumerable<PNLHeaderRowDisplay> header_row_display_options => PNLSettings.header_row_display_options;
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.PNL;
public PNLSettings get_result() {
var s = _last_loaded == null ? new PNLSettings() : SparkUtils.clone_proto(_last_loaded);
s.calculation_type = _calculation_type;
s.grid = core_settings_vm.create();
// move anchor cols to the left, order wrt grouping hierarchy
List<GridColumnSettings> new_cols = null;
IEnumerable<GridColumnSettings> cols = s.grid.columns;
var sorted_anchors = anchor_items.OrderBy(a => a.anchor_index).ToList();
for (int i = 0; i < sorted_anchors.Count; i++) {
if (GridSettingsHelper.try_find_column(cols, sorted_anchors[i].key, out var match, out int pos) && pos != i) {
if (new_cols == null) {
new_cols = s.grid.columns.ToList();
// set cols to new_cols so subsequent position lookups
// take previous column order manipulations into account.
cols = new_cols;
}
new_cols.RemoveAt(pos);
new_cols.Insert(i, match);
}
}
// rule-based animations
var trigger_anims = core_settings_vm.columns
.OfType<PNLColumnVM>()
.SelectMany(c => c.get_triggers())
.GroupBy(tup => tup.expression)
.Where(g => CoreExpressionEvaluator<PNLRowData>.validate(g.Key))
.Select(g => (
expression: g.Key,
animations: g.ToArray()))
.ToList();
var normalized_anims = trigger_anims
.Where(tup => CoreExpressionEvaluator<PNLNormalizedData>.validate(tup.expression))
.ToList();
s = s.with(anchors: anchor_items.Select(vm => vm.key),
grid: new_cols == null ? null : s.grid.with(columns: new_cols),
group_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.group_column_vm.apply_to(col_settings);
return col_settings;
}),
header_columns: core_settings_vm.columns.OfType<PNLColumnVM>().Select(c => {
var col_settings = c.create();
col_settings = c.header_column_vm.apply_to(col_settings);
return col_settings;
}),
animations: trigger_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.row)
}),
group_header_animations: normalized_anims
.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.group)
}),
header_row_animations: normalized_anims.Select(
ta => new PNLAnimationTriggerData {
expression = ta.expression,
animations = Array.ConvertAll(ta.animations, t => t.header)
}));
s.header_row_display = header_row_display;
s.header_cell_appearance = PNLCellAppearanceProto.from_appearance(header_row_defaults.to_appearance());
// group
var (group_cell_appearance, group_row_background_colors) = group_row_defaults.create();
s.group_cell_appearance = PNLCellAppearanceProto.from_appearance(group_cell_appearance);
s.group_background_colors.SetItems(group_row_background_colors.Select(c => c.rgb_color()));
//
return s;
}
public void load(PNLSettings value) {
_last_loaded = value;
header_row_defaults.load(value.header_cell_appearance.to_appearance());
group_row_defaults.load_from((value.group_cell_appearance.to_appearance(), value.group_background_colors.WrapSelectorRO(c => c.argb_color())));
var column_map = _map_column_data(value.grid.columns, value.animations);
var group_column_map = _map_column_data(value.group_columns, value.group_header_animations);
var header_column_map = _map_column_data(value.header_columns, value.header_row_animations);
core_settings_vm.load_from(value.grid);
for (int i = 0; i < core_settings_vm.columns.Count; i++) {
var col_vm = core_settings_vm.columns[i] as PNLColumnVM ?? throw new InvalidOperationException($"In {nameof(PNLSettingsVM)}, all columns should be of type {typeof(PNLColumnVM)}.");
var key = EnumHelper.parse<ColumnKeys>(col_vm.id);
var (col_settings, trigger_animations) = column_map[key];
var group_data = group_column_map.GetNullable(key);
var header_data = header_column_map.GetNullable(key);
col_vm.load_from(col_settings, trigger_animations, group_data?.settings, group_data?.triggers, header_data?.triggers, header_data?.settings);
}
using (_sync_anchors.defer_execution())
anchor_items.WrapSelector(a => a.key, k => _anchor_cache[k]).sync_with(value.anchors.Intersect(PNLSettings.anchorable_columns));
header_row_display = value.header_row_display;
calculation_type = value.calculation_type;
}
PNLSettings IValueFactory<PNLSettings>.create() => get_result();
void IValueFactory<PNLSettings>.load_from(PNLSettings value) => load(value);
IDisposable IObservable<PNLSettings>.Subscribe(IObserver<PNLSettings> observer) => throw new NotImplementedException();
static Dictionary<ColumnKeys, (GridColumnSettings settings, Dictionary<string, PNLAnimation> triggers)> _map_column_data(IEnumerable<GridColumnSettings> columns, IEnumerable<PNLAnimationTriggerData> triggers) {
var map = columns.ToDictionary(
c => c.strong_id<ColumnKeys>(),
c => (settings: c, triggers: new Dictionary<string, PNLAnimation>()));
foreach (var trig in triggers) {
foreach (var anim in trig.animations) {
if (map.TryGetValue(anim.column, out var col_data))
col_data.triggers[trig.expression] = anim;
}
}
return map;
}
void _append_or_remove_anchor(ColumnKeys key, bool include) {
if (!_anchor_cache.TryGetValue(key, out var vm))
throw new KeyNotFoundException($"Column key '{key}' is not anchorable (expected one of: {string.Join(", ", PNLSettings.anchorable_columns)}).");
if (vm.include == include)
return;
if (include) {
if (anchor_items.Any(a => a.key == key))
throw new InvalidOperationException($"{nameof(anchor_items)} already contains an element with key '{key}.'");
anchor_items.Add(vm);
vm.anchor_index = anchor_items.Count;
} else {
if (!anchor_items.Remove(vm))
throw new InvalidOperationException($"{nameof(anchor_items)} does not contain an element with key '{key}.'");
vm.anchor_index = null;
}
_sync_anchors.execute();
}
public class AnchorItemVM : ViewModelBase, IEquatable<AnchorItemVM> {
readonly PNLSettingsVM _owner;
int? _anchor_index;
public AnchorItemVM(PNLSettingsVM owner, ColumnKeys key) {
_owner = owner;
this.key = key;
}
public ColumnKeys key { get; }
public bool include { get => anchor_index.HasValue; set => _owner._append_or_remove_anchor(key, value); }
public int? anchor_index { get => _anchor_index; set => Set(ref _anchor_index, value); }
public void refresh() =>
RaisePropertyChanged(nameof(include), nameof(anchor_index));
public bool Equals(AnchorItemVM other) =>
other != null && key == other.key;
public override bool Equals(object obj) =>
Equals(obj as AnchorItemVM);
public override int GetHashCode() =>
key.GetHashCode();
public override string ToString() =>
EnumHelper.get_display_name(key);
}
}
}
@@ -90,12 +90,11 @@ namespace bts.shell.pnl {
}
int? _try_compare_groups(CollectionViewGroup x, CollectionViewGroup y) {
if (x == null || y == null)
return null;
var xcell = (IPNLCellData)x.Name;
var ycell = (IPNLCellData)y.Name;
var comp = _get_column_comparer(xcell.column);
return comp.value_comparer.Compare(xcell.value, ycell.value);
if (x?.Name is IPNLCellData xcell && y?.Name is IPNLCellData ycell) {
var comp = _get_column_comparer(xcell.column);
return comp.value_comparer.Compare(xcell.value, ycell.value);
}
return null;
}
}
}
}
@@ -698,8 +698,8 @@
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_filter.active, Source={u:DirectReference _grid}}"
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_filter.open, Source={u:DirectReference _grid}}"
key="Security"
sort_member_path="security_cell.cell_data"
data_member_path="security"
data_member_path="security_cell.cell_data"
sort_member_path="security"
HeaderTemplate="{StaticResource AnchorableColumnHeaderTemplate}"
/>
<!--Security Key-->
+2 -2
View File
@@ -102,14 +102,14 @@ namespace mbts.utils
string identifier = ((felem.Name != null) && (felem.Name.Length > 0)) ?
felem.Name :
felem.GetType().ToString();
return "$FrameworkElement [{identifier}]";
return $"FrameworkElement [{identifier}]";
} else {
// Maybe a FrameworkContentElement has focus
if (elem is FrameworkContentElement fcelem) {
string identifier = ((fcelem.Name != null) && (fcelem.Name.Length > 0)) ?
fcelem.Name :
fcelem.GetType().ToString();
return "$FrameworkContentElement [{identifier}]";
return $"FrameworkContentElement [{identifier}]";
}
return $"Element of type [{elem.GetType().ToString()}] has focus";
}
@@ -4,8 +4,6 @@ using System.Runtime.InteropServices;
using System.Security;
using Google.Protobuf;
using security_key = System.UInt32;
using bts.co2;
using System.Linq;
namespace mbts.co2
{
@@ -87,7 +85,6 @@ namespace mbts.co2
[DllImport("libspark")]
internal unsafe static extern void post_on_scan_security_thread(Action callback);
}
internal static class Co2NativeTestMethods
@@ -136,59 +133,5 @@ namespace mbts.co2
[DllImport("libspark")]
internal static extern void co2_test_get_constants(out ulong max_depth, out ulong max_implied_depth, out sbyte nan_exponent);
internal unsafe delegate void raw_receive_pb_or_string(byte* buffer, int buffer_size);
[DllImport("libspark")]
private static unsafe extern void raw_co2_compute_synthetic_book(
byte[] security_buffer,
int security_length,
IntPtr[] leg_books,
int[] leg_lengths,
int leg_books_length,
raw_receive_pb_or_string callback);
internal static unsafe BookMessageImmutable co2_compute_synthetic_book(SecurityImmutable synthetic, params BookMessageImmutable[] books) {
BookMessage result = null;
string error = null;
var security_buffer = synthetic.ToByteArray();
// var h = GCHandle.Alloc(null, GCHandleType.Pinned).
var book_buffers =
books
.Select(b => b.ToByteArray())
.ToArray();
GCHandle[] book_buffer_handles =
book_buffers
.Select(b => GCHandle.Alloc(b,GCHandleType.Pinned))
.ToArray();
IntPtr[] book_buffer_pointers =
book_buffer_handles
.Select(h => h.AddrOfPinnedObject())
.ToArray();
var lengths =
book_buffers
.Select(b => b.Length)
.ToArray();
raw_co2_compute_synthetic_book(
security_buffer,
security_buffer.Length,
book_buffer_pointers,
lengths,
book_buffers.Length,
(byte* buffer, int length) => {
if (length < 0) {
error = new string((char*)buffer,0,-1 * length);
}
else {
result = Co2NativeMethods.deserialize<BookMessage>(buffer, (ulong)length);
}
});
if(error != null) {
// C# style multiple return values
throw new Exception(error);
}
return new BookMessageImmutable(result);
}
}
}
-11
View File
@@ -52,17 +52,6 @@ namespace mbts.co2 {
return _mantissa.GetHashCode();
}
public static IntPrice from_double(double price, sbyte precision) {
if (double.IsNaN(price)) return NaN;
if (Math.Abs(price%1) != 0) {
double rounding = 0.5 * Math.Pow(10,-precision);
price += (price > 0) ? rounding : -rounding;
price *= Math.Pow(10,precision);
return new IntPrice((long)price, (sbyte)-precision);
}
return new IntPrice((long)price, 0);
}
public static IntPrice NaN = new IntPrice(0,127);
}
-40
View File
@@ -286,46 +286,6 @@ namespace mbts.co2.tests
assert_about_equal(dt_utc_now.Second, DateTime.UtcNow.Second);
}
[Test,Ignore("Sample code")]
public void merge_synthetic_book() {
Security security = new Security();
PriceSizeMessage mkPriceSize(double price, UInt32 size) {
var iprice = IntPrice.from_double(price, 11);
return new PriceSizeMessage() {
Mantissa = iprice.mantissa,
Exponent = iprice.exponent,
Size = size
};
}
IEnumerable<PriceSizeMessage> mkPrices(params (double,uint)[] args) {
return args.Select((ps) => mkPriceSize(ps.Item1,ps.Item2));
}
var leg1 = new BookMessage() {
Bids = {
mkPriceSize(2000, 10),
mkPriceSize(1999.75, 5),
mkPriceSize(1999.5, 7)
},
Asks = {
mkPriceSize(2000.25,15),
mkPriceSize(2000.5, 5),
mkPriceSize(2001,10)
}
}.immutable();
var leg2 = new BookMessage() {
Bids = {
mkPrices( (2003, 5), (2002.75, 15), (2002.5, 12))
},
Asks = {
mkPrices( (2003.25, 12), (2003.5, 3), (2003.75,5))
}
}.immutable();
var result = Co2NativeTestMethods.co2_compute_synthetic_book(security.immutable(), leg1, leg2);
Assert.IsTrue(result.Bids.First().Size == 5);
Assert.IsTrue(result.Asks.First().Size == 2);
}
}
}
+8
View File
@@ -96,6 +96,10 @@ Symbols {
future: @SymbolLists.list[GLBX].symbol
spread: @SymbolLists.list[GLBX].symbol
}
simice {
future: @SymbolLists.list[ICE].symbol
spread: @SymbolLists.list[ICE].symbol
}
}
}
@@ -106,6 +110,10 @@ Symbols {
future: @SymbolLists.list[GLBX].symbol
spread: @SymbolLists.list[GLBX].symbol
}
ice {
future: @SymbolLists.list[ICE].symbol
spread: @SymbolLists.list[ICE].symbol
}
}
}
+1 -69
View File
@@ -4,7 +4,6 @@
#include "lib/config/config.h"
#include "lib/md/protos/co2.pb.h"
#include "lib/utils/TimeStamp.h"
#include "lib/md/test/md_test_utils.h"
#include <tuple>
#include <vector>
@@ -16,7 +15,7 @@ using namespace co2;
std::vector<MDProducerHandle> create_test_securities(MDProducer& producer, const char* postfix = "M");
// void assign_ids(std::vector<MDProducerHandle>::iterator begin, std::vector<MDProducerHandle>::iterator end, security_key start_key = 1000);
void assign_ids(std::vector<MDProducerHandle>::iterator begin, std::vector<MDProducerHandle>::iterator end, security_key start_key = 1000);
extern "C" BTS_EXPORT void co2_get_1234_handle(size_t& size, MDConsumerHandle& handle, uintptr_t* hotaddr, uintptr_t* coldaddr, uintptr_t* headeraddr) {
// Test that marshaling is working
@@ -170,72 +169,5 @@ extern "C" BTS_EXPORT void co2_get_sample_datetime(datetime::LLDateTime& now, da
utc_now = datetime::ll_now();
}
// Book compute_synthetic_book(const api::Security& security, const std::vector<Book>& leg_books);
typedef void(*raw_receive_pb_or_string)(const char*, int);
extern "C" BTS_EXPORT void raw_co2_compute_synthetic_book(
const char* security_buffer,
int security_len,
const char** book_buffers,
const int* book_lengths,
int book_buffers_length,
raw_receive_pb_or_string cb) {
// Deserialize Security
api::Security security;
if (!security.ParseFromArray(security_buffer, security_len)) {
const char* err = "Security parse error";
cb(err, -1 * strlen(err));
return;
}
// Deserialize BookMessage and convert to Book
std::vector<Book> books;
for (int x = 0; x < book_buffers_length; ++x) {
BookMessage bm;
if (!bm.ParseFromArray(book_buffers[x], book_lengths[x])) {
auto err = fmt::format("Book parse error: {}", x);
cb(err.c_str(), -1 * err.size());
return;
}
Book book;
for (const auto& bid : bm.bids()) {
IntPrice ip { bid.mantissa(), (int8_t)bid.exponent() };
book.bids.push_back({ ip.price(), bid.size() });
}
for (const auto& ask : bm.asks()) {
IntPrice ip { ask.mantissa(), (int8_t)ask.exponent() };
book.asks.push_back({ ip.price(), ask.size() });
}
books.push_back(book);
}
// Make the call we wanted to make in the first place
Book bres = compute_synthetic_book(security, books);
// Copy back to a BookMessage
BookMessage bmres;
for (const auto & bid : bres.bids) {
PriceSizeMessage& ps = *bmres.add_bids();
auto ip = IntPrice_from_double<11>(bid.price);
ps.set_mantissa(ip.mantissa());
ps.set_exponent(ip.exponent());
ps.set_size(bid.size);
}
for (const auto & ask : bres.asks) {
PriceSizeMessage& ps = *bmres.add_asks();
auto ip = IntPrice_from_double<11>(ask.price);
ps.set_mantissa(ip.mantissa());
ps.set_exponent(ip.exponent());
ps.set_size(ask.size);
}
// serialize to string
auto str = bmres.SerializeAsString();
cb(str.c_str(), str.size());
}
}}}//namespace
#endif
-48
View File
@@ -1,8 +1,5 @@
#include "md_test_utils.h"
#include "../MDProducer.h"
#include "lib/utils/sane_algorithm.h"
#include "../MDConsumerHandle.h"
#include "Co2TestInstrument.h"
#include <vector>
#include <string>
@@ -41,49 +38,4 @@ void assign_ids(std::vector<MDProducerHandle>::iterator begin, std::vector<MDPro
}
}
Book compute_synthetic_book(const api::Security& security, const std::vector<Book>& leg_books) {
auto synthetic = std::make_shared<SyntheticStrategy>();
synthetic->security.CopyFrom(security);
std::vector<Co2TestInstrument> leg_instruments;
for (const auto& leg : synthetic->security.synthetic().legs()) {
const auto& security = leg.security();
Co2TestInstrument instr(security.symbol(), securitytype_of(security));
convert_security(security, instr.cold().security, true);
leg_instruments.push_back(instr);
synthetic->legs.push_back(instr.consumer());
}
// Write leg books
for (size_t x = 0; x < leg_books.size(); ++x) {
std::array<PriceSize,COCONUT_MAX_DEPTH> bids = {}, asks = {};
std::transform(
leg_books[x].bids.begin(),
leg_books[x].bids.end(),
bids.begin(),
[](const PS& ps) {
return PriceSize_from_double<7>(ps.price, ps.size);
});
std::transform(
leg_books[x].asks.begin(),
leg_books[x].asks.end(),
asks.begin(),
[](const PS& ps) {
return PriceSize_from_double<7>(ps.price, ps.size);
});
auto ts = TimeStamp::now();
leg_instruments[x].producer().update_direct(ts, bids, asks);
}
// Read synthetic book back
MDConsumerHandle synthetic_handle { synthetic };
std::array<PriceSize,COCONUT_MAX_DEPTH> raw_bids, raw_asks;
TimeStamp t;
synthetic_handle.get_direct_book(t, COCONUT_MAX_DEPTH, raw_bids.data(), raw_asks.data());
Book result {};
sane::map(raw_bids, result.bids, [](PriceSize ps) -> PS { return {ps.price(), ps.size()}; });
sane::map(raw_asks, result.asks, [](PriceSize ps) -> PS { return {ps.price(), ps.size()}; });
return result;
}
}}}//namespace
@@ -142,6 +142,50 @@ void set_leg_params(SyntheticStrategy& strat, uint32_t security_key, const spark
}
Book compute_synthetic_book(const api::Security& security, const std::vector<Book>& leg_books) {
auto synthetic = std::make_shared<SyntheticStrategy>();
synthetic->security.CopyFrom(security);
vector<Co2TestInstrument> leg_instruments;
for (const auto& leg : synthetic->security.synthetic().legs()) {
const auto& security = leg.security();
Co2TestInstrument instr(security.symbol(), securitytype_of(security));
convert_security(security, instr.cold().security, true);
leg_instruments.push_back(instr);
synthetic->legs.push_back(instr.consumer());
}
// Write leg books
for (size_t x = 0; x < leg_books.size(); ++x) {
std::array<PriceSize,COCONUT_MAX_DEPTH> bids = {}, asks = {};
std::transform(
leg_books[x].bids.begin(),
leg_books[x].bids.end(),
bids.begin(),
[](const PS& ps) {
return PriceSize_from_double<7>(ps.price, ps.size);
});
std::transform(
leg_books[x].asks.begin(),
leg_books[x].asks.end(),
asks.begin(),
[](const PS& ps) {
return PriceSize_from_double<7>(ps.price, ps.size);
});
auto ts = TimeStamp::now();
leg_instruments[x].producer().update_direct(ts, bids, asks);
}
// Read synthetic book back
MDConsumerHandle synthetic_handle { synthetic };
std::array<PriceSize,COCONUT_MAX_DEPTH> raw_bids, raw_asks;
TimeStamp t;
synthetic_handle.get_direct_book(t, COCONUT_MAX_DEPTH, raw_bids.data(), raw_asks.data());
Book result {};
sane::map(raw_bids, result.bids, [](PriceSize ps) -> PS { return {ps.price(), ps.size()}; });
sane::map(raw_asks, result.asks, [](PriceSize ps) -> PS { return {ps.price(), ps.size()}; });
return result;
}
///////////////////////
// Test Cases
@@ -0,0 +1,41 @@
1. Verify you can place orders
Launch a Ladder
Place a 10 lot order away from the market and make sure the order is not rejected
Cancel the order
2. Verify the Max Order Quantity limit works
Click on the Settings button
Click on the Safety Checks tab
Click on the checkbox next to Max Order Quantity and specify 5
You should additionally observe that the text has gone from a disabled appearance to active
The ticker box is also now active
Hit apply
Back in the ladder, attempt to place another 10 lot order away from the market. You should observe the following:
You are now working a 5 lot order
The qty in the qty box additionally reads 5
Cancel the 5 lot order
3. Verify the Max Order Quantity when disabled allows you to place a 10 lot order
This is essentially the same as step 1, except now we're explicitly unchecking the checkbox, ergo...
Uncheck the Max Order Quantity chechbox and observe that:
The Max order quantity text is disabled
The ticker box is disabled
Click Apply
In the ladder, click the 5 button so that your qty box reads 10, which is a quantity greater than the disabled 5
A 10 lot order should now be working
Cancel the 10 lot order.
4. Right click / Alternate order qty respects the limit
Re-enable the Max Order Quantity (5 is still fine)
On the Order Quantities tab, specify 7 as your 'Single Click' Quantity
In the ladder, Right click to work an order attempting to use your single click quantity
You should be working a a 5 lot order now i.e. your max order quantity
Observe that the qty box did NOT update with your alternate order quantity
5. Cross market alert communicates respects the limit
Back on the Safety Checks tab, make sure Warn on Market Cross is checked
Attempt to place a 10 lot bid order (higher than your max of 5) on the sell side of the market
You should observe the message respects your max order quantity and warns you about buying 5
Feel free to click whichever button you desire on the warning dialog
FIN
@@ -0,0 +1,12 @@
Launch an ES Ladder (i.e. an instrument whose separator is the .)
Observe the ladder prices are in fact separated by a decimal point
Launch a ZT ladder (i.e. an instrument whose separator is the -)
Observe the ladder prices are in fact separated by a dash
Perform the following in each:
Click Settings
Go to Market Price and Size Display tab
Click on Integer under Price Display Format
Click Apply
Observe that the separator disappears when you do this
Feel free to go back and forth several times
@@ -0,0 +1,6 @@
1. Verify pressing the shift or alt key while double clicking in the Price column in a Ladder Window recenters ALL ladders
Launch 2 ladders
Scroll both ladders so they are no longer centered at mid market
Press the shift + double click while over the Price column
You should observe both scrolled ladders are now centered once again
The countdown timer is also reset via this spacebar action. This is more informational; I don't expect you to wait to verify that the timer is reset.