Compare commits

..
10 Commits
544 changed files with 11308 additions and 23542 deletions

No files matched your search

+1 -1
View File
@@ -215,7 +215,7 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": ["test", "[mbo]"],
"args": ["test", "[md]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/bts_spark_sdk/Client/Overview.md;content/bts_spark_sdk/Client/Threading.md;content/bts_spark_sdk/Client/_index.md;content/bts_spark_sdk/Server/_index.md;content/bts_spark_sdk/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+1
View File
@@ -7,6 +7,7 @@ zola_path=$(readlink -f "../../externals/linux/bin/zola/zola")
mkdir -p $temp_path
cp -r ./* $temp_path
cp -r ../../src/sdk/doc/* $temp_path/content/bts_spark_sdk/Server/
cd $temp_path
$zola_path build -u '/docs/' -o $1 >$tmpfile 2>&1
@@ -1,46 +0,0 @@
+++
title = "BTS Basic Algos"
weight = 10
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
@@ -0,0 +1,6 @@
+++
title = "Client"
weight = 1
sort_by = "weight"
redirect_to = "bts_spark_sdk/Client/overview/"
+++
@@ -0,0 +1,6 @@
+++
title = "Server"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
+++
+6
View File
@@ -0,0 +1,6 @@
+++
title = "BTS Spark SDK"
weight = 2
sort_by = "weight"
insert_anchor_links = "right"
+++
-17
View File
@@ -4,23 +4,6 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 1.6.0
### User Visible Changes
#### New Features
- You can now view your order's position in queue in the ladder
#### Bug Fixes
- Fix a bug in the order popup in the ladder which could lead to a crash.
## 1.5.9
### User Visible Changes
- Fix for stunnel connections on Windows 7
## 1.5.8
### User Visible Changes
- Fixed a bug where it was possible to crash the UI by entering an order through the order ticket if Enter was used to dismiss the dialog.
- Fixed a spreader reporting bug which could cause the server to crash if an algo was used to control certain types of synthetics
- Improved user message when the UI fails to login
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
## 1.5.7
### User Visible Changes
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
+4 -64
View File
@@ -1,6 +1,4 @@
using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -74,71 +72,13 @@ namespace mbts.ui.shell
base.OnExit(e);
Environment.Exit(0);
}
void start_stunnel(string path, uint job_error)
{
string config_error;
if (!mbts.config.Configuration.try_initialize(out config_error))
{
_stunnel_process = Process.Start(path + "bin\\stunnel.exe");
if (_stunnel_process != null)
{
for (int i = 0; i < 10; i++)
{
if (mbts.config.Configuration.try_initialize(out config_error))
break;
System.Threading.Thread.Sleep(500);
}
}
else
stunnel_job_error = "Stunnel Job Error code: " + job_error +
" could not start stunnel " + (config_error ?? string.Empty);
}
}
void kill_stunnel()
{
try
{
Process[] ps = Process.GetProcessesByName("stunnel");
ps.ForEach(p =>
{
try { p.Kill(); }
catch {}
});
}
catch { }
}
Process _stunnel_process;
static internal string stunnel_job_error { get; private set;}
void init_stunnel(string[] argv)
{
try
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
if (File.Exists(path + "config\\stunnel.conf"))
{
uint job_error = BTSUINativeMethods.setup_process_job_object();
//if we can't put this process and stunnel into a job, try to stop stunnel then restart
if(job_error != 0)
kill_stunnel();
start_stunnel(path, job_error);
}
}
catch(Exception ex)
{
stunnel_job_error = "Error starting stunnel: " + ex.Message;
}
}
public new int Run() {
var argv = Environment.GetCommandLineArgs();
try {
if (argv.Contains("-multiple") || _mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
{
if(_mutex == null || _mutex.WaitOne(TimeSpan.Zero, true))
init_stunnel(argv);
try {
if (argv.Contains("-multiple") ||
_mutex == null ||
_mutex.WaitOne(TimeSpan.Zero, true)) {
return base.Run();
}
else {
@@ -6,7 +6,6 @@ using bts.api.persistence;
using bts.api.position;
using bts.api.synthetic;
using bts.co2;
using bts.md;
using bts.metrics;
using bts.support;
using System.ComponentModel.Composition;
@@ -51,9 +50,6 @@ namespace Spark {
AlgoServiceWrapper _algo_service_wrapper;
[Export]
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
[Export]
public MBOService mbo_service => context.locate<MBOService>();
}
class AlgoServiceWrapper {
@@ -1,7 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using mbts.co2;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
@@ -205,8 +205,8 @@ namespace mbts.spark {
}
void _show_order_book(Side side) =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData(
new bts.shell.orderbook.FilterStates {
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.OrderBookWindowFactory.OrderBookStartupData(
new bts.shell.OrderBookWindowFactory.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
@@ -1,5 +1,4 @@
using bts.api;
using bts.collections;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
@@ -8,6 +7,7 @@ using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
@@ -40,7 +40,7 @@ namespace mbts.spark {
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
@@ -81,7 +81,7 @@ namespace mbts.spark {
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, DefaultSelectionHighlight, h_alignment);
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UShortMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null, h_alignment);
@@ -43,7 +43,7 @@ namespace mbts.spark {
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
@@ -58,15 +58,15 @@ namespace mbts.spark {
_view = new MarketViewView { DataContext = _vm };
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
_commands = new List<SparkCommand> {
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(_vm.selected_securities_observable(), _ => true,
SparkCommands.ShowLadders.complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity.complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills.complete(_vm.selected_securities_observable(), _ => true,
secs => new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
})
),
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate(SparkCommands.KeyGestures.ShowSyntheticCreate).complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
SparkCommands.ShowSyntheticCreate.complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
@@ -1,6 +1,7 @@
using bts.api;
using bts.co2;
using mbts.co2;
using mbts.collections.extensions;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -1,23 +1,22 @@
using bts.api;
using bts.co2;
using bts.collections;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
using mbts.persist;
using mbts.ui.settings;
using System.Reactive.Linq;
namespace mbts.spark {
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
@@ -62,9 +62,9 @@ namespace mbts.spark {
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
_commands = new SparkCommand[] {
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
@@ -420,6 +420,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<c:HelpButton Grid.Column="8" Margin="3,0,0,0" ManPage="bts_spark/spreader/#creating-a-synthetic" />
</Grid>
<c:DataGridEx
x:Name="PART_DataGrid"
@@ -1,5 +1,4 @@
using bts.api;
using bts.collections;
using bts.spark;
using mbts.spark;
using mbts.ui.adorners;
@@ -8,6 +7,7 @@ using mbts.ui.utility;
using mbts.user.settings;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -66,10 +66,10 @@ namespace mbts.spark {
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
current_row_connectable_builder.create(observe_row_strategy),
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
), SparkCommands.ShowLadder.complete(
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
filters = new OrderBookWindowFactory.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
}
}
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,10 +1,12 @@
using bts.shell;
using mbts.ui.spark;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
using PriceType = bts.spark.ConditionalQuoting.Types.PriceType;
using mbts.collections.extensions;
using System.Collections.Generic;
using bts.shell;
namespace mbts.spark {
namespace mbts.spark
{
partial class SyntheticEditorVM
{
class ConditionalQuotingPriceTypeModel : RowModelBase<PriceType>
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,4 +1,5 @@
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
using System.Windows;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,44 +0,0 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
using System.Windows;
namespace mbts.spark
{
partial class SyntheticEditorVM
{
class QuoteQueueHolderModel : RowModelBase<uint>, IHaveFontParams
{
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public QuoteQueueHolderModel()
{
}
public override string display_name { get; } = "Queue Holder Length";
public override string help_text { get; } = "How many levels of queue to hold";
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
public FontWeight font_weight => FontWeights.SemiBold;
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup).NQueueHolderOrders;
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value)
{
_value_store[leg_tup.SecurityKey] = value;
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = value ;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) != null;
QuoteParams _get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => 0u);
}
}
}
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,4 @@
using bts.api;
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
@@ -200,16 +199,14 @@ namespace mbts.spark {
var post_hedge_opposite_market_based_cross_threshold = new PostHedgeOppositeMarketVolumeBasedCrossThresholdModel();
var post_hedge_opposite_market_based_cross_cross_ticks = new PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel();
var is_quote_model = new IsQuoteModel();
rows = new IRowModel[] {
_quantity_model,
_price_mult_model,
is_quote_model,
new IsQuoteModel(),
//new EnableHedgeModel(), //TAHA_SPREADER
new HedgeRatioModel(),
//new MinLeanQtyModel(), //TAHA_SPREADER
//new QuoteHiddenModel(),
new QuoteQueueHolderModel(),
new MarketDataTypeModel(),
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
@@ -1,9 +1,10 @@
using bts.api;
using bts.collections;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
@@ -169,8 +170,8 @@ namespace mbts.spark {
_bus.post(SparkTags.ShowLadderTag, md);
void _show_order_book() =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
filters = new bts.shell.orderbook.FilterStates {
_bus.post(SparkTags.ShowOrderBookTag, new OrderBookWindowFactory.OrderBookStartupData {
filters = new OrderBookWindowFactory.FilterStates {
security_criteria = new SecurityCriteria(security)
}
});
@@ -565,19 +565,20 @@
Margin="0,6,0,0"
Padding="12,8"
/>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
</StackPanel.Margin>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</StackPanel>
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
<c:DialogSkirt.RightItems>
<x:ArrayExtension Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</x:ArrayExtension>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</ScrollViewer>
</Grid>
</AdornerDecorator>
</UserControl>
</UserControl>
@@ -40,20 +40,16 @@ namespace mbts.spark {
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
public bool Equals(AlgoStructureKey other) {
if (_structure == null) {
return other._structure == null;
} else {
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
return other._structure != null && _structure.Length == other._structure.Length && _structure.SequenceEqual(other._structure);
}
}
public bool Equals(AlgoStructureKey other) =>
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
_structure.Length == other._structure.Length &&
_structure.SequenceEqual(other._structure);
public override bool Equals(object obj) =>
obj is AlgoStructureKey other && Equals(other);
public override int GetHashCode() =>
_structure == null ? 0 : EqualityComparer.aggregate_hash(_structure);
EqualityComparer.aggregate_hash(_structure);
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
@@ -11,14 +11,14 @@ namespace mbts.spark {
public static readonly IComparer<AlgoDefinitionVM> algo_definition_vm_comparer =
new ComparerBuilder<AlgoDefinitionVM>(
new [] {
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_groups),
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_modes),
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static readonly IComparer<IAlgoVM> algo_vm_comparer =
new ComparerBuilder<IAlgoVM>(
new [] {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_groups),
Comparer<IAlgoVM>.Create(_CompareUtils.compare_modes),
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
@@ -47,24 +47,28 @@ namespace mbts.spark {
}
static class _CompareUtils {
static AlgoDisplayGroup[] _sorted_groups = new[] {
AlgoDisplayGroup.Exchange,
AlgoDisplayGroup.BTS,
AlgoDisplayGroup.Studio,
AlgoDisplayGroup.API
};
public static int compare_modes(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
_compare_modes(a.key.mode, b.key.mode);
public static int compare_groups(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_groups(IAlgoVM a, IAlgoVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_modes(IAlgoVM a, IAlgoVM b) =>
_compare_modes(a.id.mode, b.id.mode);
public static int compare_names(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(IAlgoVM a, IAlgoVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.display_name, b.display_name);
static int _compare_modes(AlgoKey.Mode a, AlgoKey.Mode b) {
// Factory mode is 'lowest', consider all other modes equivalent.
if (a == AlgoKey.Mode.Factory) {
if (b != AlgoKey.Mode.Factory)
return -1;
} else if (b == AlgoKey.Mode.Factory) {
return 1;
}
return 0;
}
}
}
}
@@ -1,12 +1,10 @@
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.proto;
using bts.utils;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.color_picker;
using mbts.ui.shell;
using mbts.ui.utility;
@@ -27,8 +25,7 @@ namespace mbts.spark {
None = 0,
Exchange = 1,
Spreader = 1 << 1,
Attachable = 1 << 2,
DefaultPlaceholder = 1 << 3
DefaultPlaceholder = 1 << 2
}
class AlgoDefinitionVM : DisposableViewModelBase, IAlgoStudioContentVM, IAlgoSelector, IEquatable<AlgoDefinitionVM> {
@@ -131,16 +128,27 @@ namespace mbts.spark {
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
_algo_root.observe_current(true).SubscribeEach(vm =>
vm == null ? null :
Disposable.Create(
vm.subscribe_is_dirty().Subscribe(v => {
if (!v) {
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
if (vm == null)
return Disposable.empty;
if (vm is ILazyAlgo la) {
bool synced_with_loaded = false;
return vm.subscribe_definition().Subscribe(_ => {
if (la.is_loaded) {
if (!synced_with_loaded) {
synced_with_loaded = true;
_version.increment_version();
_on_committed_changed();
} else {
_refresh_on_local_changes.execute();
}
}),
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
} else {
synced_with_loaded = false;
}
});
}
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
}),
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
ObservableAutoWrapper.connect_item_hooks(templates, t => new CompositeDisposable(_any_templates_dirty_monitor.join(t), t), true),
Disposable.Create(() => {
@@ -160,7 +168,7 @@ namespace mbts.spark {
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeEach(t => {
.SubscribeHookLatest(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
@@ -173,7 +181,6 @@ namespace mbts.spark {
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(this.connect_data(Observable.Return(model)));
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
_dispose = resources;
await algo_root.load_from_async(def_data.value);
_version.increment_version();
@@ -218,20 +225,6 @@ namespace mbts.spark {
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
public bool is_dirty => _is_dirty.Value;
public bool is_factory_definition => key.mode == AlgoKey.Mode.Factory;
public AlgoDisplayGroup group {
get {
switch (key.mode) {
case AlgoKey.Mode.Factory:
if (algo_root != null)
return algo_root.group;
break;
case AlgoKey.Mode.Unpersisted:
case AlgoKey.Mode.Persisted:
return AlgoDisplayGroup.Studio;
}
return AlgoDisplayGroup.BTS;
}
}
public bool can_redo => _can_redo.Value;
public bool can_undo => _can_undo.Value;
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
@@ -257,46 +250,11 @@ namespace mbts.spark {
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
#region IAlgoSelector
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed {
add => algo_root_changed += value;
remove => algo_root_changed -= value;
}
event PropertyChange<IAlgoVM> IAlgoParamVM<IAlgoVM>.value_changed {
add => algo_root_changed += value;
remove => algo_root_changed -= value;
}
event PropertyChange<AlgoParamPermissions> IAlgoParamVM.permissions_changed { add { } remove { } }
event Action<bool> IAlgoParamVM.is_dirty_changed { add { } remove { } }
string IAlgoParamVM.id => key.ToString();
string IAlgoParamVM.display_name => "Algo";
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
ObservableKeyedCollection<IAlgoVM, AlgoKey> IAlgoSelector.algos => _algo_roots;
IAlgoVM IAlgoSelector.selected_algo {
get => algo_root;
set => algo_root = value;
}
IAlgoVM IAlgoParamVM<IAlgoVM>.value {
get => algo_root;
set => algo_root = value;
}
bool IAlgoParamVM.has_value => _algo_root != null;
bool IAlgoParamVM.is_enabled { get; set; } = true;
object IAlgoParamVM.value {
get => algo_root;
set => algo_root = (IAlgoVM)value;
}
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
IAlgoVM IAlgoSelector.selected_algo { get => algo_root; set => algo_root = value; }
void IAlgoSelector.load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) =>
_load_algo_data(algo_data, default_algo);
void IAlgoParamVM.revert() { }
void IAlgoParamVM.mark_committed() { }
IObservable<IAlgoVM> IAlgoSelector.subscribe_selected_algo() =>
this.observe_algo_root();
_load_algo_data(algo_data, default_algo);
#endregion
#region IObserver (algo data...)
@@ -371,29 +329,6 @@ namespace mbts.spark {
}
}
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> defs) {
var def_list = defs as IList<CustomOrderDefinitionImmutable> ?? defs.ToList();
switch (def_list.Count) {
case 0: algo_root = null; break;
case 1: load_def(def_list[0]); break;
default:
var def = def_list[0];
var key = OrderDataHelper.get_shallow_factory_key(def);
if (algo_root?.id == key) {
algo_root.load_many(def_list);
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
vm.load_many(def_list);
algo_root = vm;
} else {
OrderTypeVMFactory.create(key, def.display_name(), def.CustomCase, is_expanded: true);
algo_roots.Add(vm = _mk_algo_root(def));
vm.load_many(def_list);
algo_root = vm;
}
break;
}
}
public void load(AlgoData data) {
_assert_can_load(data.key);
if (algo_root != null)
@@ -445,12 +380,8 @@ namespace mbts.spark {
_committed = _version.value;
_is_dirty.refresh();
_refresh_undo_redo();
RaisePropertyChanged(nameof(committed_value), nameof(current_value));
}
public Snapshot? committed_value => _committed;
public Snapshot current_value => _version.value;
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
@@ -553,17 +484,5 @@ namespace mbts.spark {
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
}
struct SnapshotTemplateEqualityComparer : IEqualityComparer<Snapshot> {
public bool Equals(Snapshot x, Snapshot y) =>
x.abbreviation == y.abbreviation &&
x.custom_badge_color == y.custom_badge_color &&
x.format == y.format &&
x.data.key == y.data.key &&
x.data.name == y.data.name &&
x.data.value.Equals(y.data.value);
public int GetHashCode(Snapshot obj) => throw new NotImplementedException();
}
}
}
@@ -1,95 +0,0 @@
using bts.utils;
using mbts.collections;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace mbts.spark {
class AlgoParamRowVM : DisposableViewModelBase, DIYGrid.IRowVM, IEditableObject, ITemplateSelectorKey, IEquatable<AlgoParamRowVM> {
bool _is_enabled;
string _template_key;
ICommand _set_value_cmd;
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
this.param = param;
this.algo = algo;
this.parent = parent;
_template_key = template_key;
}
public event Action invalidated;
public IAlgoVM algo { get; set; }
public IAlgoParamVM param { get; }
public AlgoParamRowVM parent { get; }
public int level => parent == null ? 0 : parent.level + 1;
public string path_disambiguation {
get {
if (parent == null)
return string.Empty;
string pd = parent.path_disambiguation;
if (parent.algo.order_type == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Cover) {
if (pd.Length > 0)
pd += ".";
pd += parent.param.display_name;
}
return pd;
}
}
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
public ICommand set_value_cmd {
get {
if (_set_value_cmd == null) {
var cmd = new DelegateCommand(v => {
var orig_val = param.value;
try {
param.value = v;
} catch (InvalidCastException) {
param.value = orig_val;
}
}, _ => param.permissions == AlgoParamPermissions.ReadWrite);
concated_disposable(param.subscribe_permissions().Subscribe(_ => cmd.RaiseCanExecuteChanged()));
_set_value_cmd = cmd;
}
return _set_value_cmd;
}
}
string ITemplateSelectorKey.key => _template_key;
public void raise_invalidated() => invalidated?.Invoke();
public bool Equals(AlgoParamRowVM other) =>
other != null &&
param.id == other.param.id &&
parent.safe_equals(other.parent);
public override bool Equals(object obj) =>
Equals(obj as AlgoParamRowVM);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(param.id.GetHashCode(), parent?.GetHashCode() ?? 0);
#region IEditableObject
object _committed_value;
void IEditableObject.BeginEdit() => _committed_value = param.value;
void IEditableObject.EndEdit() => _committed_value = null;
void IEditableObject.CancelEdit() {
if (_committed_value != null)
param.value = _committed_value;
_committed_value = null;
}
#endregion
}
class AlgoSelectorParamRowVM : AlgoParamRowVM {
readonly IAlgoSelector _selector;
public AlgoSelectorParamRowVM(IAlgoVM algo_vm, IAlgoSelector selector, AlgoParamRowVM parent) : base(algo_vm, selector, parent) =>
_selector = selector;
public ObservableKeyedCollection<IAlgoVM, AlgoKey> algos => _selector.algos;
}
}
@@ -1,6 +1,5 @@
using bts.api;
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui;
@@ -38,7 +37,6 @@ namespace mbts.spark {
nameof(AlgoDefinitionVM.can_redo)
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly DisposableContainer _value_hooks = new DisposableContainer();
Action _definition_changed = () => { };
_ObservedData? _data_cache;
@@ -48,7 +46,6 @@ namespace mbts.spark {
value = algo_definition_vm;
_dispose = new CompositeDisposable(_value_hooks) {
_child_algo_selectors.clear_collections,
_algo_params.clear_collections,
() => _value?.Dispose()
};
}
@@ -68,6 +65,7 @@ namespace mbts.spark {
}
public AlgoKey id => _value.key;
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
public bool is_bts_type => _algo_root?.is_bts_type ?? false;
public OrderMetadata meta {
get {
if (_value != null) {
@@ -89,19 +87,8 @@ namespace mbts.spark {
}
}
public string display_name => _value?.name ?? string.Empty;
public string abbreviation => _value?.abbreviation;
public AlgoDisplayGroup group => _value?.group ?? AlgoDisplayGroup.BTS;
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<IAlgoSelector> child_algo_selectors_view {
get {
_value?.algo_root?.ensure_init();
return _child_algo_selectors;
}
}
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
[EditorBrowsable(EditorBrowsableState.Never)]
public dynamic children_vm => _algo_root?.children_vm;
IAlgoVM _algo_root => _value?.algo_root;
@@ -109,9 +96,7 @@ namespace mbts.spark {
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
IDisposable _set_hooks(AlgoDefinitionVM vm) {
var algo_root_subs = new DisposableContainer();
@@ -120,20 +105,17 @@ namespace mbts.spark {
vm.observe_algo_root().Subscribe(r => {
algo_root_subs.replace(null);
_child_algo_selectors.clear_collections();
_algo_params.clear_collections();
if (r != null) {
if (_data_cache.HasValue) {
_data_cache.Value.forward_to(r);
}
_child_algo_selectors.add_collection(r.child_algo_selectors);
_algo_params.add_collection(r.algo_params);
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
algo_root_subs.replace(new CompositeDisposable(
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
});
}
RaisePropertyChanged(nameof(group));
}),
algo_root_subs) {
() => vm.PropertyChanged -= on_value_prop_changed
@@ -213,4 +195,4 @@ namespace mbts.spark {
new _ObservedData(permissions, model, security, security_md);
}
}
}
}
@@ -1,618 +0,0 @@
<UserControl
x:Name="me"
x:Class="mbts.ui.spark.AlgoEditorGridView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/DataGrid.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="VerticalPush1">4</sys:Double>
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<sys:String x:Key="NonUniformValueText">-</sys:String>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
<Thickness x:Key="ComboBox.CellPadding" Top="2" Bottom="2"/>
<!--Converters-->
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
<cv:TimeConverter x:Key="TimeConverter"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" Multiplier="18" UnitType="Pixel"/>
<cv:EnumItemsSourceConverter x:Key="EnumItemsSourceConverter"/>
<cv:NullSubstitutionConverterWrapper x:Key="SimpleValueTextConverter" ValueForNull="{StaticResource NonUniformValueText}"/>
<cv:NullSubstitutionConverterWrapper x:Key="NullableEnumDescriptionConverter" Converter="{StaticResource EnumDescriptionConverter}" ValueForNull="{StaticResource NonUniformValueText}"/>
<cv:NullSubstitutionConverterWrapper x:Key="QuantityMeasureTextConverter" Converter="{x:Static cv:ValueConverters.QuantityMeasureTextConverter}" ValueForNull="{StaticResource NonUniformValueText}"/>
<cv:DoubleTextConverter x:Key="DoubleTextConverter" NaNText="{StaticResource NonUniformValueText}"/>
<cv:ValueSubstitutionConverterWrapper x:Key="StringTextConverter">
<cv:KeyValue Key="{x:Null}" Value="{StaticResource NonUniformValueText}"/>
<cv:KeyValue Key="{x:Static sys:String.Empty}" Value="{StaticResource NonUniformValueText}"/>
</cv:ValueSubstitutionConverterWrapper>
<cv:EnumHasFlagsConverter x:Key="ParamPermissionsToIsReadOnlyConverter">
<sp:AlgoParamPermissions>None</sp:AlgoParamPermissions>
<sp:AlgoParamPermissions>ReadOnly</sp:AlgoParamPermissions>
</cv:EnumHasFlagsConverter>
<!--Default Styles-->
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
</Style>
<!--Common Templates-->
<DataTemplate x:Key="AlgoVMItemTemplate">
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="ExecutionStrategyTemplate">
<TextBlock>
<Hyperlink Click="_on_execution_strategy_toggle_click">
Show Strategy Params
</Hyperlink>
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="IndentedParamNameAndBadgeCellTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding level, Converter={StaticResource GridLengthPixelConverter}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<usp:OrderTypeItem Grid.Column="1" Focusable="False" IsTabStop="False" BadgeFill="{Binding Path='(sp:AlgoParamRowVM.algo).(sp:IAlgoVM.meta).Color', Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding Path='param.(sp:IAlgoParamVM.display_name)'}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="NullableEnumDescriptionTemplate">
<TextBlock Text="{Binding Converter={StaticResource NullableEnumDescriptionConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="ParamNameAndDisambiguationCellTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="SimpleValueTextTemplate">
<TextBlock x:Name="value_text" Text="{Binding param.value, Converter={StaticResource SimpleValueTextConverter}}"/>
</DataTemplate>
<!--Param Name Templates-->
<u:KeyedTemplateSelector x:Key="ParamNameTemplateSelector" Default="{StaticResource ParamNameAndDisambiguationCellTemplate}">
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col1"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<TextBlock x:Name="bid_text" Grid.Column="1" HorizontalAlignment="Center">
<TextBlock.ToolTip>
<TextBlock>
Best Bid Price (click to assign)
<LineBreak/>
<Italic>
Hold a number key to cross the market by 'n' ticks.
</Italic>
</TextBlock>
</TextBlock.ToolTip>
<Hyperlink
Focusable="False"
Foreground="{u:BTSColor MarketData.bid}"
Command="{Binding DataContext.payup_bid_cmd, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
PreviewMouseLeftButtonDown="{x:Static usp:AlgoEditorGridView.PayupTicksMouseButtonHandler}"
>
<Run>
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="DataContext.bbo.bid" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
</MultiBinding>
</Run.Text>
</Run>
</Hyperlink>
</TextBlock>
<TextBlock x:Name="ask_text" Grid.Column="2" HorizontalAlignment="Center" Margin="4,0">
<TextBlock.ToolTip>
<TextBlock>
Best Ask Price (click to assign)
<LineBreak/>
<Italic>
Hold a number key to cross the market by 'n' ticks.
</Italic>
</TextBlock>
</TextBlock.ToolTip>
<Hyperlink
Focusable="False"
Foreground="{u:BTSColor MarketData.ask}"
Command="{Binding DataContext.payup_ask_cmd, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
PreviewMouseLeftButtonDown="{x:Static usp:AlgoEditorGridView.PayupTicksMouseButtonHandler}"
>
<Run>
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="DataContext.bbo.ask" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
</MultiBinding>
</Run.Text>
</Run>
</Hyperlink>
</TextBlock>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.security, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{x:Null}">
<Setter TargetName="bid_text" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="ask_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding param.permissions, Converter={StaticResource ParamPermissionsToIsReadOnlyConverter}}" Value="{u:True}">
<Setter TargetName="bid_text" Property="ToolTip" Value="Best Bid Price"/>
<Setter TargetName="ask_text" Property="ToolTip" Value="Best Ask Price"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col1"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<TextBlock
x:Name="bid_text"
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="{u:BTSColor MarketData.bid}"
Text="{Binding DataContext.bbo.bid_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Bid Size"
/>
<TextBlock
x:Name="ask_text"
Grid.Column="2"
HorizontalAlignment="Center"
Margin="4,0"
Foreground="{u:BTSColor MarketData.ask}"
Text="{Binding DataContext.bbo.ask_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Ask Size"
/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.security, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{x:Null}">
<Setter TargetName="bid_text" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="ask_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
<!--Read Templates-->
<usp:AlgoParamTemplateSelector
x:Key="ParamTemplateSelector"
IntTemplate="{StaticResource SimpleValueTextTemplate}"
NullableIntTemplate="{StaticResource SimpleValueTextTemplate}"
UIntTemplate="{StaticResource SimpleValueTextTemplate}"
NullableUIntTemplate="{StaticResource SimpleValueTextTemplate}"
ExecutionStrategyTemplate="{StaticResource ExecutionStrategyTemplate}"
>
<usp:AlgoParamTemplateSelector.KeyedTemplates>
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="param.value"/>
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
<DataTemplate>
<CheckBox Focusable="False" VerticalAlignment="Center" IsChecked="{Binding param.value, Mode=TwoWay}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.BoolTemplate>
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
<usp:AlgoParamTemplateSelector.EnumTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource EnumDescriptionConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.EnumTemplate>
<!--Nullable Enum-->
<usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource NullableEnumDescriptionConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<!--Order Def-->
<usp:AlgoParamTemplateSelector.OrderDefTemplate>
<DataTemplate>
<usp:OrderTypeItem BadgeFill="{Binding Path='(sp:AlgoParamRowVM.param).(sp:IAlgoSelector.selected_algo).meta.Color', Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding Path='(sp:AlgoParamRowVM.param).(sp:IAlgoSelector.selected_algo).(sp:IAlgoVM.display_name)'}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.OrderDefTemplate>
<!--Quantity Measure-->
<usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource QuantityMeasureTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<!--String-->
<usp:AlgoParamTemplateSelector.StringTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource StringTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.StringTemplate>
<!--Timestamp-->
<usp:AlgoParamTemplateSelector.TimestampTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource TimeConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.TimestampTemplate>
</usp:AlgoParamTemplateSelector>
<!--Write Templates-->
<usp:AlgoParamTemplateSelector x:Key="ParamEditingTemplateSelector" ExecutionStrategyTemplate="{StaticResource ExecutionStrategyTemplate}">
<usp:AlgoParamTemplateSelector.KeyedTemplates>
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
<DataTemplate>
<usp:PriceEntryControl
Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
ContractSpec="{Binding DataContext.spec, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
Focusable="False"
IsTabStop="False"
ArrowKeyTickModifiers="Ctrl"
>
<usp:PriceEntryControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</usp:PriceEntryControl.CommandBindings>
<usp:PriceEntryControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</usp:PriceEntryControl.InputBindings>
</usp:PriceEntryControl>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
<DataTemplate>
<CheckBox Focusable="False" VerticalAlignment="Center" IsChecked="{Binding param.value, Mode=TwoWay}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.BoolTemplate>
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownDouble.CommandBindings>
<c:TickUpDownDouble.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownDouble.InputBindings>
</c:TickUpDownDouble>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
<usp:AlgoParamTemplateSelector.EnumTemplate>
<DataTemplate>
<ComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.EnumTemplate>
<!--Int-->
<usp:AlgoParamTemplateSelector.IntTemplate>
<DataTemplate>
<c:TickUpDownInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownInt.CommandBindings>
<c:TickUpDownInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownInt.InputBindings>
</c:TickUpDownInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.IntTemplate>
<!--Nullable Bool-->
<usp:AlgoParamTemplateSelector.NullableBoolTemplate>
<DataTemplate>
<CheckBox Focusable="False" VerticalAlignment="Center" IsThreeState="True" IsChecked="{Binding param.value, Mode=TwoWay}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableBoolTemplate>
<!--Nullable Enum-->
<usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<DataTemplate>
<ComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
ItemTemplate="{StaticResource NullableEnumDescriptionTemplate}"
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<!--Nullable Int-->
<usp:AlgoParamTemplateSelector.NullableIntTemplate>
<DataTemplate>
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableInt.CommandBindings>
<c:TickUpDownNullableInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableInt.InputBindings>
</c:TickUpDownNullableInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableIntTemplate>
<!--Nullable UInt-->
<usp:AlgoParamTemplateSelector.NullableUIntTemplate>
<DataTemplate>
<c:TickUpDownNullableUInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableUInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableUInt.CommandBindings>
<c:TickUpDownNullableUInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableUInt.InputBindings>
</c:TickUpDownNullableUInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableUIntTemplate>
<!--Order Def-->
<usp:AlgoParamTemplateSelector.OrderDefTemplate>
<DataTemplate>
<ComboBox HorizontalAlignment="Stretch" DataContext="{Binding param}" ItemsSource="{Binding Path='(sp:IAlgoSelector.algos)'}" ItemTemplate="{StaticResource AlgoVMItemTemplate}" SelectedItem="{Binding Path='(sp:IAlgoSelector.selected_algo)', UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.OrderDefTemplate>
<!--QuantityMeasure-->
<usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<uag:QuantityMeasureControl Focusable="False" HorizontalAlignment="Stretch" QuantityMeasure="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</DataTemplate>
</usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<!--Timestamp-->
<usp:AlgoParamTemplateSelector.TimestampTemplate>
<DataTemplate>
<Grid DataContext="{Binding algo}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox
x:Name="PART_StartMode_ComboBox"
HorizontalAlignment="Stretch"
ItemsSource="{Binding start_modes}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding start_mode}"
/>
<c:DateTimePicker
x:Name="PART_AbsoluteStart_DateTimePicker"
Grid.Column="2"
TimeTuple="{Binding absolute_start}"
Visibility="{Binding show_absolute_start, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
<c:TimeSpanControl
x:Name="PART_RelativeStart_TimeSpanControl"
Grid.Column="2"
TimeSpan="{Binding relative_start, Mode=TwoWay}"
Visibility="{Binding show_relative_start, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</Grid>
</DataTemplate>
</usp:AlgoParamTemplateSelector.TimestampTemplate>
<!--UInt-->
<usp:AlgoParamTemplateSelector.UIntTemplate>
<DataTemplate>
<c:TickUpDownUInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}">
<c:TickUpDownUInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownUInt.CommandBindings>
<c:TickUpDownUInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownUInt.InputBindings>
</c:TickUpDownUInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.UIntTemplate>
</usp:AlgoParamTemplateSelector>
<usp:AlgoEditorGridRowStyleSelector x:Key="RowStyleSelector">
<usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding param.is_dirty}" Value="{u:True}">
<Setter Property="Background" Value="#8098FB98"/>
<Setter Property="ToolTip" Value="Value has been modified."/>
</DataTrigger>
</Style.Triggers>
</Style>
</usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
<usp:AlgoEditorGridRowStyleSelector.SpreaderStyle>
<Style TargetType="DataGridRow">
<Setter Property="DetailsVisibility" Value="Collapsed"/>
<Setter Property="DetailsTemplate">
<Setter.Value>
<DataTemplate>
<AdornerDecorator>
<usp:ExecutionStrategyEditor
DataContext="{Binding algo.strategy_editor}"
HorizontalAlignment="Stretch"
Margin="6"
SyntheticName="{Binding name}"
TickFactors="{Binding tick_factors}"
IsNew="{Binding is_new}"
CanUserAddNewLeg="{Binding can_add_leg}"
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
PriceCalc="{Binding price_calc}"
ParameterRows="{Binding rows}"
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
/>
</AdornerDecorator>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</usp:AlgoEditorGridRowStyleSelector.SpreaderStyle>
</usp:AlgoEditorGridRowStyleSelector>
</ResourceDictionary>
</UserControl.Resources>
<u:StateManager.Groups>
<u:StateGroupsManager>
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding IsReadOnly, Source={u:DirectReference me}, Converter={x:Static usp:AlgoEditorGridView.GeneralVisualStateConverter}}"/>
<u:StateDefinition State="{Binding CanUserChangeAlgoRoot, Source={u:DirectReference me}, Converter={x:Static usp:AlgoEditorGridView.AlgoRootVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}">
<VisualState Name="{x:Static Normal, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}"/>
<VisualState Name="{x:Static ReadOnly, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(DataGrid.IsReadOnly)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<ColorAnimation Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="Gray"/>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="root_algo_combo_box" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}">
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}"/>
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="root_algo_label" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="root_algo_combo_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ThicknessAnimation Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(Control.Margin)" BeginTime="0" Duration="0" To="0,4,0,0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="root_algo_label" VerticalAlignment="Center" Margin="{StaticResource Margin.LP2}" Visibility="Collapsed">Algo</TextBlock>
<ComboBox
x:Name="root_algo_combo_box"
Margin="40,0,0,0"
ItemsSource="{Binding AlgoRootsViewSource.View, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
SelectedItem="{Binding SelectedAlgoRoot, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
Visibility="Collapsed"
>
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding display_name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<DataGrid
x:Name="_param_grid"
Grid.Row="1"
Grid.IsSharedSizeScope="True"
VirtualizingPanel.ScrollUnit="Pixel"
AlternatingRowBackground="{x:Null}"
IsReadOnly="False"
Margin="0,2,0,0"
MinRowHeight="18"
ColumnHeaderHeight="0"
ItemsSource="{Binding AlgoParamRowsViewSource.View, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
SelectionMode="Single"
RowStyleSelector="{StaticResource RowStyleSelector}"
>
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="True" Header="Param" CellTemplateSelector="{StaticResource ParamNameTemplateSelector}" MinWidth="120" Width="*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
<Setter Property="IsTabStop" Value="False"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ParamTemplateSelector}" CellEditingTemplateSelector="{StaticResource ParamEditingTemplateSelector}" MinWidth="120" Width="*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=DataGrid}}" Value="{u:False}">
<Setter Property="ToolTip" Value="Click to edit"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
@@ -1,183 +0,0 @@
using bts.collections;
using bts.utils;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorGridView : UserControl {
public static readonly IValueConverter
GeneralVisualStateConverter = ValueConverter.create<bool, string>(is_read_only => is_read_only ? VisualStateNames.General.ReadOnly : VisualStateNames.General.Normal),
AlgoRootVisualStateConverter = ValueConverter.create<bool, string>(show_bbo => show_bbo ? VisualStateNames.AlgoRoot.Active : VisualStateNames.AlgoRoot.Inactive),
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0));
public static readonly MouseButtonEventHandler PayupTicksMouseButtonHandler = (s, e) => {
if ((s as Hyperlink)?.Command is ICommand cmd) {
cmd.ExecuteIfCan(KeyboardExtensions.GetPressedNumber() ?? 0);
e.Handled = true;
}
};
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
public AlgoEditorGridView() {
InitializeComponent();
_set_value_column_width();
// AlgoRoots
AlgoRootsViewSource = new CollectionViewSource<_AlgoRootsCollectionView>();
BindingOperations.SetBinding(AlgoRootsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoRoots)) { Source = this });
AlgoRootsViewSource.GroupDescriptions.Add(new PropertyGroupDescription(nameof(IAlgoVM.group)));
// AlgoParamRows
AlgoParamRowsViewSource = new CollectionViewSource<ListCollectionView<AlgoParamRowVM>>();
BindingOperations.SetBinding(AlgoParamRowsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoParamRows)) { Source = this });
AlgoParamRowsViewSource.Filter += param_rows_filter;
DataGridEditHelper.set_hooks(this);
void param_rows_filter(object sender, FilterEventArgs e) =>
e.Accepted = e.Item is AlgoParamRowVM row && row.param.permissions != AlgoParamPermissions.None;
}
public enum Columns { ParamName, Value }
event Action CommitEditRequested, CancelEditRequested;
public CollectionViewSource AlgoRootsViewSource { get; }
public CollectionViewSource<ListCollectionView<AlgoParamRowVM>> AlgoParamRowsViewSource { get; }
bool _stretch_value_column = true;
public bool StretchValueColumn {
get => _stretch_value_column;
set {
if (_stretch_value_column != value) {
_stretch_value_column = value;
_set_value_column_width();
}
}
}
#region IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(IsReadOnly));
public bool IsReadOnly {
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
#endregion
#region CanUserChangeAlgoRoot
public static readonly DependencyProperty CanUserChangeAlgoRootProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(CanUserChangeAlgoRoot));
public bool CanUserChangeAlgoRoot {
get => (bool)GetValue(CanUserChangeAlgoRootProperty);
set => SetValue(CanUserChangeAlgoRootProperty, value);
}
#endregion
#region AlgoRoots
public static readonly DependencyProperty AlgoRootsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<IAlgoVM>, AlgoEditorGridView>(nameof(AlgoRoots));
public IReadOnlyObservableCollection<IAlgoVM> AlgoRoots {
get => GetValue(AlgoRootsProperty) as IReadOnlyObservableCollection<IAlgoVM>;
set => SetValue(AlgoRootsProperty, value);
}
#endregion
#region SelectedAlgoRoot
public static readonly DependencyProperty SelectedAlgoRootProperty =
WPFHelper.create_dp<IAlgoVM, AlgoEditorGridView>(nameof(SelectedAlgoRoot), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public IAlgoVM SelectedAlgoRoot {
get => GetValue(SelectedAlgoRootProperty) as IAlgoVM;
set => SetValue(SelectedAlgoRootProperty, value);
}
#endregion
#region AlgoParamRows
public static readonly DependencyProperty AlgoParamRowsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<AlgoParamRowVM>, AlgoEditorGridView>(nameof(AlgoParamRows));
public IReadOnlyObservableCollection<AlgoParamRowVM> AlgoParamRows {
get => GetValue(AlgoParamRowsProperty) as IReadOnlyObservableCollection<AlgoParamRowVM>;
set => SetValue(AlgoParamRowsProperty, value);
}
#endregion
public static AlgoEditorGridView create_attach_view() =>
new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = true,
};
public void commit_edit() => CommitEditRequested?.Invoke();
public void cancel_edit() => CancelEditRequested?.Invoke();
void _set_value_column_width() {
foreach (var c in _param_grid?.Columns.empty_if_null())
c.Width = _stretch_value_column ? new DataGridLength(1, DataGridLengthUnitType.Star) : DataGridLength.Auto;
}
void _on_execution_strategy_toggle_click(object sender, RoutedEventArgs e) {
if (sender is Hyperlink hl && hl.try_find_ancestor_of_type(out DataGridRow row_view)) {
Visibility vis; string text;
if (row_view.DetailsVisibility == Visibility.Visible) {
vis = Visibility.Collapsed;
text = "Show Strategy Params";
} else {
vis = Visibility.Visible;
text = "Hide Strategy Params";
}
hl.Inlines.Clear();
hl.Inlines.Add(text);
row_view.SetCurrentValue(DataGridRow.DetailsVisibilityProperty, vis);
}
}
public static class VisualStateNames {
public static class General {
public const string GroupName = nameof(General);
public static readonly string
Normal = $"{GroupName}{nameof(Normal)}",
ReadOnly = $"{GroupName}{nameof(ReadOnly)}";
}
public static class AlgoRoot {
public const string GroupName = nameof(AlgoRoot);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
class _AlgoRootsCollectionView : ListCollectionView<IAlgoVM> {
public _AlgoRootsCollectionView(IList<IAlgoVM> list) : base(list) =>
CustomSort = AlgoCollectionHelper.algo_vm_comparer.to_non_generic();
}
}
class AlgoEditorGridRowStyleSelector : StyleSelector {
public Style DefaultStyle { get; set; }
public Style SpreaderStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container) {
if (item is AlgoParamRowVM row) {
if (row.param.id == nameof(bts.api.orderman.SpreaderOrderDefinition.ExecutionStrategy))
return SpreaderStyle;
}
return DefaultStyle;
}
}
}
@@ -1,88 +0,0 @@
using bts.collections;
using mbts.ui.controls;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
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 => {
rows.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, null, child_rows, include_algo_type_rows);
rows.add_collection(child_rows.AsReadOnly());
return hook;
})) {
rows.clear_collections
};
public static IDisposable negate_is_dirty(IReadOnlyObservableCollection<AlgoParamRowVM> rows) {
return ObservableAutoWrapper.connect_item_hooks(rows, hook);
IDisposable hook(AlgoParamRowVM r) {
r.param.is_dirty_changed += negate;
return Disposable.Create(unhook);
void negate(bool is_dirty) {
if (is_dirty)
r.param.mark_committed();
}
void unhook() =>
r.param.is_dirty_changed -= negate;
}
}
static IDisposable _wrap_rows(AlgoDefinitionVM algo_def, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) {
AlgoParamRowVM root = null;
ObservableAggregateCollection<AlgoParamRowVM> algo_params;
if (include_algo_type_rows) {
// while normal params live and die with the selected algo-type (when the algo-type changes, they are removed, and replaced with params for the new algo type),
// the algo-type row is constant (it is the row with the algo-type dropdown, which the user may use to select different algos).
algo_params = new ObservableAggregateCollection<AlgoParamRowVM>();
rows.add_collection(new ObservableAppendCollection<AlgoParamRowVM>(algo_params, root = new AlgoSelectorParamRowVM(new AlgoVMWrapper(algo_def), algo_def, null)).AsReadOnly());
} else {
algo_params = rows;
}
return algo_def.observe_algo_root().SubscribeEach(a => {
algo_params.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, root, child_rows, include_algo_type_rows);
algo_params.add_collection(child_rows.AsReadOnly());
return hook;
});
}
static IDisposable _wrap_rows(IAlgoVM algo_root, AlgoParamRowVM parent, ObservableAggregateCollection<AlgoParamRowVM> all_rows, bool include_algo_type_rows) {
var wrapped_params = new ObservableAutoWrapper<AlgoParamRowVM, IAlgoParamVM>(algo_root.algo_params, pvm => new AlgoParamRowVM(algo_root, pvm, parent));
var selector_params = new ObservableCollection<AlgoParamRowVM>();
all_rows.add_collection(wrapped_params);
all_rows.add_collection(selector_params);
return ObservableAggregateCollection.connect(selector_params, algo_root.child_algo_selectors, new Func<IAlgoSelector, (ObservableCollection<AlgoParamRowVM>, IDisposable)>(hook_children));
(ObservableCollection<AlgoParamRowVM>, IDisposable) hook_children(IAlgoSelector s) {
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
var selector_wrapper = new AlgoSelectorParamRowVM(algo_root, s, parent);
return (
include_algo_type_rows ?
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
child_rows,
s.subscribe_selected_algo().SubscribeEach(a => {
s.value = a;
child_rows.clear_collections();
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
}));
}
}
}
}
@@ -1,76 +0,0 @@
using bts.collections;
using bts.spark;
using mbts.spark;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui.spark {
static class AlgoParamTemplateKeys {
public const string
Price = nameof(Price),
Size = nameof(Size);
}
/// TODO: remove this class and just use a <see cref="KeyedTemplateSelector"/>.
class AlgoParamTemplateSelector : DataTemplateSelector {
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key);
public DataTemplate BoolTemplate { get; set; }
public DataTemplate NullableBoolTemplate { get; set; }
public DataTemplate IntTemplate { get; set; }
public DataTemplate NullableIntTemplate { get; set; }
public DataTemplate UIntTemplate { get; set; }
public DataTemplate NullableUIntTemplate { get; set; }
public DataTemplate DoubleTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
public DataTemplate NullableEnumTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
public DataTemplate QuantityMeasureTemplate { get; set; }
public DataTemplate OrderDefTemplate { get; set; }
public DataTemplate ExecutionStrategyTemplate { get; set; }
public DataTemplate TimestampTemplate { get; set; }
/// <see cref="_keyed_templates"/> is needed for the 'price' param. Price was formerly edited in a separate control, however now it is just another param row.
/// Before this, the generic type argument in the <see cref="IAlgoParamVM{T}"/> implementation was enough to determine the template.
/// Since 'price' is just a double, further specification was needed to associate it with a 'PriceEntryControl' template.
public IList KeyedTemplates => _keyed_templates;
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item is ITemplateSelectorKey tsk && tsk.key is string template_key && _keyed_templates.TryGetValue(template_key, out var tkv))
return tkv.Value;
IAlgoParamVM pvm = item as IAlgoParamVM ?? (item as AlgoParamRowVM)?.param;
if (pvm != null) {
if (pvm is IAlgoParamVM<double>) return DoubleTemplate;
if (pvm is IAlgoParamVM<int>) return IntTemplate;
if (pvm is IAlgoParamVM<int?>) return NullableIntTemplate;
if (pvm is IAlgoParamVM<uint>) return UIntTemplate;
if (pvm is IAlgoParamVM<uint?>) return NullableUIntTemplate;
if (pvm is IAlgoParamVM<bool>) return NullableBoolTemplate;
if (pvm is IAlgoParamVM<bool?>) return BoolTemplate;
if (pvm is IAlgoParamVM<string>) return StringTemplate;
if (pvm is IAlgoParamVM<IAlgoVM>) return OrderDefTemplate;
if (pvm is IAlgoParamVM<QuantityMeasureImmutable?>) return QuantityMeasureTemplate;
if (pvm is IAlgoParamVM<ExecutionStrategy>) return ExecutionStrategyTemplate;
if (pvm is IAlgoParamVM<Google.Protobuf.WellKnownTypes.Timestamp>) return TimestampTemplate;
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), typeof(IAlgoParamVM<>)).FirstOrDefault();
if (targ != null) {
if (targ.IsEnum)
return EnumTemplate;
else if (targ.IsNullable()) {
targ = targ.GetGenericArguments()[0];
if (targ.IsEnum)
return NullableEnumTemplate;
}
}
}
return null;
}
}
}
@@ -1,181 +0,0 @@
#define ENTER_BEHAVES_LIKE_TAB
using bts;
using mbts.ui.controls;
using mbts.ui.extensions;
using mbts.ui.spark.algo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorGridView {
static class DataGridEditHelper {
public static IDisposable set_hooks(AlgoEditorGridView algo_editor) {
var grid = algo_editor._param_grid;
grid.PreviewKeyDown += on_preview_key_down;
grid.KeyDown += on_key_down;
grid.SelectionChanged += on_selection_changed;
grid.PreparingCellForEdit += on_preparing_cell_for_edit;
grid.CellEditEnding += on_cell_edit_ending;
grid.GotFocus += on_got_focus;
algo_editor.CommitEditRequested += on_commit_edit_requested;
algo_editor.CancelEditRequested += on_cancel_edit_requested;
return Disposable.Create(() => {
grid.PreviewKeyDown += on_preview_key_down;
grid.KeyDown -= on_key_down;
grid.SelectionChanged -= on_selection_changed;
grid.PreparingCellForEdit -= on_preparing_cell_for_edit;
grid.CellEditEnding -= on_cell_edit_ending;
grid.GotFocus -= on_got_focus;
algo_editor.CommitEditRequested -= on_commit_edit_requested;
algo_editor.CancelEditRequested -= on_cancel_edit_requested;
});
/// Handlers
// hooked up to PreviewKeyDown, instead of KeyDown, so the cells can't override this behavior
void on_preview_key_down(object sender, KeyEventArgs e) {
switch (e.Key) {
case Key.Escape: e.Handled = cancel_edit(); break;
case Key.Enter:
if (grid.TryGetCurrentRowView(out var row_view)) {
if (row_view.IsEditing) {
commit_edit();
#if ENTER_BEHAVES_LIKE_TAB
NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Down);
#endif
} else grid.BeginEdit();
}
e.Handled = true;
break;
}
}
// hooked up to KeyDown, instead of PreviewKeyDown, so cells can implement custom Key/CommandBindings, or other key-based behavior without the DataGrid handling it in the PreviewKeyDown event.
// for ex: TickUpDown cells can do things like TickUp/Down when Ctrl+Up/Down arrow-key is pressed.
void on_key_down(object sender, KeyEventArgs e) {
switch (e.Key) {
case Key.Up: NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Up); break;
case Key.Down: NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Down); break;
default: return;
}
e.Handled = true;
}
void on_selection_changed(object sender, SelectionChangedEventArgs e) {
_commit(e.RemovedItems?.Cast<object>()?.SafeSingleOrDefault());
_commit(e.AddedItems?.Cast<object>()?.SafeSingleOrDefault());
void _commit(object row_item) {
if (grid.TryGetRowView(row_item, out var row_view) && row_view.IsEditing)
grid.CommitEdit();
}
}
void on_preparing_cell_for_edit(object sender, DataGridPreparingCellForEditEventArgs e) {
if (e.EditingElement is ContentPresenter presenter) {
OneOf.Match(get<TextBox>, get<ComboBox>, get<QuantityMeasureControl>, get<ToggleButton>, text_box, combo_box, quantity_measure_control, toggle_button);
T get<T>() => presenter.descendants_of_type<T>().FirstOrDefault();
// TextBox
void text_box(TextBox tb) => Keyboard.Focus(tb);
// ComboBox
void combo_box(ComboBox cb) { }
// QuantityMeasureControl
async void quantity_measure_control(QuantityMeasureControl qmc) {
if (!focus_text()) {
await qmc.LoadedAsync();
focus_text();
}
bool focus_text() =>
qmc.try_find_descendent_of_type(out TextBox tb) && Keyboard.Focus(tb) == tb;
}
// ToggleButton
void toggle_button(ToggleButton tb) {
if (Mouse.LeftButton == MouseButtonState.Pressed && !tb.IsLoaded) {
// this is a hack to toggle IsChecked when the user clicks a checkbox cell that is not already in edit mode.
IDisposable dispose = null;
dispose = Disposable.Create(
tb.SubscribePreviewMouseUp(on_mouse_up),
tb.SubscribeMouseLeave(on_mouse_leave));
void on_mouse_up(object s, MouseButtonEventArgs mbe) {
if (tb.IsMouseOver)
tb.SetCurrentValue(ToggleButton.IsCheckedProperty, !tb.IsChecked);
Disposable.dispose(ref dispose);
}
void on_mouse_leave(object s, MouseEventArgs me) =>
Disposable.dispose(ref dispose);
}
}
}
e.EditingElement.Focus();
}
void on_cell_edit_ending(object sender, DataGridCellEditEndingEventArgs e) {
if (e.EditingElement is ContentPresenter presenter) {
switch (e.EditAction) {
case DataGridEditAction.Cancel: OneOf.Match(get<TextBox>, get<ComboBox>, get<ToggleButton>, cancel_text_box, cancel_combo_box, cancel_toggle_button); break;
case DataGridEditAction.Commit: OneOf.Match(get<TextBox>, get<ComboBox>, get<ToggleButton>, commit_text_box, commit_combo_box, commit_toggle_button); break;
}
T get<T>() => presenter.descendants_of_type<T>().FirstOrDefault();
// TextBox
void commit_text_box(TextBox tb) => tb.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
void cancel_text_box(TextBox tb) => tb.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
// ComboBox
void commit_combo_box(ComboBox cb) { }
void cancel_combo_box(ComboBox cb) { }
// ToggleButton
void commit_toggle_button(ToggleButton tb) { }
void cancel_toggle_button(ToggleButton tb) { }
}
}
void on_got_focus(object sender, RoutedEventArgs e) {
if (e.OriginalSource is DataGridCell cell && cell.try_find_ancestor_of_type(out DataGridRow row)) {
if (row.IsEditing) {
if (!cell.IsEditing) {
grid.CancelEdit(DataGridEditingUnit.Row);
if (try_set_current())
grid.BeginEdit(e);
}
} else {
try_set_current();
if (!row.IsEditing)
grid.BeginEdit(e);
}
bool try_set_current() {
grid.SetCurrentValue(DataGrid.CurrentItemProperty, row.Item);
if (grid.TryGetCurrentRowView(out var row_view)) {
row_view.SetCurrentValue(DataGridRow.IsSelectedProperty, true);
grid.SetCurrentValue(DataGrid.CurrentCellProperty, new DataGridCellInfo(row.Item, grid.Columns[1]));
return true;
} else return false;
}
}
}
void on_commit_edit_requested() {
if (grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing)
commit_edit();
}
void commit_edit() {
using (grid.SuspendGotFocus(on_got_focus))
grid.CommitEdit(DataGridEditingUnit.Row, true);
}
void on_cancel_edit_requested() =>
cancel_edit();
bool cancel_edit() {
if (grid.CurrentCell != null && grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing) {
using (grid.SuspendGotFocus(on_got_focus))
grid.CancelEdit(DataGridEditingUnit.Row);
return true;
} else return false;
}
}
}
}
}
@@ -110,7 +110,6 @@
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
IsTabStop="False"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
@@ -1,7 +1,6 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using bts.utils;
using mbts.collections;
@@ -15,6 +14,7 @@ using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
@@ -34,7 +34,7 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
readonly OrdersModel _model;
readonly OperableCollection<Row, FillTuple, string> _rows;
readonly RXOperableCollection<Row, FillTuple, string> _rows;
readonly DIYGridSortManager<Columns> _sort_manager;
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
@@ -87,7 +87,7 @@ namespace mbts.spark {
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
/// Rows
_rows = new OperableCollection<Row, FillTuple, string>(
_rows = new RXOperableCollection<Row, FillTuple, string>(
(ft, fk) => new Row(ft, model),
ft => ft.fill.FillKey,
new ComparerBuilder<FillTuple>(new[] {
@@ -1,8 +1,6 @@
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
namespace mbts.spark {
/// <summary>
@@ -11,7 +9,6 @@ namespace mbts.spark {
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
bool _disposed;
FillsVM _value;
IDisposable _columns_duplex;
public event Action<bool> OnDisposed;
@@ -21,23 +18,20 @@ namespace mbts.spark {
if (value != _value) {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
Disposable.dispose(ref _columns_duplex);
Disposable.replace(ref _value, value);
if (_value != null) {
columns.Clear();
rows.clear_collections();
}
Disposable.dispose(ref _value);
_value = value;
if (_value != null) {
_columns_duplex = ObservableAutoWrapper.connect_two_way(_value.columns, columns);
rows.add_collection(_value.rows);
columns.add_collection(_value.columns);
} else {
rows.clear_collections();
columns.clear_collections();
}
}
}
}
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public ObservableAggregateCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableAggregateCollection<DIYGrid.ColumnBase>();
public void Dispose() {
if (_disposed)
@@ -28,7 +28,7 @@ namespace mbts.spark {
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
: base("Fills", bus, factory, SparkCommands.ShowFills.icon) {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
@@ -69,7 +69,6 @@ namespace bts.shell {
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default() =>
new StartupData(null, null, new FilterStates(), null);
@@ -83,7 +82,7 @@ namespace bts.shell {
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
@@ -94,7 +93,7 @@ namespace bts.shell {
yield return new SortingCriteria(fill_time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
@@ -113,34 +112,8 @@ namespace bts.shell {
yield return security_key;
}
}
public static class Mini {
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
new Dictionary<Columns, (bool is_visible, double? width_override)> {
{ Columns.Security, (true, null) },
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
{ Columns.Side, (true, ShortColumnWidths.Side) },
{ Columns.Size, (true, ShortColumnWidths.Size) },
{ Columns.Price, (true, ShortColumnWidths.Price) },
};
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
return true;
data = default;
return false;
}
static GridColumnSettings _convert(GridColumnSettings default_col) =>
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
default_col.with(is_visible: d.is_visible, width: d.width_override) :
default_col.with(is_visible: false);
}
}
}
[ProtoContract]
@@ -1,14 +0,0 @@
using mbts.ui;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace mbts.ui.spark {
static class LadderViewHelper {
public static readonly DragDeltaEventHandler ColumnHeaderThumb_DragDeltaHandler =
(s, e) =>
e.Handled =
s is Thumb thumb &&
thumb.try_find_ancestor_of_type(out Canvas canvas) &&
canvas.ActualWidth + e.HorizontalChange < canvas.MinWidth;
}
}
@@ -1,117 +0,0 @@
<UserControl
x:Class="mbts.ui.spark.MiniLadderView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
Background="{x:Static u:BTSColors.common_background_brush}"
TextElement.FontSize="{x:Static u:FontSizes.Small}"
>
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Background" Value="{u:BTSColor header}"/>
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
<Setter Property="BorderBrush" Value="{u:BTSColor header_border}"/>
<Setter Property="BorderThickness" Value="0,0,1,0"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="23" />
<Setter Property="MinWidth" Value="30" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
<Grid SnapsToDevicePixels="True">
<Border x:Name="HeaderBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="7"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Padding="{TemplateBinding Padding}" Grid.RowSpan="2">
<ContentPresenter x:Name="HeaderContent"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="0,0,0,1"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</Grid>
</Border>
<Canvas MinWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MinWidth, Mode=OneTime}">
<Thumb x:Name="PART_HeaderGripper" DragDelta="{x:Static usp:LadderViewHelper.ColumnHeaderThumb_DragDeltaHandler}">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="Canvas.Right" Value="-9"/>
<Setter Property="Width" Value="18"/>
<Setter Property="Height" Value="{Binding ActualHeight, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="Transparent" Padding="{TemplateBinding Padding}">
<Rectangle Fill="{TemplateBinding Background}" HorizontalAlignment="Center" Width="2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{u:BTSColor header_text}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Canvas>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<sp:LadderGridHost
x:Name="ladder_grid_host"
Command="{Binding send_order_command}"
Orders="{Binding orders}"
orders_column_width="{Binding Settings.orders_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
bid_column_width="{Binding Settings.orders_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
price_column_width="{Binding Settings.price_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
ask_column_width="{Binding Settings.ask_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
LastColumnWidth="{Binding Settings.last_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
VolumeColumnWidth="{Binding Settings.volume_column.general.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
CanUserSendOrders="{Binding CanUserSendOrders, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
IsTabStop="False"
>
<sp:LadderGridHost.Resources>
<Style TargetType="c:Badge">
<Setter Property="Background" Value="{Binding background}"/>
<Setter Property="BorderBrush" Value="{Binding border}"/>
<Setter Property="FontFamily" Value="{Binding font_family}"/>
<Setter Property="FontSize" Value="{Binding font_size}"/>
<Setter Property="Foreground" Value="{Binding foreground}"/>
<Setter Property="TextLines" Value="{Binding text_lines}"/>
<Setter Property="Width" Value="{x:Static c:Badge.DefaultWidth}"/>
<Setter Property="HasMultipleOrdersAtLevel" Value="{Binding has_multiple_orders_at_same_level}"/>
<Setter Property="IsHedgeOrder" Value="{Binding is_hedge_order}"/>
<Setter Property="AlgoType" Value="{Binding algo_type}"/>
<Setter Property="Abbreviation" Value="{Binding algo_studio_abbreviation}" />
<Setter Property="Cursor" Value="Hand"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding hoverbackground}"/>
</Trigger>
</Style.Triggers>
</Style>
</sp:LadderGridHost.Resources>
</sp:LadderGridHost>
</AdornerDecorator>
</UserControl>
@@ -1,29 +0,0 @@
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui.spark {
partial class MiniLadderView : UserControl {
public MiniLadderView() =>
InitializeComponent();
#region Settings
public static readonly DependencyProperty SettingsProperty =
WPFHelper.create_dp<mbts.spark.LadderSettings, MiniLadderView>(nameof(Settings));
public mbts.spark.LadderSettings Settings {
get => GetValue(SettingsProperty) as mbts.spark.LadderSettings;
set => SetValue(SettingsProperty, value);
}
#endregion
#region CanUserSendOrders
public static readonly DependencyProperty CanUserSendOrdersProperty =
WPFHelper.create_dp<bool, MiniLadderView>(nameof(CanUserSendOrders));
public bool CanUserSendOrders {
get => (bool)GetValue(CanUserSendOrdersProperty);
set => SetValue(CanUserSendOrdersProperty, value);
}
#endregion
}
}
@@ -23,14 +23,16 @@ namespace bts.shell {
readonly IWindowManager _wm;
readonly UserSettingsHub _settings_hub;
readonly OrdersModel _orders_model;
readonly MarketDataAuxiliaryModel _md_auxiliary_model;
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model)
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model, MarketDataAuxiliaryModel md_auxiliary_model)
: base(owner, "SparkLadder") {
_bus = bus;
_wm = wm;
_settings_hub = settings_hub;
_orders_model = orders_model;
_md_auxiliary_model = md_auxiliary_model;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
@@ -54,16 +56,15 @@ namespace bts.shell {
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
var view = new Lazy<UIElement>(() => {
var model = new LadderModel(md, _orders_model);
var model = new LadderModel(md, _orders_model, _md_auxiliary_model);
var settings_manager = new WindowSettingsManager<LadderSettings>(
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
us => sanitize_ladder_settings(us.ladder),
(us, v) => us.with(sanitize_ladder_settings(v)));
var init = state_settings ?? settings_manager.default_settings;
init.use_defaults |= state_settings == null;
var vm = LadderVM.create(_bus, model, settings_manager, init);
var vm = new LadderVM(_bus, model, settings_manager, init);
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
var lv = new LadderView(vm);
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(vm.model.order_gateway.algo_model.ui_configuration))
lv.Resources[kv.Key] = kv.Value;
@@ -138,13 +139,12 @@ namespace bts.shell {
readonly struct LadderConfig {
public readonly uint max_open_ladders;
public readonly bool ladders_on_threads, open_algo_popup_on_select, use_algo_param_grid;
public readonly bool ladders_on_threads, open_algo_popup_on_select;
public readonly TimeSpan security_timeout;
public LadderConfig(bts.config.LadderKnobs knobs, bool ladders_on_threads) {
max_open_ladders = knobs.max_open_ladders;
open_algo_popup_on_select = knobs.open_algo_popup_on_select;
use_algo_param_grid = knobs.use_algo_param_grid;
security_timeout = TimeSpan.FromSeconds(Math.Max(15, knobs.security_timeout_secs));
this.ladders_on_threads = ladders_on_threads;
}
@@ -6,7 +6,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:uc="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
@@ -22,7 +22,6 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
</ResourceDictionary.MergedDictionaries>
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
@@ -50,56 +49,16 @@
<Style x:Key="{x:Static sp:MiniOrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<!-- Completed order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Completed, Type=order:OrderStatus}"/>
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=completed_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Error, RejectedBySpark, Disconnected order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Error, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum RejectedBySpark, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Disconnected, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Cancel order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Canceled, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=canceled_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="{x:Static sp:MiniOrderBookView.RowTemplateKey}">
<sp:MiniOrderBookRowView/>
@@ -123,8 +82,7 @@
RowTemplate="{StaticResource {x:Static sp:MiniOrderBookView.RowTemplateKey}}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
RowsHorizontalScrollBarVisibility="Collapsed"
RowHeight="20"
SelectionMode="Single"
RowHeight="20"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
@@ -191,8 +149,9 @@
</ResourceDictionary>
</c:DIYGrid.Resources>
<c:DIYGrid.InputBindings>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
</c:DIYGrid.InputBindings>
<!--<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/> -->
</c:DIYGrid.InputBindings>
<c:DIYGrid.CommandBindings>
<CommandBinding
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
@@ -1,12 +1,11 @@
using mbts.ui.controls;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.spark {
internal partial class MiniOrderBookView : UserControl {
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice, Attach }
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
@@ -22,9 +21,6 @@ namespace mbts.spark {
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public MiniOrderBookRowView() : base() {
FocusManager.SetIsFocusScope(this, true);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
public MiniOrderBookRowView() : base() { }
}
}
@@ -1,16 +1,13 @@
using bts;
using bts.api;
using bts.api.orderman;
using bts.shell.orderbook;
using bts.utils;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.ui.utility.text;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -23,10 +20,10 @@ using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using static bts.shell.OrderBookWindowFactory;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static mbts.ui.controls.DIYGrid;
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
using System.Windows.Input;
namespace mbts.spark {
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
@@ -201,7 +198,7 @@ namespace mbts.spark {
"Price",
r => r.price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new DoubleMinMax<OrderSecurityAlgoMeta>(
ort => ort.order.price(),
nameof(OrderBookRowVM.price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -210,14 +207,13 @@ namespace mbts.spark {
// not usually part of the main order book, but it's useful to add it
// rather than debugging the popup window
//Add(edit_price_column = new OrderBookVM.EditablePriceColumn());
//Add(new OrderBookVM.AttachColumn("DataContext.attach_vm"));
// avg filled price
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.AvgFilled,
"Avg Price",
r => r.avg_price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new DoubleMinMax<OrderSecurityAlgoMeta>(
ort => ort.order.avg_price(),
nameof(OrderBookRowVM.avg_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -228,7 +224,7 @@ namespace mbts.spark {
"Last Filled",
r => r.last_price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new DoubleMinMax<OrderSecurityAlgoMeta>(
ort => ort.order.last_price(),
nameof(OrderBookRowVM.last_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -298,6 +294,7 @@ namespace mbts.spark {
public event Action filter_changed;
public CancelColumn cancel_column { get; }
public SecurityColumn security_column { get; }
public SimpleTextColumn<string[]> symbol_column { get; }
public SimpleTextColumn<TimeFilterTuple> time_created_column { get; }
@@ -312,6 +309,7 @@ namespace mbts.spark {
public SizeColumn qty_column { get; }
public EditableQtyColumn edit_qty_column { get; }
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
public EditablePriceColumn edit_price_column { get; }
public FilledColumn filled_qty_column { get; }
public SimpleTextColumn<DoubleMinMaxState> last_price_column { get; }
public SimpleTextColumn<DoubleMinMaxState> avg_price_column { get; }
@@ -322,7 +320,7 @@ namespace mbts.spark {
public SimpleTextColumn<string> parent_key_column { get; }
public SimpleTextColumn<string> attached_target_order_key_column { get; }
public FilterStates create_filter_states() =>
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
new FilterStates {
order_key_regex = order_key_column.filter.get_state(),
parent_key_regex = parent_key_column.filter.get_state(),
@@ -345,10 +343,8 @@ namespace mbts.spark {
last_change = last_change_column.filter.get_state(),
security_criteria = security_column.filter.get_state(),
symbols = symbol_column.filter.get_state()
};
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() =>
(create_filter_states(), get_sorting_data().sorting_criterion);
},
get_sorting_data().sorting_criterion);
public void load_from((FilterStates filters, IReadOnlyList<SortingCriteria> sorting) state) {
if (state.filters is FilterStates fs) {
@@ -478,16 +474,6 @@ namespace mbts.spark {
static IComparer<OrderBookRowVM> _wrap_comparer<T>(Func<OrderBookRowVM, T> get, IComparer<T> comparer) =>
comparer.wrap(get);
static KeyBinding _create_keyinput_binding(Key k, string command_path) {
var key_binding = new KeyBinding { Key = k };
BindingOperations.SetBinding(
key_binding,
InputBinding.CommandProperty,
new Binding { Path = new PropertyPath(command_path) }
);
return key_binding;
}
public abstract class OrderbookColumnBase<T> : ColumnBase<Columns>, IKeyedColumn<Columns>, IFilterableColumn, ICVOrderBookColumn {
ListSortDirection? _cached_sort_direction;
readonly string _filter_template;
@@ -651,14 +637,19 @@ namespace mbts.spark {
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
//
var grid = new Grid { VerticalAlignment = VerticalAlignment.Center };
var grid = new Grid();
Grid.SetColumn(security_view, 1);
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.Children.Add(toggle_button);
grid.Children.Add(security_view);
grid.VerticalAlignment = VerticalAlignment.Center;
//
return new Border { Child = grid, Margin = new Thickness(10, 0, 0, 0) };
var border = new Border { Child = grid };
border.Margin = new Thickness(10, 0, 0, 0);
CellRender.set_background_animation_bindings(border, nameof(OrderBookRowVM.security));
return border;
}
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
@@ -707,8 +698,7 @@ namespace mbts.spark {
Margin = new Thickness(2),
VerticalAlignment = VerticalAlignment.Center,
Height = 14,
Width = 14,
Focusable = false
Width = 14
};
button.SetBinding(Button.CommandProperty, _command_binding);
@@ -716,341 +706,18 @@ namespace mbts.spark {
}
}
public class AttachDetachColumn : FrameworkElementColumnBase<Columns> {
readonly Type _mini_or_full_ob_type;
public AttachDetachColumn(Type mini_or_full_ob_type) : base(Columns.Attach) {
if (!(mini_or_full_ob_type == typeof(MiniOrderBookView) || mini_or_full_ob_type == typeof(OrderBookView))) {
throw new ArgumentException();
}
id = key.ToString();
Header = DefaultColumnHeaders.AttachDetach;
Width = 45;
_mini_or_full_ob_type = mini_or_full_ob_type;
}
// General DataBinding trouble shooting tip:
// This is how one can get extra debug statements during databinding
//PresentationTraceSources.SetTraceLevel(_datacontext_binding, PresentationTraceLevel.High);
public override FrameworkElement view_factory() {
var popup = new System.Windows.Controls.Primitives.Popup();
#region Attach Button
// create the attach button and assign the menu to it
var attach_button = new Button() {
Name = "attach_button",
Content = "A",
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
ToolTip = "Attach"
};
var attach_grid = new Grid() {
Width = 20,
Height = 20,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
attach_grid.Children.Add(attach_button);
attach_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
var orderbook_vm_datacontext_binding = new Binding("DataContext");
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
// setting the button binding, easy, same visual tree as the MiniOrderBookView
// this has button has to be bound to the orderbook_vm b/c of the popup
// that will be assigned to the PlacementTarget and the DataContext will be
// kept in sync between those controls via a PlacementTarget.DataContext binding
attach_button.SetBinding(Button.DataContextProperty, orderbook_vm_datacontext_binding);
var row_vm_attach_to_visibility = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.is_attachable))) { Converter = new mbts.ui.converters.BooleanToVisibilityConverter() };
row_vm_attach_to_visibility.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
attach_button.SetBinding(Button.VisibilityProperty, row_vm_attach_to_visibility);
var row_vm_attach_command_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.attach_command)));
row_vm_attach_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = AlgoEditorGridView.create_attach_view();
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
#region Dialog Skirt
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "attach_border",
Child = grid,
};
#endregion
popup.Child = border;
popup.StaysOpen = false;
popup.PlacementTarget = attach_button;
popup.Placement = PlacementMode.Bottom;
// assign the Popup the DataContext of PlacementTarget, which will always be kept in sync with our parent button
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
#endregion
#region Detach Button
var detach_button = new Button() {
Name = "detach_button",
Content = "D",
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Focusable = false,
ToolTip = "Detach"
};
//var orderbook_vm_datacontext_binding = new Binding(_detach_command_binding_path);
//orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
detach_button.SetBinding(Button.CommandProperty, new Binding(nameof(OrderBookRowVM.detach_command)));
detach_button.SetBinding(Button.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_detachable)) { Converter = new mbts.ui.converters.BooleanToVisibilityConverter() });
var detach_grid = new Grid() {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = 20,
Height = 20,
};
detach_grid.Children.Add(detach_button);
#endregion
var the_whole_thing = new Grid();
the_whole_thing.Children.Add(attach_grid);
the_whole_thing.Children.Add(detach_grid);
the_whole_thing.Children.Add(popup);
return the_whole_thing;
}
}
public class UpdateColumn : FrameworkElementColumnBase<Columns> {
readonly Type _mini_or_full_ob_type;
public UpdateColumn(Type mini_or_full_ob_type) : base(Columns.Update) {
if (!(mini_or_full_ob_type == typeof(MiniOrderBookView) || mini_or_full_ob_type == typeof(OrderBookView))) {
throw new ArgumentException();
}
id = key.ToString();
Header = DefaultColumnHeaders.Update;
Width = 45;
_mini_or_full_ob_type = mini_or_full_ob_type;
}
public override FrameworkElement view_factory() {
var popup = new System.Windows.Controls.Primitives.Popup();
#region Attach Button
// create the attach button and assign the menu to it
var update_button = new Button() {
Name = "update_button",
Content = "···",
Margin = new Thickness(2),
VerticalAlignment = VerticalAlignment.Center,
Focusable = false
};
update_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
var orderbook_vm_datacontext_binding = new Binding("DataContext");
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
// setting the button binding, easy, same visual tree as the MiniOrderBookView
// this has button has to be bound to the orderbook_vm b/c of the popup
// that will be assigned to the PlacementTarget and the DataContext will be
// kept in sync between those controls via a PlacementTarget.DataContext binding
update_button.SetBinding(Button.DataContextProperty, orderbook_vm_datacontext_binding);
var row_vm_update_command_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.update_command)));
row_vm_update_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
update_button.SetBinding(Button.CommandProperty, row_vm_update_command_binding);
#endregion
#region OrderParamsPanel
// finally, set the order_params DataContext to that of the ContextMenu
var order_params = new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = false,
};
;
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
#endregion
#region Dialog Skirt
var dialog_skirt_button_style =
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
Application.Current.TryFindResource("CommonButton") as Style ??
Application.Current.TryFindResource(typeof(Button)) as Style;
// go find the OBVM on the ContextMenu to get the command
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
// and Click event on a Button appropriately, but since the Button that will perform this
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
// we make use of this addition trick from John that allows for both of these things to happen.
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
// pure genious!
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
};
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
// create a KeyedStyleSelector here I guess.
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
var awesome_command_style_key = "SendAttachButtonStyleKey";
var cancel_style_key = "CancelButtonStyleKey";
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
}
};
var kss = new KeyedStyleSelector();
kss.Map.Add(new StyleSelectorKeyValue {
Key = awesome_command_style_key,
Value = send_attached_button_style
});
kss.Map.Add(
new StyleSelectorKeyValue {
Key = cancel_style_key,
Value = new Style(typeof(Button), dialog_skirt_button_style)
});
dialog_skirt.RightItemContainerStyleSelector = kss;
Grid.SetRow(dialog_skirt, 1);
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(order_params);
grid.Children.Add(dialog_skirt);
var border = new Border() {
Background = BTSColors.common_background_brush,
BorderThickness = new Thickness(1),
Name = "update_border",
Child = grid,
};
#endregion
popup.Child = border;
popup.StaysOpen = false;
popup.PlacementTarget = update_button;
popup.Placement = PlacementMode.Bottom;
// assign the Popup the DataContext of PlacementTarget, which will always be kept in sync with our parent button
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
});
var the_whole_thing = new Grid();
the_whole_thing.Children.Add(update_button);
the_whole_thing.Children.Add(popup);
return the_whole_thing;
}
}
public class EditableQtyColumn:FrameworkElementColumnBase<Columns> {
public EditableQtyColumn() : base(Columns.EditableQty) {
id = key.ToString();
Header = "Qty";
Width = 40;
Width = 64;
}
public override FrameworkElement view_factory() {
var tick_up_down = new TickUpDownNullableUIntCommit();
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.SourceValueProperty, nameof(OrderBookRowVM.prospective_quantity));
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Enter, nameof(OrderBookRowVM.modify_order_quantity_andor_price)));
tick_up_down.InputBindings.Add(_create_keyinput_binding(Key.Escape, nameof(OrderBookRowVM.revert_prospective_order_quantity_andor_price)));
tick_up_down.SetBinding(TickUpDownNullableUIntCommit.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity));
return tick_up_down;
}
}
@@ -1060,54 +727,16 @@ namespace mbts.spark {
public EditablePriceColumn() : base(Columns.EditablePrice) {
id = key.ToString();
Header = "Price";
Width = 64;
Width = 96;
}
public override FrameworkElement view_factory() {
var price_up_down = new PriceEntryCommitControl();
price_up_down.SetBinding(PriceEntryCommitControl.SourceValueProperty, nameof(OrderBookRowVM.prospective_price));
price_up_down.SetBinding(PriceEntryCommitControl.SourceContractSpecProperty, nameof(OrderBookRowVM.contract_spec));
price_up_down.SetBinding(PriceEntryCommitControl.OnCommitProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
price_up_down.InputBindings.Add(_create_keyinput_binding(Key.Enter, nameof(OrderBookRowVM.modify_order_quantity_andor_price)));
price_up_down.InputBindings.Add(_create_keyinput_binding(Key.Escape, nameof(OrderBookRowVM.revert_prospective_order_quantity_andor_price)));
price_up_down.SetBinding(PriceEntryCommitControl.OnCommitProperty, nameof(OrderBookRowVM.modify_order_price));
return price_up_down;
}
}
public class CommitColumn : FrameworkElementColumnBase<Columns> {
public CommitColumn() : base(Columns.Commit) {
id = key.ToString();
Header = "";
Width = 20;
}
public override FrameworkElement view_factory() {
var commit_button = new Button() {
Name = "commit_button",
Content = "✔",
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
};
var is_enabled_trigger = new Trigger() { Property = Button.IsEnabledProperty, Value = true };
is_enabled_trigger.Setters.Add(new Setter(Button.BackgroundProperty, new SolidColorBrush(Colors.Green).frozen()));
is_enabled_trigger.Setters.Add(new Setter(Button.ForegroundProperty, new SolidColorBrush(Colors.White).frozen()));
var s = new Style(typeof(Button));
s.Triggers.Add(is_enabled_trigger);
commit_button.Style = s;
commit_button.SetBinding(Button.CommandProperty, nameof(OrderBookRowVM.modify_order_quantity_andor_price));
var grid = new Grid() {
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = 20,
Height = 20,
};
grid.Children.Add(commit_button);
return grid;
}
}
#endregion
@@ -1138,6 +767,5 @@ namespace mbts.spark {
public static Brush get_side_brush(OrderBookRowVM r) => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
}
}
}
@@ -0,0 +1,215 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
class OrderBookRowCollection : ObservableCollection<OrderBookRowVM> {
static readonly IEqualityComparer<INode<OrderBookRowVM>> _node_equality_comparer = EqualityComparer.Create<INode<OrderBookRowVM>, uint>(n => n.value.order_key);
readonly Func<OrderSecurityAlgoMeta, OrderBookRowVM> _row_factory;
/// Critical: Any and all row mutations <see cref="OrderBookRowVM"/> must be executed through <see cref="SortedTree{T, TKey}.TryReplace(TKey, Func{T, T})"/>.
/// Mutations can change the sort-position of a row, and <see cref="_tree"/> will be broken if its elements aren't properly sorted (it relies heavily on binary-search logic).
SortedTree<OrderBookRowVM, uint> _tree;
IComparer<OrderBookRowVM> _comparer;
Func<OrderSecurityAlgoMeta, bool> _filter;
bool _is_refresh_pending;
int _deferment_count = 0;
/// When processing changed-items, the <see cref="SortedTree{T, TKey}.TryReplace(TKey, Func{T, T})"/> method is used to update the changed row. It is not expected to fail (return false),
/// but if it does, we don't want to overwhelm the logs with a bunch of useless noise in case it starts happening repeatedly, hence the int fields below.
/// <see cref="_max_replace_misses_logged"/>, <see cref="_n_replace_misses_logged"/>, <see cref="update(IIncrementalTableUpdateTransform{OrderSecurity, uint})"/>.
const int _max_replace_misses_logged = 10;
int _n_replace_misses_logged;
public OrderBookRowCollection(Func<OrderSecurityAlgoMeta, OrderBookRowVM> row_factory, IComparer<OrderBookRowVM> comparer) {
_row_factory = row_factory;
_tree = _mk_tree(_comparer = comparer);
}
public Func<OrderSecurityAlgoMeta, bool> filter {
get => _filter;
set {
_filter = value;
_refresh_branches(_tree.RootNodes);
reset();
}
}
public IComparer<OrderBookRowVM> comparer {
get => _comparer;
set {
_tree = _tree.Resort(_comparer = value);
reset();
}
}
public int total_row_count { get; private set; }
IEnumerable<OrderBookRowVM> _visible_rows => _tree.Where(r => r.is_visible);
bool _is_refresh_deferred => _deferment_count > 0;
public IEnumerable<INode<OrderBookRowVM>> get_inner_nodes(OrderBookRowVM row) =>
_tree.EnumerateInnerNodes(row);
public IEnumerable<OrderBookRowVM> get_descendent_rows(OrderBookRowVM row) =>
_tree.EnumerateInnerValues(row);
public void collapse_all() {
foreach (var n in _tree.RootNodes) {
collapse_node(n);
}
if (_is_refresh_deferred) {
_is_refresh_pending = true;
} else {
Clear();
foreach (var n in _tree.RootNodes) {
if (n.value.is_visible)
Add(n.value);
}
}
// local methods
void collapse_node(INode<OrderBookRowVM> node) {
node.value.collapse_internal();
foreach (var cn in node.inner) {
cn.value.is_visible = false;
collapse_node(cn);
}
}
}
public void expand_all() {
foreach (var n in _tree.RootNodes) {
expand_node(n);
}
if (_is_refresh_deferred) {
_is_refresh_pending = true;
} else {
Clear();
foreach (var n in _tree.RootNodes) {
if (n.value.is_visible) {
Add(n.value);
foreach (var r in n.inner_values())
Add(r);
}
}
}
// local methods
void expand_node(INode<OrderBookRowVM> node) {
node.value.expand_internal();
foreach (var cn in node.inner) {
cn.value.is_visible = node.value.is_visible;
expand_node(cn);
}
}
}
/// <summary>
/// Refreshes the collection by calling <see cref="GlobalExtensions.sync_with{T}(IList{T}, IEnumerable{T})"/> with all visible rows.
/// </summary>
public void refresh() {
if (_is_refresh_deferred) {
_is_refresh_pending = true;
} else {
_is_refresh_pending = false;
this.sync_with(_visible_rows);
}
}
/// <summary>
/// Resets the collection by calling <see cref="Collection{T}.Clear"/>, then adding all visible rows.
/// </summary>
public void reset(bool force = false) {
if (_is_refresh_deferred && !force) {
_is_refresh_pending = true;
} else {
Clear();
this.AddRange(_visible_rows);
_is_refresh_pending = false;
}
}
public IDisposable defer_refresh(bool force_refresh_after = false) {
// use a counter instead of a simple bool flag to gracefully handle recursive calls to defer_refresh(),
// by only calling refresh() after all IDisposables returned by defer_refresh() have been disposed.
_deferment_count++;
_is_refresh_pending |= force_refresh_after;
return Disposable.Create(() => {
_deferment_count--;
if (_is_refresh_pending)
refresh();
});
}
public void update(TableUpdate<OrderSecurityAlgoMeta, uint> table) {
IEnumerable<INode<OrderBookRowVM>> impacted_branch_roots;
int n_removed = table.removed.Count();
if (n_removed == total_row_count) {
// full refresh (most likely this is the first update or a server restart)
_tree = _mk_tree(_comparer, table.added.Select(x => _row_factory(x)));
impacted_branch_roots = _tree.RootNodes;
} else {
foreach (OrderSecurityAlgoMeta ort in table.removed)
_tree.Remove(ort.order.Key);
foreach (var change in table.changes) {
if (!_tree.TryReplace(change.New.order.Key, r => { r.update(change.New); return r; })) {
// uh oh, replace failed! Let's log this, but not a million times (this seems like the kind of error that, if it happens once, could happen a lot).
if (_n_replace_misses_logged++ < _max_replace_misses_logged)
Log.Ui.error($"{nameof(OrderBookRowCollection)} failed to process Changed {nameof(OrderSecurity)} in update (old:{change.Old.order.Key}, new:{change.New.order.Key}). This is the '{_n_replace_misses_logged}' time this has happened in this OrderBook.");
}
}
foreach (OrderSecurityAlgoMeta ort in table.added)
_tree.Insert(_row_factory(ort));
impacted_branch_roots =
table.added.Concat(table.changed).Select(ort => ort.order.Key).Select(_tree.GetRootNode)
.Concat(
table.removed
.Where(ort => ort.order.has_parent())
.Select(ort => ort.order.ParentKey)
.Distinct() // since these are ParentKeys, there could be duplicates.
.Select(_tree.GetRootNode)
.Where(n => n != null)
).Distinct(_node_equality_comparer);
}
_refresh_branches(impacted_branch_roots);
refresh();
int old_row_count = total_row_count;
total_row_count += table.added.Count() - n_removed;
if (old_row_count != total_row_count)
OnPropertyChanged(new PropertyChangedEventArgs(nameof(total_row_count)));
}
static SortedTree<OrderBookRowVM, uint> _mk_tree(IComparer<OrderBookRowVM> comp, IEnumerable<OrderBookRowVM> init = null) =>
new SortedTree<OrderBookRowVM, uint>(comp, r => r.order_key, r => r.parent_order_key, init);
void _refresh_branches(IEnumerable<INode<OrderBookRowVM>> roots) {
if (filter == null) {
foreach (var n in roots)
refresh_tree_properties(0, true, n);
} else {
foreach (var n in roots)
refresh_tree_properties(0, get_branch_passes_filter(n), n);
}
// local methods
void refresh_tree_properties(int depth_level, bool is_visible, INode<OrderBookRowVM> node) {
node.value.depth_level = depth_level;
node.value.is_visible = is_visible;
using (var en = node.inner.GetEnumerator()) {
if (en.MoveNext()) {
node.value.is_parent = true;
depth_level++;
do refresh_tree_properties(depth_level, node.value.is_visible && node.value.is_expanded, en.Current);
while (en.MoveNext());
}
}
}
bool get_branch_passes_filter(INode<OrderBookRowVM> n) =>
filter(n.value.order_tuple) || n.inner.Any(get_branch_passes_filter);
}
}
}
@@ -1,26 +1,27 @@
using bts;
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Reactive;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using System.Reactive.Linq;
namespace mbts.spark {
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityAlgoMeta>, INotifyPropertyChanged {
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM {
const string _unknown_security_price_format_string = "0.#########";
const int _unknown_security_price_num_fractional_digits = 9;
readonly OrderBookVM _owner;
Brush _badge_fill;
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _modify_order_quantity, _modify_order_price;
bool _is_parent, _is_expanded, _is_selected;
int _depth_level;
SecurityImmutable? _security => order_tuple.security;
@@ -41,7 +42,8 @@ namespace mbts.spark {
}
private void OrderBookRowVM_invalidated() {
modify_order_quantity_andor_price?.TryRaiseCanExecuteChanged();
modify_order_quantity?.TryRaiseCanExecuteChanged();
modify_order_price?.TryRaiseCanExecuteChanged();
}
//// This takes care of the case where the OB has all rows visible
@@ -57,9 +59,13 @@ namespace mbts.spark {
cb => invalidated -= cb);
}
protected override void Dispose(bool disposing) {
this.invalidated -= OrderBookRowVM_invalidated;
base.Dispose(disposing);
}
public event Action invalidated;
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
public event PropertyChangedEventHandler PropertyChanged;
// Core
public OrderSecurityAlgoMeta order_tuple { get; private set; }
@@ -98,11 +104,6 @@ namespace mbts.spark {
public uint quantity => order.quantity();
public uint filled_quantity => order.filled();
public string last_msg => order.last_msg();
public bool is_non_terminal => order.is_non_terminal();
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
// Tree
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
public bool is_child => order.has_parent();
@@ -146,152 +147,92 @@ namespace mbts.spark {
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
public ICommand cancel_command => _cancel_cmd ??
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working || status == OrderStatus.Disconnected));
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working));
public ICommand modify_order_quantity_andor_price => _modify_order_quantity_andor_price ??
(_modify_order_quantity_andor_price = new DelegateCommand(
() => {
if (is_qty_dirty && is_price_dirty) {
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
} else if (is_qty_dirty) {
_owner.modify_order_quantity(this, prospective_quantity);
} else if (is_price_dirty) {
_owner.modify_order_price(this, this.prospective_price);
}
public ICommand modify_order_quantity => _modify_order_quantity ??
(_modify_order_quantity = new DelegateCommand(
(desired_quantity) => {
_owner.modify_order_quantity(this, (uint)desired_quantity);
_unselect_me();
},
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
)
},
(desired_quantity) =>
(desired_quantity != null && this.status == OrderStatus.Working && (uint)desired_quantity != this.quantity)
|| is_qty_dirty
)
);
public ICommand modify_order_price => _modify_order_price ??
(_modify_order_price = new DelegateCommand(
(desired_price) => {
_owner.modify_order_price(this, (double)desired_price);
_unselect_me();
},
(desired_price) =>
(desired_price != null && this.status == OrderStatus.Working && (double)desired_price != this.price)
|| is_price_dirty
)
);
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
() => {
prospective_price = price;
prospective_quantity = quantity;
},
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
)
);
public ICommand detach_command => _detach_command ??
(_detach_command = new DelegateCommand(() => {
_owner.detach_orders(this);
})
);
public ICommand attach_command => _attach_command ?? (
_attach_command = new DelegateCommand(() => {
// the _select me will trigger the OBVM.attach_vm to get primed
_select_me();
_owner.begin_attach_algos();
})
);
public ICommand update_command => _update_command ?? (
_update_command = new DelegateCommand(() => {
_select_me();
})
);
#region IHierarchicalRow
bool IHierarchicalRow.is_parent => is_parent;
bool IHierarchicalRow.is_expanded => is_expanded;
int IHierarchicalRow.depth_level => depth_level;
OrderSecurityAlgoMeta IHierarchicalRow<OrderSecurityAlgoMeta>.value => order_tuple;
void IHierarchicalRow.on_is_parent_changed(bool is_parent) => this.is_parent = is_parent;
/// Do NOT actually call the <see cref="is_expanded"/> setter (it has side effects) - the row collection calls this method when the row is expanded/collapsed by some **external means, outside of this class.
/// It should not trigger any functional response beyond modifying the value returned by <see cref="is_expanded"/>.
/// **external means are:
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.expand_all"/>,
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.expand_row(TKey)"/>
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_all"/>
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_row(TKey)"/>
void IHierarchicalRow.on_is_expanded_changed(bool is_expanded) => Set(ref _is_expanded, is_expanded, nameof(is_expanded));
void IHierarchicalRow.on_depth_level_changed(int depth_level) => this.depth_level = depth_level;
void IHierarchicalRow<OrderSecurityAlgoMeta>.on_value_changed(OrderSecurityAlgoMeta value) => update(value);
#endregion
private void _unselect_me() {
this.is_selected = false;
_owner.current_row = null;
}
private void _select_me() {
this.is_selected = true;
_owner.current_row = this;
}
// These fns present a way for the orderbook to avoid noisy, intermittent collection changes during "Expand/Collapse All" ops,
// when incremental changes are ignored in favor of a full reset, after 'is_expanded' is updated for all rows.
internal void expand_internal() => Set(ref _is_expanded, true, nameof(is_expanded));
internal void collapse_internal() => Set(ref _is_expanded, false, nameof(is_expanded));
internal void expand_internal() => Set(ref _is_expanded, true);
internal void collapse_internal() => Set(ref _is_expanded, false);
public void update(OrderSecurityAlgoMeta order_tuple) {
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
bool had_security = _security.HasValue;
var old_order_tuple = this.order_tuple;
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
var old_order_tuple = this.order_tuple;
var dirty = ViewRefresh.get_dirty_cells(old_order_tuple, order_tuple);
OrderRecordImmutable old_order = old_order_tuple.order;
this.order_tuple = order_tuple;
// order type
if ((Columns.OrderType & dirty) != 0)
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
bool invalidate = !(
old_order.last_change() == order.last_change() &&
old_order.filled() == order.filled() &&
old_order.last_price() == order.last_price() &&
old_order.avg_price().Equals(order.avg_price()) &&
old_order.price().Equals(order.price()) &&
old_order.quantity() == order.quantity() &&
old_order.last_msg() == order.last_msg());
// however, the order-type and security cells DO use databindings - if these cells have changed, the PropertyChanged event must be raised regardless of whether or not invalidated is raised
if (old_order.order_type() != order.order_type())
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
// quantity
if ((Columns.Size & dirty) != 0)
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
// via the Mini OB update column mechanism (vs the EditableQty column)
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
// row will no longer match the filter
prospective_quantity = quantity;
// price
if ((Columns.Price & dirty) != 0) {
if (old_order.quantity() != order.quantity()) {
RaisePropertyChanged(nameof(quantity));
RaisePropertyChanged(nameof(is_qty_dirty));
}
if (old_order.price() != order.price()) {
RaisePropertyChanged(nameof(price));
RaisePropertyChanged(nameof(is_price_dirty));
}
// security
if ((Columns.Security & dirty) != 0)
if (!had_security && _security.HasValue) {
RaisePropertyChanged(nameof(security), nameof(security_text));
// badge fill
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
invalidate = true;
}
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
// invalidate by setting to null (will be re-instantiated in the public getter)
_badge_fill = null;
RaisePropertyChanged(nameof(badge_fill));
}
// display name
if (old_order_tuple.display_name() != order_tuple.display_name())
RaisePropertyChanged(nameof(display_name));
// status
if ((Columns.Status & dirty) != 0)
if (old_order.status() != order.status()) {
invalidate = true;
RaisePropertyChanged(nameof(status));
// last message
if ((Columns.LastMessage & dirty) != 0)
}
if (old_order.last_msg() != order.last_msg()) {
RaisePropertyChanged(nameof(last_msg));
// filled status desc
if ((ViewRefresh.filled_status_desc_mask & dirty) != 0)
RaisePropertyChanged(nameof(filled_status_desc));
// show order modify cmd
}
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
_show_order_modify_cmd.RaiseCanExecuteChanged();
// always raise updated
updated?.Invoke((old_order_tuple, order_tuple));
// terminal state
if (old_order_tuple.order.is_non_terminal() != order_tuple.order.is_non_terminal()) {
// while we call refresh_is_at/detachable in the OrderBookRowCollection.update
// on all the children of a modified row, we used to not call it on the row istelf,
// which should of course be done here. This manifested via the very top parent in any
// length of attachable orders *still* showing an attach icon after some descendant was
// detached
RaisePropertyChanged(nameof(is_non_terminal), nameof(is_attachable), nameof(is_detachable));
// this will update the cancel column in the mini ob
cancel_command.TryRaiseCanExecuteChanged();
}
// always refresh attach and detach b/c
// conditionally raise invalidated
if ((dirty & ViewRefresh.invalidate_mask) != 0)
if (invalidate)
raise_invalidated();
}
@@ -313,74 +254,5 @@ namespace mbts.spark {
string _fmt_price(double v) => security.HasValue ?
security.Value.ContractSpec.format(v) :
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
void RaisePropertyChanged(params string[] props) {
var pc = PropertyChanged;
if (pc == null)
return;
for (int i = 0; i < props.Length; i++)
pc(this, new PropertyChangedEventArgs(props[i]));
}
bool Set<T>(ref T backing, T newval, [CallerMemberName]string prop = "") where T : struct, IEquatable<T> {
if (!backing.Equals(newval)) {
backing = newval;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
return true;
} else return false;
}
bool SetVisual<T>(ref T backing, T newval, [CallerMemberName]string prop = "") where T : struct, IEquatable<T> {
if (Set(ref backing, newval, prop)) {
raise_invalidated();
return true;
} else return false;
}
static class ViewRefresh {
public const Columns
invalidate_mask =
Columns.Security |
Columns.LastChange |
Columns.Filled |
Columns.LastPrice |
Columns.AvgFilled |
Columns.Price |
Columns.Size |
Columns.Status |
Columns.ParentKey |
Columns.AttachedTargetOrderKey |
Columns.LastMessage,
filled_status_desc_mask =
Columns.Filled |
Columns.AvgFilled |
Columns.Status;
public static Columns get_dirty_cells(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) {
var dirty = (Columns)0;
if (oldval.security != newval.security)
dirty |= Columns.Security;
if (oldval.order.last_change() != newval.order.last_change())
dirty |= Columns.LastChange;
if (oldval.order.filled() != newval.order.filled())
dirty |= Columns.Filled;
if (oldval.order.last_price() != newval.order.last_price())
dirty |= Columns.LastPrice;
if (oldval.order.avg_price() != newval.order.avg_price())
dirty |= Columns.AvgFilled;
if (oldval.order.price() != newval.order.price())
dirty |= Columns.Price;
if (oldval.order.quantity() != newval.order.quantity())
dirty |= Columns.Size;
if (oldval.order.ParentKey != newval.order.ParentKey)
dirty |= Columns.ParentKey;
if (oldval.order.attached_target_order_key() != newval.order.attached_target_order_key())
dirty |= Columns.AttachedTargetOrderKey;
if (oldval.order.status() != newval.order.status())
dirty |= Columns.Status;
if (oldval.order.last_msg() != newval.order.last_msg())
dirty |= Columns.LastMessage;
return dirty;
}
}
}
}
@@ -1,12 +1,17 @@
using bts.api.orderman;
using bts.collections;
using bts.spark;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using mbts.collections.extensions;
using bts.collections;
using bts.api;
namespace mbts.spark.tests {
namespace mbts.spark.tests
{
static internal class MockOrderRecords {
public static OrderRecordImmutable mock_order(uint key, DateTime d, uint parent_key = 0) {
OrderRecord o = new OrderRecord();
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,6 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.orderbook;
using bts.spark;
using bts.view;
using mbts.persist;
@@ -19,15 +18,16 @@ using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.orderbook.OrderBookGridSettings>;
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
namespace mbts.spark {
internal class OrderBookWindow : SparkWindow<OrderBookStartupData>, ISharedWindow {
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
const string _window_type = "OrderBook", _default_title = "Order Book";
readonly INotificationService _notification_service;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
IDisposable _attachment_exit_sub;
OrderBookStartupData _state;
OrderBookWindowFactory.OrderBookStartupData _state;
OrdersModel _model;
OrderBookVM _vm;
OrderBookView _view;
@@ -35,7 +35,7 @@ namespace mbts.spark {
SparkCommand[] _commands;
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
_model = model;
_factory = factory;
@@ -45,13 +45,13 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
}
public override OrderBookStartupData SaveState => _state = _vm?.create();
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm?.create();
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override double MinWidth =>
OrderBookStartupData.DefaultGrid.general.columns.Sum(gcs => gcs.width)
OrderBookWindowFactory.OrderBookStartupData.DefaultGrid.general.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
@@ -68,16 +68,13 @@ namespace mbts.spark {
_vm.load_from(_state);
_view = new OrderBookView(_vm);
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
var show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
var toggle_show_right_panel_command = new SparkCommandCommand(
get_toggle_show_right_panel_cmd_title(),
var toggle_right_panel_is_visible_command = new SparkCommandCommand(
get_toggle_algo_editor_cmd_title(),
Icons.ModifyOrder,
_vm.toggle_show_right_panel_cmd,
OrderBookView.KeyboardShortcuts.ToggleShowRightPanel) {
group = CommandGroups.Modify,
show_in_toolbar = false
};
_vm.toggle_algo_editor_is_visible_cmd,
new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift));
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Buy).Select(o => o.order.Key)),
@@ -91,7 +88,6 @@ namespace mbts.spark {
return resp == BTSMessageBoxButtons.Yes;
}
);
cancel_buy_orders_command.group = CommandGroups.Cancel;
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Sell).Select(o => o.order.Key)),
@@ -105,7 +101,6 @@ namespace mbts.spark {
return resp == BTSMessageBoxButtons.Yes;
}
);
cancel_sell_orders_command.group = CommandGroups.Cancel;
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
_model.working_orders.Select(os => os.Select(o => o.Key)),
@@ -119,13 +114,11 @@ namespace mbts.spark {
return resp == BTSMessageBoxButtons.Yes;
}
);
cancel_all_orders_command.group = CommandGroups.Cancel;
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
() => _vm.selected_orders.Where(x => x.status() == OrderStatus.Working).Select(x => x.Key),
can_execute: () => _vm.selected_orders.Any(x => x.status() == OrderStatus.Working)
);
cancel_selected_orders_command.group = CommandGroups.Cancel;
var recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
() =>
@@ -133,44 +126,41 @@ namespace mbts.spark {
.Select(x => (x.Def, x.State.Price, x.State.Quantity - x.State.FilledQuantity)),
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
);
recreate_selected_orders_command.group = CommandGroups.Modify;
var attach_selected_orders_command = new SparkCommandCommand(
"Attach To Selected",
"Attach To Selected Orders",
Icons.AttachAlgos,
_vm.begin_attach_cmd,
new DelegateCommand(_vm.begin_attach_algos, () => _vm.selected_rows.Count > 0),
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
attach_selected_orders_command.group = CommandGroups.Modify;
var detach_selected_orders_command = new SparkCommandCommand(
"Detach Selected",
"Detach And Cancel Selected Orders",
Icons.DetachAlgos,
_vm.detach_selected_orders_cmd,
new DelegateCommand(_vm.detach_selected_algos, () => _vm.selected_rows.Count > 0),
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
detach_selected_orders_command.group = CommandGroups.Modify;
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
Icons.RightArrow,
_vm.collapse_all_rows_command);
toggle_collapse_all_command.group = CommandGroups.Auxiliary;
var toggle_expand_all_command = new SparkCommandCommand("Expand All",
Icons.DownArrow,
_vm.expand_all_rows_command);
toggle_expand_all_command.group = CommandGroups.Auxiliary;
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
_settings_manager,
us => us.order_book_grid,
UserSettings.FileExtensions.GRID,
s => {
if (s.general is null)
s = OrderBookStartupData.DefaultGrid;
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.general = s.general.with(_vm.core_columns.Select(c => c.create()));
return _vm.grid_settings_manager.create_vm(s, this.observe_title());
s = OrderBookWindowFactory.OrderBookStartupData.DefaultGrid;
return _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.general.with(_vm.core_columns.Select(c => c.create())),
this.observe_title()
);
}
);
// TODO: bind to main window's Title
@@ -181,36 +171,27 @@ namespace mbts.spark {
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}));
show_grid_settings_command.group = CommandGroups.Auxiliary;
_commands = new SparkCommand[] {
toggle_show_right_panel_command,
attach_selected_orders_command,
detach_selected_orders_command,
recreate_selected_orders_command,
show_fills_command,
toggle_right_panel_is_visible_command,
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_orders_command,
recreate_selected_orders_command,
attach_selected_orders_command,
detach_selected_orders_command,
toggle_collapse_all_command,
toggle_expand_all_command,
show_grid_settings_command,
// hidden cmds (i.e., keyboard shortcuts at window level)
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders))
show_grid_settings_command
};
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.show_right_panel))
.Subscribe(_ => toggle_show_right_panel_command.title = get_toggle_show_right_panel_cmd_title());
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.algo_editor_is_visible))
.Subscribe(_ => toggle_right_panel_is_visible_command.title = get_toggle_algo_editor_cmd_title());
_vm.selected_order_changed += vm_selected_order_changed;
// local methods
string get_toggle_show_right_panel_cmd_title() =>
_vm.show_right_panel ? OrderBookView.CommandDescriptions.HideRightPanel : OrderBookView.CommandDescriptions.ShowRightPanel;
SparkCommand hidden_cmd(SparkCommand cmd) {
cmd.show_in_context_menu = false;
cmd.show_in_toolbar = false;
return cmd;
}
string get_toggle_algo_editor_cmd_title() =>
$"{(_vm.algo_editor_is_visible ? "Hide" : "Show")} Order Params";
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
@@ -228,14 +209,7 @@ namespace mbts.spark {
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
_vm.is_attaching_orders = false;
_vm.is_attaching_algos = false;
}));
static class CommandGroups {
public const string
Modify = nameof(Modify),
Cancel = nameof(Cancel),
Auxiliary = nameof(Auxiliary);
}
}
}
@@ -1,15 +1,25 @@
using bts.api;
using bts.shell.orderbook;
using bts.api.orderman;
using bts.spark;
using bts.utils;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace bts.shell {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookStartupData> {
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookWindowFactory.OrderBookStartupData> {
readonly IBus _bus;
readonly INotificationService _notification_service;
readonly INotificationServiceDataCache _notification_service_data_cache;
@@ -37,5 +47,293 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
[ProtoContract]
public class OrderBookStartupData {
const bool _default_algo_editor_is_visible = true;
const double
_default_right_panel_width = 400,
_default_fills_slave_grid_height = 138;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid;
// algo editor is visible
[ProtoMember(16)]
[DefaultValue(_default_algo_editor_is_visible)]
public bool algo_editor_is_visible { get; set; }
// right panel width
[ProtoMember(17)]
[DefaultValue(_default_right_panel_width)]
public double right_panel_width { get; set; }
// fills slave grid height
[ProtoMember(18)]
[DefaultValue(_default_fills_slave_grid_height)]
public double fills_slave_grid_height { get; set; }
// fills slave grid is visible
[ProtoMember(19)]
public bool fills_slave_grid_is_visible { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() {
algo_editor_is_visible = _default_algo_editor_is_visible;
right_panel_width = _default_right_panel_width;
fills_slave_grid_height = _default_fills_slave_grid_height;
}
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, bool algo_editor_is_visible = false, double right_panel_width = 0, double fills_slave_grid_height = 0, bool fills_slave_grid_is_visible = false, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.algo_editor_is_visible = algo_editor_is_visible;
this.right_panel_width = right_panel_width;
this.fills_slave_grid_height = fills_slave_grid_height;
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
#pragma warning disable CS0612 // Type or member is obsolete
this.grid = grid;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() => use_defaults ? null : new OrderBookGridSettings(grid, predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
_default_general_grid,
_default_algo_editor_is_visible,
_default_right_panel_width,
_default_fills_slave_grid_height,
use_defaults: true);
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
public static GridSettings default_mini_grid => GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80);
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in all) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
[ProtoContract]
public class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public api.orderman.TimeInForce[] tifs;
[ProtoMember(8)]
public api.orderman.CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public UIntMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public UIntMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public api.orderman.OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols));
}
[ProtoContract]
public class OrderBookGridSettings : IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>, IEquatable<OrderBookGridSettings> {
[ProtoMember(1)]
public GridSettings general { get; set; }
[ProtoMember(2)]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
public OrderBookGridSettings() { }
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, double right_panel_width = double.NaN) {
this.general = general;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
}
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations);
public bool Equals(OrderBookGridSettings other) =>
other != null &&
EqualityComparer.null_check_equals(general, other.general) &&
EqualityComparer.safe_sequence_equals(predicate_animations, other.predicate_animations);
public override bool Equals(object obj) => Equals(obj as OrderBookGridSettings);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
EqualityComparer.safe_get_hash_code(general),
EqualityComparer.safe_aggregate_hash(predicate_animations));
}
}
}
}
@@ -1,19 +0,0 @@
using mbts.ui;
using ProtoBuf;
using System.Collections.Generic;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
[ProtoContract]
class OrderBookFillsGridSettings {
[ProtoMember(1)]
public GridSettings general { get; set; }
[ProtoMember(2)]
public FillsWindowFactory.FilterStates filters { get; set; }
[ProtoMember(3)]
public List<SortingCriteria> sorting_criterion { get; set; }
public FillsWindowFactory.StartupData to_startup_data() =>
new FillsWindowFactory.StartupData(null, sorting_criterion, filters, general);
}
}
@@ -1,194 +0,0 @@
using bts.api;
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell.orderbook {
[ProtoContract]
class FilterStates : IEquatable<FilterStates> {
[ProtoMember(1)]
public SecurityCriteria security_criteria;
[ProtoMember(2)]
public string order_key_regex;
[ProtoMember(3)]
public TimeFilterTuple time_created;
[ProtoMember(4)]
public string[] users;
[ProtoMember(5)]
public string[] accounts;
[ProtoMember(6)]
public Venue[] exchanges;
[ProtoMember(7)]
public TimeInForce[] tifs;
[ProtoMember(8)]
public CustomOrderDefinition.CustomOneofCase[] order_types;
[ProtoMember(9)]
public Side[] sides;
[ProtoMember(10)]
public UIntMinMaxState qty;
[ProtoMember(11)]
public DoubleMinMaxState price;
[ProtoMember(12)]
public UIntMinMaxState qty_filled;
[ProtoMember(13)]
public DoubleMinMaxState avg_price;
[ProtoMember(14)]
public DoubleMinMaxState last_price;
[ProtoMember(15)]
public OrderStatus[] statuses;
[ProtoMember(16)]
public string exchange_message_regex;
[ProtoMember(17)]
public TimeFilterTuple last_change;
//[ProtoMember(18)]
//public string security_regex;
[ProtoMember(19)]
public api.orderman.OrderSubType[] order_sub_types;
[ProtoMember(20)]
public string[] symbols;
[ProtoMember(21)]
public string parent_key_regex;
[ProtoMember(22)]
public string attached_target_order_key_regex;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.order_key_regex &&
parent_key_regex == other.parent_key_regex &&
attached_target_order_key_regex == other.attached_target_order_key_regex &&
time_created.Equals(other.time_created) &&
EqualityComparer.safe_sequence_equals(users, other.users) &&
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
qty.safe_equals(other.qty) &&
price.safe_equals(other.price) &&
qty_filled.safe_equals(other.qty_filled) &&
avg_price.safe_equals(other.avg_price) &&
last_price.safe_equals(other.last_price) &&
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
exchange_message_regex == other.exchange_message_regex &&
last_change.Equals(other.last_change) &&
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
public override bool Equals(object obj) => Equals(obj as FilterStates);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
security_criteria?.GetHashCode() ?? 0,
order_key_regex?.GetHashCode() ?? 0,
parent_key_regex?.GetHashCode() ?? 0,
attached_target_order_key_regex?.GetHashCode() ?? 0,
time_created.GetHashCode(),
EqualityComparer.safe_aggregate_hash(users),
EqualityComparer.safe_aggregate_hash(accounts),
EqualityComparer.safe_aggregate_hash(exchanges),
EqualityComparer.safe_aggregate_hash(tifs),
EqualityComparer.safe_aggregate_hash(order_types),
EqualityComparer.safe_aggregate_hash(sides),
qty?.GetHashCode() ?? 0,
price?.GetHashCode() ?? 0,
qty_filled?.GetHashCode() ?? 0,
avg_price?.GetHashCode() ?? 0,
last_price?.GetHashCode() ?? 0,
EqualityComparer.safe_aggregate_hash(statuses),
exchange_message_regex?.GetHashCode() ?? 0,
last_change.GetHashCode(),
EqualityComparer.safe_aggregate_hash(order_sub_types),
EqualityComparer.safe_aggregate_hash(symbols));
}
static class FilterStatesExtensions {
public static Func<OrderSecurityAlgoMeta, bool> build_predicate(this FilterStates fs) {
var predicates = new List<Func<OrderSecurityAlgoMeta, bool>>();
// security
if (fs.security_criteria?.is_defined() ?? false)
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
// regex
add_regex(fs.order_key_regex, o => o.order.Key.ToString());
add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
// time
add_time_filter(fs.time_created, o => o.order.created());
add_time_filter(fs.last_change, o => o.order.last_change());
// discrete values
add_discrete_values(fs.users, o => o.order.user());
add_discrete_values(fs.accounts, o => o.order.account());
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
add_discrete_values(fs.tifs, o => o.order.time_in_force());
add_discrete_values(fs.order_types, o => o.order.order_type());
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
add_discrete_values(fs.sides, o => o.order.side());
add_discrete_values(fs.statuses, o => o.order.status());
add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
add_numeric(fs.qty, o => o.order.quantity());
add_numeric(fs.price, o => o.order.price());
add_numeric(fs.qty_filled, o => o.order.filled());
add_numeric(fs.avg_price, o => o.order.avg_price());
add_numeric(fs.last_price, o => o.order.last_price());
return predicates.Count == 0 ? null : new Func<OrderSecurityAlgoMeta, bool>(o => predicates.All(p => p(o)));
// local methods (impl)
void add_regex(string str, Func<OrderSecurityAlgoMeta, string> selector) {
var regex = RegexFilterUtils.build_regex(str);
if (regex == null)
return;
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => regex.IsMatch(selector(o) ?? string.Empty);
}
void add_time_filter(TimeFilterTuple t, Func<OrderSecurityAlgoMeta, DateTime> selector) {
if (!t.is_active)
return;
var dt_pred = TimeFilterUtils.build_predicate(t);
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => dt_pred(selector(o));
}
void add_discrete_values<T>(T[] values, Func<OrderSecurityAlgoMeta, T> selector) {
if (values.is_null_or_empty())
return;
if (values.Length < 16) {
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => values.Contains(selector(o));
} else {
var set = new HashSet<T>(values);
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => set.Contains(selector(o));
}
}
void add_numeric<T>(IMinMaxState<T> value, Func<OrderSecurityAlgoMeta, T> selector) where T : struct, IComparable<T> {
if (value == null)
return;
if (value.minimum.active) {
var min = value.minimum.value.Value;
if (value.maximum.active) {
var max = value.maximum.value.Value;
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) {
var v = selector(o);
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
}
} else {
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => min.CompareTo(selector(o)) <= 0;
}
} else if (value.maximum.active) {
var max = value.maximum.value.Value;
predicates.Add(impl);
bool impl(OrderSecurityAlgoMeta o) => max.CompareTo(selector(o)) >= 0;
}
}
}
}
}
@@ -1,60 +0,0 @@
using bts.api.orderman;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
enum AlgoTreeDisplayMode {
FullTree,
RootAndLeafNodes
}
[ProtoContract]
class OrderBookGridSettings : IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>, IEquatable<OrderBookGridSettings> {
const AlgoTreeDisplayMode _default_algo_tree_display_mode = AlgoTreeDisplayMode.FullTree;
// general
[ProtoMember(1)]
public GridSettings general { get; set; }
// predicate animations
[ProtoMember(2)]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// algo tree display mode
[ProtoMember(3)]
[DefaultValue(_default_algo_tree_display_mode)]
public AlgoTreeDisplayMode algo_tree_display_mode { get; set; }
public OrderBookGridSettings() =>
algo_tree_display_mode = _default_algo_tree_display_mode;
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
this.general = general;
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
}
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
public OrderBookGridSettings with(GridSettings general) =>
new OrderBookGridSettings(general ?? this.general, predicate_animations);
public bool Equals(OrderBookGridSettings other) =>
other != null &&
algo_tree_display_mode == other.algo_tree_display_mode &&
EqualityComparer.null_check_equals(general, other.general) &&
EqualityComparer.safe_sequence_equals(predicate_animations, other.predicate_animations);
public override bool Equals(object obj) => Equals(obj as OrderBookGridSettings);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
EqualityComparer.safe_get_hash_code(general),
EqualityComparer.safe_aggregate_hash(predicate_animations));
}
}
@@ -1,127 +0,0 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
namespace bts.shell.orderbook {
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookGridSettingsManager(
IObservable<string> owner_title,
IBus bus,
INotificationService notification_service,
OrdersModel model,
IEnumerable<OrderBookRowVM> rows,
ObservableCollection<DIYGrid.ColumnBase> columns) {
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
// TODO: Remove the immutable font-weight flags when cell animation is ready
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
c => c.ToString(), c => GridColumnSettingsFeatureFlags.ImmutableFontWeight);
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
// TODO: Remove the immutable font-color flags when cell animation is ready
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
}
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
owner_title, bus, model, rows, columns,
() => new OrderRecordTuplePredicate(),
() => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
return pvm;
},
col_id => {
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
return pvm;
},
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))) { algo_tree_display_mode = _algo_tree_display_mode },
p => p.create_predicate(notification_service.data_cache),
p => p.create_notification_predicate(notification_service.data_cache),
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
col_settings_flags);
_core_man_as_observer = core_manager;
}
public event Action algo_tree_display_mode_changed;
public GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_manager { get; }
public AlgoTreeDisplayMode algo_tree_display_mode {
get => _algo_tree_display_mode;
set {
if (_algo_tree_display_mode != value) {
_algo_tree_display_mode = value;
algo_tree_display_mode_changed?.Invoke();
}
}
}
public void load_from(OrderBookGridSettings gs) {
core_manager.load_from(gs);
algo_tree_display_mode = gs.algo_tree_display_mode;
}
public OrderBookGridSettings create(IEnumerable<SortingCriteria> sorting_criterion) {
var gs = core_manager.create();
gs.general = gs.general.with(sorting_criterion: sorting_criterion);
gs.algo_tree_display_mode = _algo_tree_display_mode;
return gs;
}
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs, IObservable<string> owner_title) =>
new OrderBookSettingsVM(core_manager.create_vm(gs.general, owner_title)) {
algo_tree_display_mode = gs.algo_tree_display_mode
};
public void Dispose() => core_manager.Dispose();
static OrderRecordTuplePredicate.PredicateTypeOneofCase _corresponding_predicate_type(Columns col) {
switch (col) {
case Columns.Security: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Security;
case Columns.Time: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Time;
case Columns.LastChange: return OrderRecordTuplePredicate.PredicateTypeOneofCase.LastChange;
case Columns.User: return OrderRecordTuplePredicate.PredicateTypeOneofCase.User;
case Columns.Account: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Account;
case Columns.Exchange: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Exchange;
case Columns.TimeInForce: return OrderRecordTuplePredicate.PredicateTypeOneofCase.TimeInForce;
case Columns.OrderType: return OrderRecordTuplePredicate.PredicateTypeOneofCase.OrderType;
case Columns.SubType: return OrderRecordTuplePredicate.PredicateTypeOneofCase.SubType;
case Columns.Side: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Side;
case Columns.Size: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Size;
case Columns.Price: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Price;
case Columns.Filled: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Filled;
case Columns.AvgFilled: return OrderRecordTuplePredicate.PredicateTypeOneofCase.AvgFilled;
case Columns.LastPrice: return OrderRecordTuplePredicate.PredicateTypeOneofCase.LastPrice;
case Columns.Status: return OrderRecordTuplePredicate.PredicateTypeOneofCase.Status;
case Columns.LastMessage: return OrderRecordTuplePredicate.PredicateTypeOneofCase.LastMessage;
case Columns.SecurityKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.SecurityKey;
case Columns.OrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.OrderKey;
case Columns.ParentKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.ParentKey;
case Columns.AttachedTargetOrderKey: return OrderRecordTuplePredicate.PredicateTypeOneofCase.AttachedTargetOrderKey;
default: return default;
}
}
void IObserver<OrderBookGridSettings>.OnNext(OrderBookGridSettings value) {
_core_man_as_observer.OnNext(value);
algo_tree_display_mode = value.algo_tree_display_mode;
}
void IObserver<OrderBookGridSettings>.OnError(Exception error) => _core_man_as_observer.OnError(error);
void IObserver<OrderBookGridSettings>.OnCompleted() => _core_man_as_observer.OnCompleted();
}
}
@@ -1,124 +0,0 @@
using mbts;
using ProtoBuf;
using System;
using System.ComponentModel;
namespace bts.shell.orderbook {
[ProtoContract]
class OrderBookRightPanelSettings : IEquatable<OrderBookRightPanelSettings> {
public OrderBookRightPanelSettings() {
mode = Defaults.mode;
stack = new StackSettings();
tab = Defaults.tab;
show = Defaults.show;
width = Defaults.width;
}
public enum ViewMode {
[Description("Stack Mode")]
Stack = 1,
[Description("Tab Mode")]
Tab = 1 << 1,
}
public enum Components {
None = 0,
Params = 1,
Ladder = 1 << 1,
Fills = 1 << 2
}
// show
[ProtoMember(1)]
[DefaultValue(Defaults.show)]
public bool show { get; set; }
// width
[ProtoMember(2)]
[DefaultValue(Defaults.width)]
public double width { get; set; }
// view mode
[ProtoMember(3)]
[DefaultValue(Defaults.mode)]
public ViewMode mode { get; set; }
// stack
[ProtoMember(4)]
public StackSettings stack { get; set; }
// tab
[ProtoMember(5)]
[DefaultValue(Defaults.tab)]
public Components tab { get; set; }
public bool Equals(OrderBookRightPanelSettings other) =>
other != null &&
show == other.show &&
width == other.width &&
mode == other.mode &&
tab == other.tab &&
stack.safe_equals(other.stack);
[ProtoContract]
public class StackSettings : IEquatable<StackSettings> {
// show order params
[ProtoMember(1)]
[DefaultValue(Defaults.show_order_params)]
public bool show_order_params { get; set; }
// order params height
[ProtoMember(2)]
[DefaultValue(Defaults.order_params_height)]
public double order_params_height { get; set; }
// show ladder
[ProtoMember(3)]
[DefaultValue(Defaults.show_fills)]
public bool show_ladder { get; set; }
// ladder height
[ProtoMember(4)]
[DefaultValue(Defaults.ladder_height)]
public double ladder_height { get; set; }
// show fills
[ProtoMember(5)]
[DefaultValue(Defaults.show_fills)]
public bool show_fills { get; set; }
// fills height
[ProtoMember(6)]
[DefaultValue(Defaults.fills_height)]
public double fills_height { get; set; }
// fills grid
[ProtoMember(7)]
public OrderBookFillsGridSettings fills_grid { get; set; }
public StackSettings() {
show_order_params = Defaults.show_order_params;
order_params_height = Defaults.order_params_height;
show_fills = Defaults.show_fills;
fills_height = Defaults.fills_height;
show_ladder = Defaults.show_ladder;
ladder_height = Defaults.ladder_height;
}
public bool Equals(StackSettings other) =>
other != null &&
show_ladder == other.show_ladder &&
show_order_params == other.show_order_params &&
show_fills == other.show_fills;
static class Defaults {
public const bool
show_order_params = true,
show_ladder = true,
show_fills = true;
public const double
right_panel_width = 400,
order_params_height = 250,
fills_height = 300,
ladder_height = 350;
}
}
static class Defaults {
public const ViewMode mode = ViewMode.Tab;
public const Components tab = Components.Params;
public const bool show = true;
public const double width = 400;
}
}
}
@@ -1,209 +0,0 @@
using bts.api.orderman;
using mbts;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using GridSettings = mbts.ui.settings.GridSettings;
namespace bts.shell.orderbook {
[ProtoContract]
class OrderBookStartupData {
const bool
_default_show_right_panel = true,
_default_show_order_params = true;
const double
_default_right_panel_width = 400,
_default_order_params_height = 250,
_default_fills_height = 300,
_default_ladder_height = 350;
[ProtoMember(16)]
OrderBookRightPanelSettings _right_panel;
// ALREADY USED TAGS
// [ProtoMember(5)]
// public readonly List<SortingCriteria> sort_criteria;
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
// right panel (host)
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
// order params
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
public double order_params_height { get; set; }
// ladder
public bool show_ladder { get; set; }
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
// fills
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
// filters
[ProtoMember(10)]
public FilterStates filters;
// grid
[ProtoMember(15)]
[Obsolete]
public GridSettings grid { get; set; }
// orderbook grid
[ProtoMember(17)]
public OrderBookGridSettings orderbook_grid { get; set; }
// fills settings
[ProtoMember(26)]
public OrderBookFillsGridSettings fills_settings { get; set; }
// predicate animations
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
// use defaults
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() {
show_right_panel = _default_show_right_panel;
show_order_params = _default_show_order_params;
right_panel_width = _default_right_panel_width;
order_params_height = _default_order_params_height;
fills_height = _default_fills_height;
ladder_height = _default_ladder_height;
}
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
this.show_right_panel = show_right_panel;
this.right_panel_width = right_panel_width;
this.fills_height = fills_height;
this.show_fills = show_fills;
this.orderbook_grid = orderbook_grid;
#pragma warning disable CS0612 // Type or member is obsolete
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
if (predicate_animations != null)
_predicate_animations.AddRange(predicate_animations);
#pragma warning restore CS0612 // Type or member is obsolete
}
#pragma warning disable CS0612 // Type or member is obsolete
[Obsolete]
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
public OrderBookGridSettings grid_settings_wrapper() {
if (use_defaults)
return null;
if (orderbook_grid == null)
orderbook_grid = new OrderBookGridSettings(grid.clone(), _predicate_animations);
return orderbook_grid;
}
#pragma warning restore CS0612 // Type or member is obsolete
public static OrderBookStartupData Default =>
new OrderBookStartupData(
new FilterStates { statuses = new[] { OrderStatus.Working } },
DefaultGrid,
_default_show_right_panel,
_default_right_panel_width,
_default_fills_height,
use_defaults: true) {
show_order_params = _default_show_order_params,
order_params_height = _default_order_params_height,
fills_settings = DefaultFillsGrid
};
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
};
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsWindowFactory.StartupData.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criterion = default_fills.sorting_criterion.ToList(),
filters = default_fills.filters,
general = FillsWindowFactory.StartupData.MiniGridSettings
};
return rv;
}
}
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion));
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
public static IEnumerable<SortingCriteria> sorting_criterion {
get {
yield return new SortingCriteria(time_created.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return symbol;
yield return time_created;
yield return last_change;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return sub_type;
yield return side;
yield return quantity;
yield return price;
yield return filled_quantity;
yield return avg_price;
yield return last_price;
yield return status;
yield return last_message;
yield return security_key;
yield return order_key;
yield return parent_key;
yield return attached_target_order_key;
}
}
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
};
public static IEnumerable<GridColumnSettings> mini {
get {
foreach (var col_setting in all) {
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
yield return col_setting.with(is_visible: false);
} else {
yield return col_setting;
}
}
}
}
}
}
}
@@ -1,45 +0,0 @@
using bts.api;
using bts.api.orderman;
using mbts;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using System;
using System.Collections.Generic;
namespace bts.shell.orderbook {
class OrderBookSettingsVM : DisposableViewModelBase, IWindowSettingsVM<OrderBookGridSettings>, ITemplateSelectorKey {
AlgoTreeDisplayMode _algo_tree_display_mode;
public OrderBookSettingsVM(GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm) =>
this.core_settings_vm = core_settings_vm;
public OrderBookSettingsVM(
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
Func<OrderRecordTuplePredicate> predicate_data_factory,
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
Func<string, IPropertyWrapperVM<OrderRecordTuplePredicate>> create_column_predicate_vm,
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
owner_title, bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
}
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
public AlgoTreeDisplayMode algo_tree_display_mode { get => _algo_tree_display_mode; set => Set(ref _algo_tree_display_mode, value); }
string ITemplateSelectorKey.key => GridSettingsTemplateKeys.OrderBook;
public void load(OrderBookGridSettings grid_settings) {
core_settings_vm.load(grid_settings);
algo_tree_display_mode = grid_settings.algo_tree_display_mode;
}
public OrderBookGridSettings get_result() {
var gs = core_settings_vm.get_result();
gs.algo_tree_display_mode = algo_tree_display_mode;
return gs;
}
}
}
@@ -112,54 +112,15 @@
<c:DIYGrid.RowStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<!-- Completed order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Completed, Type=order:OrderStatus}"/>
<Condition Binding="{Binding status}" Value="{u:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=completed_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Error, RejectedBySpark, Disconnected order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Error, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum RejectedBySpark, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Disconnected, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<!-- Cancel order background -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{u:Enum Canceled, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="{x:Static Member=canceled_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</c:DIYGrid.RowStyle>
@@ -13,35 +13,31 @@ namespace mbts.spark {
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
public enum ColumnKeys {
Security = 1,
Symbol = 1 << 1,
Time = 1 << 2,
LastChange = 1 << 3,
User = 1 << 4,
Account = 1 << 5,
Exchange = 1 << 6,
TimeInForce = 1 << 7,
OrderType = 1 << 8,
SubType = 1 << 9,
Side = 1 << 10,
Size = 1 << 11,
Price = 1 << 12,
Filled = 1 << 13,
AvgFilled = 1 << 14,
LastPrice = 1 << 15,
Status = 1 << 16,
LastMessage = 1 << 17,
SecurityKey = 1 << 18,
OrderKey = 1 << 19,
ParentKey = 1 << 20,
AttachedTargetOrderKey = 1 << 21,
Cancel = 1 << 22,
EditableQty = 1 << 23,
EditablePrice = 1 << 24,
Attach = 1 << 25,
Detach = 1 << 26,
Update = 1 << 27,
Commit = 1 << 28
Security,
Symbol,
Time,
LastChange,
User,
Account,
Exchange,
TimeInForce,
OrderType,
SubType,
Side,
Size,
Price,
Filled,
AvgFilled,
LastPrice,
Status,
LastMessage,
SecurityKey,
OrderKey,
ParentKey,
AttachedTargetOrderKey,
Cancel,
EditableQty,
EditablePrice
}
public OrderBookGridView() =>
@@ -1,492 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:ie="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ob="clr-namespace:bts.shell.orderbook"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
>
<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>
<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FFCCCCCC</Color>
<Color x:Key="ControlDarkColor">#FFAAAAAA</Color>
<Color x:Key="ControlMouseOverColor">#200080FF</Color>
<Color x:Key="ControlPressedColor">#800080FF</Color>
<Color x:Key="TabLightColor">#FFE5E5E5</Color>
<Color x:Key="TabMediumColor">#FFF0F0F0</Color>
<!--Border colors-->
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="MouseOverBorderMediumColor">#90006CD9</Color>
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
<Color x:Key="TabBorderColor">#FFACACAC</Color>
<Color x:Key="TabMouseOverBorderMediumColor">#FF7EB4EA</Color>
<!--Control-specific resources.-->
<Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
<Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
<Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>
<Color x:Key="NavButtonFrameColor">#FF3843C4</Color>
<Color x:Key="TabMouseOverBackgroundLightColor">#FFECF4FC</Color>
<Color x:Key="TabMouseOverBackgroundMediumColor">#FFDCECFC</Color>
<LinearGradientBrush x:Key="MenuPopupBrush" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="0"/>
<GradientStop Color="{DynamicResource ControlMediumColor}" Offset="0.5"/>
<GradientStop Color="{DynamicResource ControlLightColor}" Offset="1"/>
</LinearGradientBrush>
<!--Converters-->
<cv:CastConverter x:Key="CastConverter"/>
<Style x:Key="ToolBarButtonBaseStyle" TargetType="{x:Type ButtonBase}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1,0"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Border
x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource ControlPressedColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource ControlMouseOverColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource DisabledControlDarkColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="{StaticResource ControlPressedColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked"/>
<VisualState x:Name="Indeterminate"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static ToolBar.ToggleButtonStyleKey}" BasedOn="{StaticResource ToolBarButtonBaseStyle}" TargetType="{x:Type ToggleButton}"/>
<Style x:Key="ToolBarThumbStyle" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Cursor" Value="SizeAll"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="Transparent" SnapsToDevicePixels="True">
<Rectangle Margin="0,2">
<Rectangle.Fill>
<DrawingBrush
Viewport="0,0,4,4"
ViewportUnits="Absolute"
Viewbox="0,0,8,8"
ViewboxUnits="Absolute"
TileMode="Tile"
>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="#AAA" Geometry="M 4 4 L 4 8 L 8 8 L 8 4 z"/>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ToolBarOverflowButtonStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="ClickMode" Value="Press"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border
x:Name="Border"
BorderBrush="Transparent"
Background="Transparent"
BorderThickness="1,0"
SnapsToDevicePixels="True"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:ModalStateDefinition Mode="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(ToggleButton.DataContext).(sp:OrderBookVM.right_panel_view_mode)', RelativeSource={RelativeSource Self}, Converter={StaticResource CastConverter}}">
<u:ModalStateDefinition.Source>
<u:MouseStateDefinition MouseSource="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent)', RelativeSource={RelativeSource Self}, Converter={StaticResource CastConverter}}"/>
</u:ModalStateDefinition.Source>
</u:ModalStateDefinition>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource DisabledBorderLightColor}"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="MouseStates">
<!--Stack-->
<VisualState Name="StackNormal"/>
<VisualState Name="StackMouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource ControlMouseOverColor}"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource MouseOverBorderMediumColor}"/>
</Storyboard>
</VisualState>
<VisualState Name="StackPressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
<!--Tab-->
<VisualState Name="TabNormal"/>
<VisualState Name="TabMouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource ControlMouseOverColor}"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource MouseOverBorderMediumColor}"/>
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderThickness)" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Path
x:Name="Arrow"
Fill="Black"
VerticalAlignment="Bottom"
Margin="2,3"
Data="M -0.5 3 L 5.5 3 L 2.5 6 Z"/>
<ContentPresenter/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding right_panel_view_mode}" Value="{x:Static Tab, MemberType={u:Nested ob:OrderBookRightPanelSettings.ViewMode}}">
<Setter TargetName="Border" Property="BorderThickness" Value="0,0,0,1"/>
<Setter TargetName="Border" Property="BorderBrush">
<Setter.Value>
<SolidColorBrush Color="{StaticResource TabBorderColor}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--MenuItem Storyboards-->
<Storyboard x:Key="TabCheckedStoryboard">
<!--CheckMark Opacity-->
<DoubleAnimation Storyboard.TargetName="CheckMark" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
<!--Background Opacity-->
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="0"/>
<!--Border Thickness/Opacity/Color-->
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderThickness)" BeginTime="0" Duration="0" To="1,1,1,0"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource TabBorderColor}"/>
<!--Padding-->
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Padding)" BeginTime="0" Duration="0" To="6,4,6,5"/>
</Storyboard>
<Storyboard x:Key="TabMouseOverStoryboard">
<!--Background Opacity/Color-->
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[0].(GradientStop.Color)" BeginTime="0" Duration="0" To="{StaticResource TabMouseOverBackgroundLightColor}"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[1].(GradientStop.Color)" BeginTime="0" Duration="0" To="{StaticResource TabMouseOverBackgroundMediumColor}"/>
<!--Border Thickness/Opacity/Color-->
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderThickness)" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="0.5625"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource TabMouseOverBorderMediumColor}"/>
<!--Padding-->
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Padding)" BeginTime="0" Duration="0" To="6,2"/>
</Storyboard>
<Style x:Key="RightPanel.MenuItem" TargetType="MenuItem">
<Setter Property="c:MenuItemEx.ClickMode" Value="Press"/>
<Setter Property="BorderThickness" Value="1,0"/>
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="MenuItem">
<Border
x:Name="Border"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="4,1,5,1"
Height="{Binding Padding, RelativeSource={RelativeSource Self}, Converter={x:Static sp:OrderBookView.ToolBarMenuItemHeightConverter}}"
MinHeight="{TemplateBinding MinHeight}"
VerticalAlignment="Bottom"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:MouseStateDefinition MouseSource="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent)', RelativeSource={RelativeSource Self}, Converter={StaticResource CastConverter}}">
<u:MouseStateDefinition.Mode>
<MultiBinding>
<MultiBinding.Converter>
<cv:ConcatConverter/>
</MultiBinding.Converter>
<Binding Path="(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(MenuItem.DataContext).(sp:OrderBookVM.right_panel_view_mode)" RelativeSource="{RelativeSource Self}" Converter="{x:Static cv:ValueConverters.SimpleString}"/>
<Binding Path="(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(MenuItem.IsChecked)" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForTrue="Checked" ValueForFalse="Unchecked"/>
</Binding.Converter>
</Binding>
</MultiBinding>
</u:MouseStateDefinition.Mode>
</u:MouseStateDefinition>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<Border.BorderBrush>
<SolidColorBrush Color="#006CD9" Opacity="0"/>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1" Opacity="0">
<GradientStop Color="#400080FF" Offset="0.0"/>
<GradientStop Color="#400080FF" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<!--Stack-->
<VisualState Name="StackUncheckedNormal"/>
<VisualState Name="StackUncheckedMouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="0.5625"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="0.5"/>
</Storyboard>
</VisualState>
<VisualState Name="StackUncheckedPressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
<VisualState Name="StackCheckedNormal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="CheckMark" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[0].(GradientStop.Color)" BeginTime="0" Duration="0" To="#800080FF"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[1].(GradientStop.Color)" BeginTime="0" Duration="0" To="#800080FF"/>
</Storyboard>
</VisualState>
<VisualState Name="StackCheckedMouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="CheckMark" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[0].(GradientStop.Color)" BeginTime="0" Duration="0" To="#800080FF"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[1].(GradientStop.Color)" BeginTime="0" Duration="0" To="#800080FF"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="0.5625"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="0.5"/>
</Storyboard>
</VisualState>
<VisualState Name="StackCheckedPressed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="CheckMark" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
</Storyboard>
</VisualState>
<!--Tab-->
<VisualState Name="TabUncheckedNormal">
<Storyboard>
<!--Background Opacity/Color-->
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[0].(GradientStop.Color)" BeginTime="0" Duration="0" To="{StaticResource TabMediumColor}"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.Background).(LinearGradientBrush.GradientStops)[1].(GradientStop.Color)" BeginTime="0" Duration="0" To="{StaticResource TabLightColor}"/>
<!--Border Thickness/Opacity/Color-->
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.BorderThickness)" BeginTime="0" Duration="0" To="1"/>
<DoubleAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="1"/>
<ColorAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="{StaticResource TabBorderColor}"/>
<ThicknessAnimation Storyboard.TargetName="Border" Storyboard.TargetProperty="(Border.Padding)" BeginTime="0" Duration="0" To="6,2"/>
</Storyboard>
</VisualState>
<VisualState Name="TabUncheckedMouseOver" Storyboard="{StaticResource TabMouseOverStoryboard}"/>
<VisualState Name="TabUncheckedPressed" Storyboard="{StaticResource TabMouseOverStoryboard}"/>
<VisualState Name="TabCheckedNormal" Storyboard="{StaticResource TabCheckedStoryboard}"/>
<VisualState Name="TabCheckedMouseOver" Storyboard="{StaticResource TabCheckedStoryboard}"/>
<VisualState Name="TabCheckedPressed" Storyboard="{StaticResource TabCheckedStoryboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="CheckMark" VerticalAlignment="Bottom" Height="9.5" Margin="0,0,0,3" Opacity="0">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black" Geometry="{Binding Source={StaticResource {x:Static u:Icons.CheckMarkGeometryKey}}}"/>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Image Grid.Column="1" Margin="{DynamicResource IconPadding}" Source="{TemplateBinding Icon}"/>
<ContentPresenter
Grid.Column="2"
TextElement.FontWeight="SemiBold"
Margin="4,0,0,0"
ContentSource="Header"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type ToolBar}" TargetType="{x:Type ToolBar}">
<Setter Property="ToolBarTray.IsLocked" Value="True"/>
<Setter Property="Background" Value="LightGray"/>
<Setter Property="BorderBrush" Value="DarkGray"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolBar}">
<Border
x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
>
<DockPanel>
<ToggleButton
DockPanel.Dock="Right"
IsChecked="{Binding IsOverflowOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ToolBarOverflowButtonStyle}"
>
<Popup
x:Name="OverflowPopup"
AllowsTransparency="True"
Placement="Bottom"
StaysOpen="False"
Focusable="False"
PopupAnimation="Slide"
IsOpen="{Binding IsOverflowOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
>
<Border
x:Name="DropDownBorder"
BorderThickness="1"
BorderBrush="{u:BTSColor UI.Button.normal_border}"
Background="{StaticResource ControlBackground}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeave">
<ie:ChangePropertyAction TargetName="OverflowPopup" PropertyName="IsOpen" Value="False"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel Orientation="Vertical">
<MenuItem
c:RadioButtonGroupManager.Manager="{Binding Path='(c:RadioButtonGroupManager.Manager)', RelativeSource={RelativeSource AncestorType=ToolBar}}"
HorizontalAlignment="Stretch"
IsCheckable="True"
DataContext="{u:Enum Tab, Type={u:Nested ob:OrderBookRightPanelSettings.ViewMode}}"
Header="{Binding}"
HeaderTemplate="{StaticResource EnumDescriptionTemplate}"
/>
<MenuItem
c:RadioButtonGroupManager.Manager="{Binding Path='(c:RadioButtonGroupManager.Manager)', RelativeSource={RelativeSource AncestorType=ToolBar}}"
HorizontalAlignment="Stretch"
IsCheckable="True"
DataContext="{u:Enum Stack, Type={u:Nested ob:OrderBookRightPanelSettings.ViewMode}}"
Header="{Binding}"
HeaderTemplate="{StaticResource EnumDescriptionTemplate}"
/>
</StackPanel>
</Border>
</Popup>
</ToggleButton>
<Thumb x:Name="ToolBarThumb" Style="{StaticResource ToolBarThumbStyle}" Width="10"/>
<ToolBarPanel x:Name="PART_ToolBarPanel" IsItemsHost="True"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsOverflowOpen" Value="True">
<Setter TargetName="ToolBarThumb" Property="IsEnabled" Value="False"/>
</Trigger>
<Trigger Property="ToolBarTray.IsLocked" Value="True">
<Setter TargetName="ToolBarThumb" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type ToolBarTray}" TargetType="{x:Type ToolBarTray}">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="{DynamicResource WindowColor}" Offset="1.0"/>
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
@@ -4,15 +4,12 @@
x:Name="Root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:api="clr-namespace:bts.api;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:ut="clr-namespace:mbts.ui.utilities"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ob="clr-namespace:bts.shell.orderbook"
Grid.Row="0"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}"
@@ -22,28 +19,12 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderBookToolBar.xaml"/>
</ResourceDictionary.MergedDictionaries>
<sys:Double x:Key="GridSplitterWidth">5</sys:Double>
<sys:Double x:Key="GridSplitterAndMenuExpanderWidth">10</sys:Double>
<sys:Double x:Key="FillsSlaveGridMinHeight">64</sys:Double>
<SolidColorBrush x:Key="AlgoAttachmentBackground" Color="#55009500"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
<cv:GridLengthConverter x:Key="GridLengthStarConverter" UnitType="Star"/>
<cv:IntSubtractionConverter x:Key="MinusOneConverter" Value="1"/>
<cv:ValueConverterPipe x:Key="SelectedOrdersAndOthersSuffixVisibilityConverter">
<cv:IntGreaterThanConverter Value="1"/>
<BooleanToVisibilityConverter/>
</cv:ValueConverterPipe>
<cv:ValueMap x:Key="SideToBrushConverter" Default="{u:Brush Black}">
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{x:Static bid_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{x:Static ask_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
</cv:ValueMap>
<cv:ValueMap x:Key="OrderParamsVisibilityConverter" Default="{x:Static Visibility.Visible}">
<cv:KeyValue Key="{u:Int 0}" Value="{x:Static Visibility.Hidden}"/>
</cv:ValueMap>
<Style TargetType="GridSplitter">
<Setter Property="IsTabStop" Value="False"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource Control.MediumGrayBrush}"/>
@@ -51,7 +32,6 @@
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
@@ -96,25 +76,6 @@
<Setter Property="Command" Value="{Binding DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
<DataTemplate x:Key="OrderDescriptionTemplate">
<TextBlock>
<TextBlock.Inlines>
<Run Text="{Binding security, Mode=OneWay}" FontWeight="Bold"/>
<Run Text="{Binding side, Mode=OneWay}" Foreground="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
<Run Text="{Binding quantity, Mode=OneWay}"/>
<Run>@</Run>
<Run>
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="price" Mode="OneWay"/>
<Binding Path="security.ContractSpec" Mode="OneWay"/>
</MultiBinding>
</Run.Text>
</Run>
<Run Text="{Binding filled_status_desc, Mode=OneWay, StringFormat='({0})'}"/>
</TextBlock.Inlines>
</TextBlock>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
@@ -122,31 +83,64 @@
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding DataContext.show_right_panel, Source={u:DirectReference ContentRoot}, Converter={x:Static sp:OrderBookView.RightPanelVisualStateConverter}}"/>
<u:StateDefinition>
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelVisualStateConverter}">
<Binding Path="DataContext.algo_editor_is_visible" Source="{u:DirectReference ContentRoot}"/>
<Binding Path="DataContext.is_attaching_algos" Source="{u:DirectReference ContentRoot}"/>
</MultiBinding>
</u:StateDefinition>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
<!--Right Panel Closed-->
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}"/>
<!--Right Panel Open-->
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
<!--Normal State-->
<VisualState Name="{x:Static Normal, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}"/>
<!--Attach Orders State-->
<VisualState Name="{x:Static AttachOrders, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
<Storyboard>
<!--Orders Grid-->
<Int32Animation Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="1"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Pen}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="attachment_grid_overlay_border" Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<!--Right Panel-->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_host" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_width_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<!--Modify Orders Controls-->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_orders_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<!--Fills Slave Grid-->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_toggle_hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<!--Attach Orders Controls-->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_content_root" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--Modify Orders State-->
<VisualState Name="{x:Static ModifyOrders, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
<Storyboard>
<!--Orders Grid-->
<Int32Animation Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="1"/>
<!--Right Panel-->
<DoubleAnimation Storyboard.TargetName="right_panel_expander_toggle" Storyboard.TargetProperty="(FrameworkElement.Width)" BeginTime="0" Duration="0" To="{StaticResource GridSplitterWidth}"/>
<Int32Animation Storyboard.TargetName="right_panel_expander_toggle" Storyboard.TargetProperty="(Grid.Column)" BeginTime="0" Duration="0" To="1"/>
<ThicknessAnimation Storyboard.TargetName="right_panel_expander_toggle" Storyboard.TargetProperty="(FrameworkElement.Margin)" BeginTime="0" Duration="0">
<ThicknessAnimation.To>
<Thickness Left="{StaticResource GridSplitterWidth}"/>
</ThicknessAnimation.To>
</ThicknessAnimation>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_expander_toggle" Storyboard.TargetProperty="(Control.HorizontalAlignment)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static HorizontalAlignment.Left}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_content_root" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_host" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_width_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
@@ -171,16 +165,18 @@
Columns="{Binding columns}"
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Rows="{Binding rows}"
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
>
<FrameworkElement.Margin>
<Binding Source="{StaticResource GridSplitterWidth}">
<Binding.Converter>
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
</Binding.Converter>
</Binding>
</FrameworkElement.Margin>
</sp:OrderBookGridView>
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
Cursor="Arrow"
/>
<Border
x:Name="attachment_grid_overlay_border"
Panel.ZIndex="1"
Width="{Binding ActualWidth, ElementName=orders_grid}"
Height="{Binding ActualHeight, ElementName=orders_grid}"
Background="{StaticResource AlgoAttachmentBackground}"
Opacity="0"
IsHitTestVisible="False"
/>
<GridSplitter
x:Name="right_panel_width_splitter"
Grid.Column="1"
@@ -189,392 +185,212 @@
VerticalAlignment="Stretch"
Width="{StaticResource GridSplitterWidth}"
BorderThickness="1,0,0,0"
BorderBrush="{u:BTSColor UI.form_content_border}"
Visibility="Collapsed"
>
<GridSplitter.Opacity>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<cv:BooleanToObjectConverter ValueForFalse="{u:Double 0}" ValueForTrue="{u:Double 1}"/>
</Binding.Converter>
</Binding>
</GridSplitter.Opacity>
</GridSplitter>
<ToggleButton
x:Name="right_panel_expander_toggle"
Grid.Column="2"
Panel.ZIndex="1"
HorizontalAlignment="Right"
Width="7"
BorderThickness="1"
IsChecked="{Binding show_right_panel}"
>
<ToggleButton.Style>
<Style TargetType="ToggleButton" BasedOn="{StaticResource MenuExpanderButtonBase}">
<Setter Property="Opacity">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<cv:MultiValueConverterPipe>
<cv:BooleanOrConverter/>
<cv:BooleanToObjectConverter ValueForFalse="{u:Double 0}" ValueForTrue="{u:Double 1}"/>
</cv:MultiValueConverterPipe>
</MultiBinding.Converter>
<Binding Path="IsMouseOver" RelativeSource="{RelativeSource Self}"/>
<Binding Path="show_right_panel"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
<Binding Path="show_right_panel">
<Binding.Converter>
<cv:BooleanToObjectConverter
ValueForFalse="{x:Static ShowRightPanel, MemberType={u:Nested sp:OrderBookView.CommandDescriptions}}"
ValueForTrue="{x:Static HideRightPanel, MemberType={u:Nested sp:OrderBookView.CommandDescriptions}}"
/>
</Binding.Converter>
</Binding>
<Binding Source="{x:Static ToggleShowRightPanel, MemberType={u:Nested sp:OrderBookView.KeyboardShortcuts}}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Cursor" Value="ScrollW"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Cursor" Value="ScrollE"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
<Grid x:Name="right_panel_content_root" Grid.Column="1" Visibility="Collapsed">
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference right_panel_content_root}}">
<u:StateDefinitionCollection>
<u:StateDefinition>
<MultiBinding Converter="{x:Static sp:OrderBookView.SlaveDataVisualStateConverter}">
<Binding Path="DataContext.show_order_params" Source="{u:DirectReference right_panel_content_root}"/>
<Binding Path="DataContext.show_fills" Source="{u:DirectReference right_panel_content_root}"/>
<Binding Path="DataContext.show_ladder" Source="{u:DirectReference right_panel_content_root}"/>
</MultiBinding>
</u:StateDefinition>
<u:StateDefinition State="{Binding DataContext.security_plurality, Source={u:DirectReference right_panel_content_root}, Converter={x:Static sp:OrderBookView.LadderVisualStateConverter}}"/>
<u:StateDefinition State="{Binding DataContext.current_row, Source={u:DirectReference right_panel_content_root}, Converter={x:Static sp:OrderBookView.OrderParamsVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<VisualState Name="{x:Static None, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static OrderParams, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static OrderParamsLadder, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static OrderParamsFills, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static OrderParamsLadderFills, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}"/>
<VisualState Name="{x:Static Ladder, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static LadderFills, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static Fills, MemberType={u:Nested sp:OrderBookView.VisualStateNames.SlaveData}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.Ladder}}">
<VisualState Name="{x:Static OneSecuritySelected, MemberType={u:Nested sp:OrderBookView.VisualStateNames.Ladder}}"/>
<VisualState Name="{x:Static NoSecuritySelected, MemberType={u:Nested sp:OrderBookView.VisualStateNames.Ladder}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="No Orders Selected (select orders in the grid)"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="{x:Static ManySecuritiesSelected, MemberType={u:Nested sp:OrderBookView.VisualStateNames.Ladder}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ladder" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Multiple Securities Selected (select orders with a common security)"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.OrderDescription}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.OrderDescription}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.OrderDescription}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="no_rows_selected_text_block" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="current_row_description_content_control" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_description_root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Select orders in the grid to view corresponding details/parameters below."/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterAndMenuExpanderWidth}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
/>
<Grid x:Name="right_panel_host" Grid.Column="1" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding order_params_height, Converter={StaticResource GridLengthStarConverter}, Mode=TwoWay}"/>
<RowDefinition Height="5" MaxHeight="5"/>
<RowDefinition Height="{Binding ladder_height, Converter={StaticResource GridLengthStarConverter}, Mode=TwoWay}"/>
<RowDefinition Height="5" MaxHeight="5"/>
<RowDefinition Height="{Binding fills_height, Converter={StaticResource GridLengthStarConverter}, Mode=TwoWay}"/>
</Grid.RowDefinitions>
<ToolBar
x:Name="right_panel_tool_bar"
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemContainerStyle="{StaticResource RightPanel.MenuItem}"
BorderBrush="DarkGray"
BorderThickness="1"
>
<ToolBar.Background>
<SolidColorBrush Color="LightGray" Opacity="1"/>
</ToolBar.Background>
<u:StateManager.VisualState>
<Binding Path="right_panel_view_mode">
<Binding.Converter>
<cv:ValueMap Default="Tab">
<cv:KeyValue Key="{x:Static Tab, MemberType={u:Nested ob:OrderBookRightPanelSettings.ViewMode}}" Value="Tab"/>
<cv:KeyValue Key="{x:Static Stack, MemberType={u:Nested ob:OrderBookRightPanelSettings.ViewMode}}" Value="Stack"/>
</cv:ValueMap>
</Binding.Converter>
</Binding>
</u:StateManager.VisualState>
<Grid x:Name="right_panel_content_root">
<u:StateManager.Groups>
<u:StateGroupsManager Target="{Binding Source={u:DirectReference right_panel_content_root}}">
<u:StateDefinitionCollection>
<u:StateDefinition>
<MultiBinding Converter="{x:Static sp:OrderBookView.FillsSlaveGridStateConverter}">
<Binding Path="DataContext.fills_slave_grid_is_visible" Source="{u:DirectReference right_panel_content_root}"/>
<Binding Path="DataContext.is_attaching_algos" Source="{u:DirectReference right_panel_content_root}"/>
</MultiBinding>
</u:StateDefinition>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState Name="Tab">
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
<Storyboard>
<ThicknessAnimation Storyboard.TargetName="right_panel_tool_bar" Storyboard.TargetProperty="(Control.BorderThickness)" BeginTime="0" Duration="0" To="0"/>
<ColorAnimation Storyboard.TargetName="right_panel_tool_bar" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#44000000"/>
<DoubleAnimation Storyboard.TargetName="right_panel_tool_bar" Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Opacity)" BeginTime="0" Duration="0" To="0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="tab_remainder_menu_item" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_grid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
</Int32AnimationUsingKeyFrames>
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Stack"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<c:RadioButtonGroupManager.Manager>
<c:RadioButtonGroupManager SelectedItem="{Binding DataContext.right_panel_view_mode, Source={u:DirectReference right_panel_tool_bar}, Mode=TwoWay}"/>
</c:RadioButtonGroupManager.Manager>
<MenuItem x:Name="params_menu_item" ToolBar.OverflowMode="Never" Header="Params" IsChecked="{Binding show_order_params}" Icon="{x:Static u:Icons.ModifyOrder}" ToolTip="Show Order/Algo Parameters for Selected Orders">
<MenuItem.Resources>
<Thickness x:Key="IconPadding" Left="2" Top="3" Bottom="2"/>
</MenuItem.Resources>
</MenuItem>
<MenuItem x:Name="ladder_menu_item" ToolBar.OverflowMode="Never" Header="Ladder" IsChecked="{Binding show_ladder}" Icon="{x:Static u:Icons.Ladder}" ToolTip="Show Selected Orders in Ladder">
<MenuItem.Resources>
<Thickness x:Key="IconPadding" Left="2" Top="3" Bottom="2"/>
</MenuItem.Resources>
</MenuItem>
<MenuItem x:Name="fills_menu_item" ToolBar.OverflowMode="Never" Header="Fills" IsChecked="{Binding show_fills}" Icon="{x:Static u:Icons.Payment}" ToolTip="Show Fills for Selected Orders">
<MenuItem.Resources>
<Thickness x:Key="IconPadding" Left="2" Top="4.5" Bottom="4.5"/>
</MenuItem.Resources>
</MenuItem>
<MenuItem x:Name="tab_remainder_menu_item" ToolBar.OverflowMode="Never" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" IsCheckable="False" Visibility="Collapsed">
<MenuItem.Template>
<ControlTemplate TargetType="MenuItem">
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1">
<Rectangle.Fill>
<SolidColorBrush Color="{StaticResource TabBorderColor}"/>
</Rectangle.Fill>
</Rectangle>
</ControlTemplate>
</MenuItem.Template>
<MenuItem.Width>
<MultiBinding>
<MultiBinding.Converter>
<cv:RemainingDimensionConverter K="1"/>
</MultiBinding.Converter>
<Binding Path="ActualWidth" ElementName="params_menu_item"/>
<Binding Path="ActualWidth" ElementName="ladder_menu_item"/>
<Binding Path="ActualWidth" ElementName="fills_menu_item"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource AncestorType=ToolBar}"/>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterWidth}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition MinHeight="{StaticResource FillsSlaveGridMinHeight}" Height="{Binding fills_slave_grid_height, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}" MaxHeight="{Binding ActualHeight, ElementName=right_panel_content_root, Converter={x:Static sp:OrderBookView.FillsSlaveGridMaxHeightConverter}}"/>
<RowDefinition MinHeight="{x:Static sp:OrderBookView.GridSplitterHeight}" Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="fills_toggle_hyperlink" Grid.Column="1">
<TextBlock.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
</TextBlock.Margin>
<Hyperlink Command="{Binding toggle_fills_slave_grid_is_visible_cmd}">
<Hyperlink.Inlines>
<Run Text="{Binding current_row, Converter={x:Static sp:OrderBookView.OrderDescriptionConverter}, Mode=OneWay}"/>
</Hyperlink.Inlines>
</Hyperlink>
</TextBlock>
<c:DIYGrid x:Name="fills_grid" Grid.Column="1" Grid.Row="1" Columns="{Binding fills_vm.columns}" Rows="{Binding fills_vm.rows}" Visibility="Collapsed">
<c:DIYGrid.Margin>
<Thickness Bottom="{x:Static sp:OrderBookView.GridSplitterHeight}"/>
</c:DIYGrid.Margin>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
<GridSplitter
x:Name="fills_order_params_splitter"
Grid.Column="1"
Grid.Row="1"
Panel.ZIndex="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
BorderThickness="0,1,0,0"
Visibility="Collapsed"
/>
<GroupBox
x:Name="order_params_group_box"
Grid.Column="1"
Grid.Row="1"
Grid.RowSpan="2"
Padding="12"
BorderThickness="1"
HorizontalAlignment="Left"
Header="Order Parameters"
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
>
<usp:OrderParamsPanel
x:Name="order_params_panel"
DataContext="{Binding order_mod_vm}"
Price="{Binding price}"
Size="{Binding size_calculator.value}"
Accounts="{Binding accounts}"
SelectedAccount="{Binding selected_account}"
BestBidPrice="{Binding bbo.bid}"
BestBidSize="{Binding bbo.bid_size}"
BestAskPrice="{Binding bbo.ask}"
BestAskSize="{Binding bbo.ask_size}"
SizePresets="{Binding size_calculator.buttons}"
Security="{Binding security}"
AlgoVM="{Binding algo_vm}"
IsEnabled="{Binding IsEnabled, ElementName=modify_orders_button}"
>
<usp:OrderParamsPanel.CommandBindings>
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
</usp:OrderParamsPanel.CommandBindings>
</usp:OrderParamsPanel>
</GroupBox>
<GroupBox
x:Name="attachment_content_root"
Grid.Column="1"
Grid.Row="1"
Grid.RowSpan="2"
Padding="12"
BorderThickness="1"
HorizontalAlignment="Left"
Header="Attached Order Parameters"
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
Margin="0,4,0,0"
Visibility="Collapsed"
>
<usp:OrderParamsPanel
x:Name="attached_order_params_panel"
DataContext="{Binding attach_vm}"
Price="{Binding price}"
Size="{Binding size_calculator.value}"
Accounts="{Binding accounts}"
SelectedAccount="{Binding selected_account}"
BestBidPrice="{Binding bbo.bid}"
BestBidSize="{Binding bbo.bid_size}"
BestAskPrice="{Binding bbo.ask}"
BestAskSize="{Binding bbo.ask_size}"
SizePresets="{Binding size_calculator.buttons}"
Security="{Binding security}"
AlgoVM="{Binding algo_vm}"
IsEnabled="{Binding IsEnabled, ElementName=send_attached_orders_button}"
>
<usp:OrderParamsPanel.CommandBindings>
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
</usp:OrderParamsPanel.CommandBindings>
</usp:OrderParamsPanel>
</GroupBox>
</Grid>
<Canvas Grid.Row="1" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=order_params_group_box}" Height="30">
<Button
x:Name="modify_orders_button"
Canvas.Top="{StaticResource DialogSkirtPadding.Top}"
Command="{Binding modify_selected_algos_command}"
>
<Canvas.Left>
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelDialogSkirtLeftAnchorConverter}">
<Binding Path="Width" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</MenuItem.Width>
</MenuItem>
</ToolBar>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Vertical">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
</StackPanel.Margin>
<StackPanel x:Name="order_description_root" Margin="0,5,0,1" Orientation="Horizontal">
<StackPanel.ToolTip>
<ItemsControl ItemsSource="{Binding DataContext.selected_rows, Source={u:DirectReference right_panel_content_root}}" ItemTemplate="{StaticResource OrderDescriptionTemplate}"/>
</StackPanel.ToolTip>
<ContentControl x:Name="current_row_description_content_control" Content="{Binding current_row}" ContentTemplate="{StaticResource OrderDescriptionTemplate}" IsTabStop="False" Visibility="Visible"/>
<TextBlock x:Name="no_rows_selected_text_block" Visibility="Collapsed">
(No orders are selected)
</Canvas.Left>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Orders ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Modify Orders"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
Margin="3,0,0,0"
FontStyle="Italic"
Visibility="{Binding selected_rows.Count, Converter={StaticResource SelectedOrdersAndOthersSuffixVisibilityConverter}}"
VerticalAlignment="Top"
Text="{Binding selected_rows.Count, Converter={StaticResource MinusOneConverter}, Mode=OneWay, StringFormat='and {0} others...'}"
/>
</Button>
</Canvas>
<Canvas Grid.Row="1" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=attachment_content_root}" Height="30">
<StackPanel
x:Name="attach_orders_dialog_skirt"
Orientation="Horizontal"
Canvas.Top="{StaticResource DialogSkirtPadding.Top}"
Visibility="Collapsed"
>
<Canvas.Left>
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelDialogSkirtLeftAnchorConverter}">
<Binding Path="Width" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Canvas.Left>
<Button x:Name="send_attached_orders_button" Command="{Binding send_attached_orders_cmd}" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Attach To Orders ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Attach To Orders"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<Button u:Behavior.CloseWindow="False" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_attach_orders_cmd}" Style="{StaticResource CancelButton}"/>
</StackPanel>
</StackPanel>
<ScrollViewer x:Name="order_params_root" Grid.Column="1" Grid.Row="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Border Padding="12,12,12,6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<usp:AlgoEditorGridView
x:Name="algo_editor"
DataContext="{Binding order_mod_vm}"
AlgoRoots="{Binding algo_vm.algo_roots}"
SelectedAlgoRoot="{Binding algo_vm.algo_root}"
AlgoParamRows="{Binding param_rows}"
IsReadOnly="{Binding DataContext.can_user_modify_selected_orders, ElementName=Root, Converter={StaticResource NotConverter}}"
/>
<StackPanel Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,0,0" Orientation="Horizontal">
<Button x:Name="modify_orders_button" Command="{Binding modify_selected_orders_cmd}">
<Button.Margin>
<Thickness Right="{StaticResource DialogSkirtButtonPadding.Right}"/>
</Button.Margin>
<Button.ToolTip>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
<Binding Source="{x:Static ModifyOrders, MemberType={u:Nested sp:OrderBookView.CommandDescriptions}}"/>
<Binding Source="{x:Static ModifyOrders, MemberType={u:Nested sp:OrderBookView.KeyboardShortcuts}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button.ToolTip>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Orders ({0})'}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Text" Value="Modify Orders"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Button>
<Button Command="{Binding revert_selected_orders_cmd}">
<Button.Margin>
<Thickness Left="{StaticResource DialogSkirtButtonPadding.Left}"/>
</Button.Margin>
<Button.ToolTip>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{x:Static ut:CommandHelper.DescriptionConverter}">
<Binding Source="{x:Static RevertOrders, MemberType={u:Nested sp:OrderBookView.CommandDescriptions}}"/>
<Binding Source="{x:Static RevertOrders, MemberType={u:Nested sp:OrderBookView.KeyboardShortcuts}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Button.ToolTip>
Revert
</Button>
</StackPanel>
</Grid>
</Border>
</ScrollViewer>
<GridSplitter
x:Name="order_params_splitter"
Grid.Column="1"
Grid.Row="3"
Panel.ZIndex="1"
Width="{StaticResource GridSplitterWidth}"
VerticalAlignment="Stretch"
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
BorderThickness="0,1,0,0"
ResizeBehavior="PreviousAndNext"
/>
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding order_mod_vm.ladder_vm}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
<GridSplitter
x:Name="ladder_splitter"
Grid.Column="1"
Grid.Row="5"
Panel.ZIndex="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
BorderThickness="0,1,0,0"
ResizeBehavior="PreviousAndNext"
/>
<c:DIYGrid x:Name="fills_grid" Grid.Column="1" Grid.Row="6" DataContext="{Binding fills_vm}" Columns="{Binding columns}" Rows="{Binding rows}" IsTabStop="False">
<c:DIYGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Fills Settings" Command="{Binding Path='(sp:OrderBookView.DataContext).(sp:OrderBookVM.launch_fills_settings_cmd)', Source={u:DirectReference Root}}" Icon="{Binding Source={x:Static u:Icons.Admin}, Converter={StaticResource IconConverter}}"/>
</ContextMenu>
</c:DIYGrid.ContextMenu>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<usp:FillsRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
</Canvas>
</Grid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
@@ -1,218 +1,132 @@
using bts.api.orderman;
using bts.utils;
//#define EXPAND_ATTACHMENT_PANEL
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using OrderParamsVM = mbts.spark.OrderBookVM.OrderParamsVM;
namespace mbts.spark {
internal partial class OrderBookView : UserControl {
public const double
GridSplitterWidth = 5,
GridSplitterHeight = 5;
GridSplitterHeight = 5,
MinAlgoEditorHeight = 64,
MinAttachOrderParamsPanelWidth = 340,
AttachOrderParamsPanelWidthRatio = 0.33;
public static readonly IValueConverter
RightPanelVisualStateConverter = ValueConverter.create<bool, string>(show_panel => show_panel ? VisualStateNames.RightPanel.Active : VisualStateNames.RightPanel.Inactive),
OrderParamsVisualStateConverter = ValueConverter.create<OrderBookRowVM, string>(row => row != null ? VisualStateNames.OrderDescription.Active : VisualStateNames.OrderDescription.Inactive),
ToolBarMenuItemHeightConverter = ValueConverter.create<Thickness, double>(padding => padding.Top + 22 + padding.Bottom),
LadderVisualStateConverter = ValueConverter.create<Plurality, string>(security_plurality => {
switch (security_plurality) {
case Plurality.None: return VisualStateNames.Ladder.NoSecuritySelected;
case Plurality.One: return VisualStateNames.Ladder.OneSecuritySelected;
case Plurality.Many: return VisualStateNames.Ladder.ManySecuritiesSelected;
default: return null;
}
});
OrderDescriptionConverter = ValueConverter.create<OrderBookRowVM, string>(row => row == null ? string.Empty : row.order.short_description(row.security)),
FillsSlaveGridMaxHeightConverter = ValueConverter.create<double, double>(right_panel_height => right_panel_height - (GridSplitterHeight + MinAlgoEditorHeight)),
FillsPanelVisualStateConverter = ValueConverter.create<bool, string>(show => show ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive),
AttachmentTargetCursorConverter = ValueConverter.create<bool, Cursor>(is_active => is_active ? Cursors.Pen : Cursors.Arrow);
public static readonly IMultiValueConverter
RightPanelVisualStateConverter = ValueConverter.create<bool, bool, string>((is_modifying, is_attaching) =>
is_attaching ? VisualStateNames.RightPanel.AttachOrders :
(is_modifying ? VisualStateNames.RightPanel.ModifyOrders : VisualStateNames.RightPanel.Normal)),
RightPanelDialogSkirtLeftAnchorConverter = ValueConverter.create<double, double, double>((canvas_width, skirt_width) => canvas_width - skirt_width + GridSplitterWidth),
SlaveDataVisualStateConverter = ValueConverter.create<bool, bool, bool, string>(
(show_order_params, show_fills, show_ladder) => {
VisualStateNames.SlaveData.Data d = (VisualStateNames.SlaveData.Data)0;
if (show_order_params)
d |= VisualStateNames.SlaveData.Data.OrderParams;
if (show_fills)
d |= VisualStateNames.SlaveData.Data.Fills;
if (show_ladder)
d |= VisualStateNames.SlaveData.Data.Ladder;
return VisualStateNames.SlaveData.fmt_name(d);
});
IDisposable _attach_op;
FillsSlaveGridStateConverter = ValueConverter.create<bool, bool, string>((is_fills_grid_visible, is_attaching) => is_fills_grid_visible && !is_attaching ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive);
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
var algo_editor_cm = new ContextMenu();
algo_editor_cm.Items.AddRange(order_mod_menu());
algo_editor.ContextMenu = algo_editor_cm;
vm.SubscribeDisposed(
Disposable.Create(
algo_editor._param_grid.SubscribeIsEditing().SubscribeEach(is_editing => is_editing ? orders_grid.SubscribeGotFocus(on_orders_grid_got_focus) : null),
WPFHelper.connect_vertical_splitter_components(vm, right_panel_content_root.RowDefinitions, 2,
/*order params*/(order_params_root, nameof(OrderBookVM.order_params_height), vm.subscribe_show_order_params()),
/*mini ladder */(ladder, nameof(OrderBookVM.ladder_height), vm.subscribe_show_ladder()),
/*fills grid */(fills_grid, nameof(OrderBookVM.fills_height), vm.subscribe_show_fills())))
);
// 'send attach orders' behavior
vm.begin_attaching += begin_attach_orders;
vm.end_attaching += () => Disposable.dispose(ref _attach_op);
// 'scroll to current order' behavior (mini ladder)
vm.SubscribeDisposed(vm.subscribe_show_ladder().SubscribeEach(show => show ? connect_mini_ladder_auto_scroll() : null));
IDisposable connect_mini_ladder_auto_scroll() {
bool is_busy = false;
vm.selected_order_changed += on_selected_order_changed;
return Disposable.Create(() => vm.selected_order_changed -= on_selected_order_changed);
async void on_selected_order_changed(OrderRecordImmutable? order) {
if (order.HasValue && !is_busy) {
is_busy = true;
await Task.Delay(200);
if (vm.current_order.HasValue && vm.current_order.Value.security_key() == ladder.ladder_grid_host.security.Id && vm.current_order.Value.is_active())
ladder.ladder_grid_host.scroll_to_price(vm.current_order.Value.price());
is_busy = false;
#if EXPAND_ATTACHMENT_PANEL
(RightPanelState right_panel, IDisposable hooks)? attach_op = null;
vm.begin_attaching += on_begin_attaching;
vm.end_attaching += on_end_attaching;
vm.SubscribeDisposed(() => {
if (attach_op.HasValue) {
vm.right_panel_width = attach_op.Value.right_panel.raw_width;
vm.fills_slave_grid_is_visible = attach_op.Value.right_panel.fills_slave_grid_is_visible;
}
vm.begin_attaching -= on_begin_attaching;
vm.end_attaching -= on_end_attaching;
});
const int delay_ms = 25;
async void on_begin_attaching() {
if (attach_op.HasValue) {
// begin-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
return;
}
attach_op = (new RightPanelState(vm.right_panel_is_visible, vm.fills_slave_grid_is_visible, vm.raw_right_panel_width), orders_grid.SubscribePreviewMouseDown(cancel_on_grid_click));
vm.fills_slave_grid_is_visible = false;
vm.algo_editor_is_visible = true;
double target_width = Math.Max(ActualWidth * AttachOrderParamsPanelWidthRatio, MinAttachOrderParamsPanelWidth);
if (vm.right_panel_width < target_width) {
foreach (double incr in _get_incremental_widths(attach_op.Value.right_panel.is_visible ? attach_op.Value.right_panel.raw_width : 0, target_width)) {
vm.right_panel_width = incr;
await Task.Delay(delay_ms);
if (!attach_op.HasValue)
break;
}
}
}
void on_orders_grid_got_focus(object sender, RoutedEventArgs e) {
if (e.OriginalSource is ListBoxItem && algo_editor._param_grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing)
algo_editor._param_grid.CancelEdit();
async void on_end_attaching() {
if (!attach_op.HasValue) {
// end-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
return;
}
attach_op.Value.hooks.Dispose();
var incr_widths = _get_incremental_widths(vm.right_panel_width, attach_op.Value.right_panel.raw_width);
vm.algo_editor_is_visible = attach_op.Value.right_panel.is_visible;
vm.fills_slave_grid_is_visible = attach_op.Value.right_panel.fills_slave_grid_is_visible;
vm.right_panel_width = attach_op.Value.right_panel.raw_width;
attach_op = null;
foreach (double incr in incr_widths) {
vm.right_panel_width = incr;
await Task.Delay(delay_ms);
if (attach_op.HasValue)
break;
}
}
IEnumerable order_mod_menu() {
var revert_cell_mi = new MenuItem();
var revert_cell_cmd = new DelegateCommand(revert_impl, () => get_current_row()?.param?.is_dirty ?? false);
revert_cell_mi.Command = revert_cell_cmd;
IDisposable param_is_dirty_sub = null;
algo_editor._param_grid.CurrentCellChanged += on_current_cell_changed;
// menu items
yield return revert_cell_mi;
yield return new MenuItem {
Header = CommandHelper.fmt("Revert All", KeyboardShortcuts.RevertOrders),
Command = vm.revert_selected_orders_cmd
};
yield return new Separator();
yield return new MenuItem {
Header = CommandHelper.fmt("Modify Orders", KeyboardShortcuts.ModifyOrders),
Command = CommandWithActionWrapper.wrap(vm.modify_selected_orders_cmd, algo_editor.commit_edit)
};
// local methods
AlgoParamRowVM get_current_row() => algo_editor._param_grid.SelectedItem as AlgoParamRowVM;
void on_current_cell_changed(object sender, EventArgs e) {
Disposable.dispose(ref param_is_dirty_sub);
var r = get_current_row();
var header = new StringBuilderEx("Revert ");
revert_cell_cmd.RaiseCanExecuteChanged();
if (r != null) {
header.Add("'", r.param.display_name, "'");
r.param.is_dirty_changed += on_is_dirty_changed;
param_is_dirty_sub = Disposable.Create(dispose);
// local methods
void on_is_dirty_changed(bool _) => revert_cell_cmd.RaiseCanExecuteChanged();
void dispose() => r.param.is_dirty_changed -= on_is_dirty_changed;
} else {
header.Add("(Param)");
}
revert_cell_mi.Header = header.ToString();
}
void revert_impl() {
if (get_current_row() is AlgoParamRowVM r) {
algo_editor.cancel_edit();
r.param.revert();
}
}
#else
IDisposable attach_op = null;
vm.begin_attaching += on_begin_attaching;
vm.end_attaching += on_end_attaching;
void on_begin_attaching() => Disposable.replace(ref attach_op, orders_grid.SubscribePreviewMouseDown(cancel_on_grid_click));
void on_end_attaching() => Disposable.dispose(ref attach_op);
#endif
void cancel_on_grid_click(object sender, MouseButtonEventArgs e) {
if (!KeyboardExtensions.IsCtrlDown() && !KeyboardExtensions.IsShiftDown())
vm.is_attaching_algos = false;
}
}
public new OrderBookVM DataContext {
get => base.DataContext as OrderBookVM;
set => base.DataContext = value;
static IEnumerable<double> _get_incremental_widths(double from, double to) {
var list = new List<double> { to };
int incr = 1;
if (from < to) {
do {
list.Add(to - (incr * 0.6666));
list.Add(to - incr);
from += incr;
incr += incr;
} while (from < to);
} else if (from > to) {
do {
list.Add(to + incr);
incr += incr;
to += incr;
} while (from > to);
}
for (int i = list.Count - 1; i >= 0; i--) {
yield return list[i];
}
}
public void begin_attach_orders() {
Disposable.dispose(ref _attach_op);
var vm = DataContext.attach_vm;
// attach orders view
var attach_orders_view = AlgoEditorGridView.create_attach_view();
attach_orders_view.DataContext = vm;
attach_orders_view.StretchValueColumn = false;
attach_orders_view.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(string.Join(".", nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))) { Source = vm });
attach_orders_view.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(string.Join(".", nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))) { Source = vm });
attach_orders_view.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)) { Source = vm });
// dialog skirt
var dialog_skirt = new DialogSkirt {
RightItems = new[] {
new DialogSkirtItem("Attach To Orders", DataContext.send_attached_orders_cmd) { is_default = true },
new DialogSkirtItem("Cancel", new DelegateCommand(() => Disposable.dispose(ref _attach_op))) { is_cancel = true }
}
};
Grid.SetRow(dialog_skirt, 1);
// grid
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.Children.Add(attach_orders_view);
grid.Children.Add(dialog_skirt);
// popup adorner
var popup_adorner = new PopupAdorner(
orders_grid, orders_grid,
new Border {
Child = grid,
Background = BTSColors.common_background_brush,
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(1)
},
HorizontalPosition.Middle,
VerticalPosition.Middle,
0,
orders_grid.RowHeight) {
OpenRelativeToMouse = true,
ClipToBounds = true
};
popup_adorner.attach();
popup_adorner.IsOpen = true;
// attach op (dispose)
_attach_op = new CompositeDisposable(orders_grid.SubscribePreviewMouseDown((s, e) => {
if (!KeyboardExtensions.IsShiftDown() && !KeyboardExtensions.IsCtrlDown() && !popup_adorner.PopupElement.IsMouseOver)
Disposable.dispose(ref _attach_op);
})) {
() => {
popup_adorner.IsOpen = false;
popup_adorner.detach();
DataContext.is_attaching_orders = false;
}
};
}
struct RightPanelState {
public readonly bool is_visible, fills_slave_grid_is_visible;
public readonly double raw_width;
public static class CommandDescriptions {
public static readonly string
ShowRightPanel = "Show Details Panel",
HideRightPanel = "Hide Details Panel",
ModifyOrders = "Modify selected orders",
RevertOrders = "Revert modifications to selected orders";
}
public static class KeyboardShortcuts {
public static readonly KeyGesture
ToggleShowRightPanel = new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift),
ModifyOrders = new KeyGesture(Key.S, ModifierKeys.Control | ModifierKeys.Shift),
RevertOrders = new KeyGesture(Key.R, ModifierKeys.Control);
public RightPanelState(bool right_panel_is_visible, bool fills_slave_grid_is_visible, double raw_right_panel_width) {
this.is_visible = right_panel_is_visible;
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
this.raw_width = raw_right_panel_width;
}
}
public static class VisualStateNames {
@@ -220,51 +134,18 @@ namespace mbts.spark {
public const string GroupName = nameof(RightPanel);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
Normal = $"{GroupName}{nameof(Normal)}",
ModifyOrders = $"{GroupName}{nameof(ModifyOrders)}",
AttachOrders = $"{GroupName}{nameof(AttachOrders)}";
}
public static class OrderDescription {
public const string GroupName = nameof(OrderDescription);
public static class FillsPanel {
public const string GroupName = nameof(FillsPanel);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class Ladder {
public const string GroupName = nameof(Ladder);
public static readonly string
NoSecuritySelected = $"{GroupName}{nameof(NoSecuritySelected)}",
OneSecuritySelected = $"{GroupName}{nameof(OneSecuritySelected)}",
ManySecuritiesSelected = $"{GroupName}{nameof(ManySecuritiesSelected)}";
}
public static class SlaveData {
public const string GroupName = nameof(SlaveData);
public static readonly string
None = $"{GroupName}{nameof(None)}",
OrderParams = $"{GroupName}{nameof(OrderParams)}",
OrderParamsLadder = $"{GroupName}{nameof(OrderParamsLadder)}",
OrderParamsFills = $"{GroupName}{nameof(OrderParamsFills)}",
OrderParamsLadderFills = $"{GroupName}{nameof(OrderParamsLadderFills)}",
LadderFills = $"{GroupName}{nameof(LadderFills)}",
Ladder = $"{GroupName}{nameof(Ladder)}",
Fills = $"{GroupName}{nameof(Fills)}";
public enum Data {
OrderParams = 1,
Ladder = 1 << 1,
Fills = 1 << 2
}
public static string fmt_name(Data d) {
var vis_data = string.Join("", EnumHelper.split_flags(d));
return vis_data == string.Empty ? None : GroupName + vis_data;
}
}
}
}
}
}
@@ -1,285 +1,143 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<cv:MultiValueConverterPipe x:Key="CalculatorButtonsVisualStateConverter">
<cv:BooleanNoneConverter/>
<cv:ValueMap Default="{x:Static Normal, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.CalculatorButtons}}">
<cv:KeyValue Key="{u:True}" Value="{x:Static Stretched, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.CalculatorButtons}}"/>
</cv:ValueMap>
</cv:MultiValueConverterPipe>
<cv:MultiValueConverterPipe x:Key="CalculatorButtonsColumnsConverter">
<cv:BooleanNoneConverter/>
<cv:ValueMap Default="{u:Int 3}">
<cv:KeyValue Key="{u:True}" Value="{u:Int 6}"/>
</cv:ValueMap>
</cv:MultiValueConverterPipe>
<ControlTemplate x:Key="OrderParamsPanelTemplate" TargetType="usp:OrderParamsPanel">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(usp:OrderParamsPanel.IsReadOnly)', RelativeSource={RelativeSource Self}, Converter={x:Static usp:OrderParamsPanel.GeneralVisualStateConverter}}"/>
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(usp:OrderParamsPanel.ShowBBO)', RelativeSource={RelativeSource Self}, Converter={x:Static usp:OrderParamsPanel.BBOVisualStateConverter}}"/>
<u:StateDefinition>
<MultiBinding Converter="{StaticResource CalculatorButtonsVisualStateConverter}">
<Binding Path="(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(usp:OrderParamsPanel.ShowAccount)" RelativeSource="{RelativeSource Self}"/>
<Binding Path="(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent).(usp:OrderParamsPanel.ShowTimeInForce)" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</u:StateDefinition>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.General}}">
<VisualState Name="{x:Static Normal, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.General}}"/>
<VisualState Name="{x:Static ReadOnly, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.General}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="price_root" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="size_and_auxiliary_root" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="algo_editor" Storyboard.TargetProperty="(usp:AlgoEditorGridView.IsReadOnly)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.BBO}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.BBO}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.BBO}}">
<Storyboard>
<!--Price-->
<Int32Animation Storyboard.TargetName="price_entry_control" Storyboard.TargetProperty="(Grid.Column)" BeginTime="0" Duration="0" To="0"/>
<Int32Animation Storyboard.TargetName="price_entry_control" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="5"/>
<ThicknessAnimation Storyboard.TargetName="price_entry_control" Storyboard.TargetProperty="(Control.Margin)" BeginTime="0" Duration="0" To="40,0,0,0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="price_label" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<!--Size-->
<Int32Animation Storyboard.TargetName="size_tick_up_down" Storyboard.TargetProperty="(Grid.Column)" BeginTime="0" Duration="0" To="0"/>
<Int32Animation Storyboard.TargetName="size_tick_up_down" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="5"/>
<ThicknessAnimation Storyboard.TargetName="size_tick_up_down" Storyboard.TargetProperty="(Control.Margin)" BeginTime="0" Duration="0" To="40,0,0,0"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="size_label" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<!--BBO Buttons-->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="best_bid_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="best_bid_size_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="best_ask_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="best_ask_size_button" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.CalculatorButtons}}">
<VisualState Name="{x:Static Normal, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.CalculatorButtons}}"/>
<VisualState Name="{x:Static Stretched, MemberType={u:Nested usp:OrderParamsPanel.VisualStateNames.CalculatorButtons}}">
<Storyboard>
<Int32Animation Storyboard.TargetName="calculator_buttons_items_control" Storyboard.TargetProperty="(Grid.Column)" BeginTime="0" Duration="0" To="0"/>
<Int32Animation Storyboard.TargetName="calculator_buttons_items_control" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="5"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Grid.IsSharedSizeScope="True">
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<usp:PriceEntryControl
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ToolTip="Price"
/>
<Button
x:Name="best_bid_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
<Button
x:Name="best_ask_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="price_root">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="price_label" Margin="8,0,0,0" Text="Price" Visibility="Collapsed"/>
<usp:PriceEntryControl
x:Name="price_entry_control"
Grid.Column="0"
Grid.ColumnSpan="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ToolTip="Price"
IsTabStop="False"
/>
<Button
x:Name="best_bid_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
<Button
x:Name="best_ask_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
FontWeight="Bold"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
>
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
</MultiBinding>
</Button>
</Grid>
<Grid x:Name="size_and_auxiliary_root" Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="size_label" Margin="8,0,0,0" Text="Size" Visibility="Collapsed"/>
<c:TickUpDownNullableUInt x:Name="size_tick_up_down" Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
<Button
x:Name="best_bid_size_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
/>
<Button
x:Name="best_ask_size_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
/>
<ItemsControl
x:Name="calculator_buttons_items_control"
Grid.Row="2"
Grid.RowSpan="2"
Margin="-4,-4,0,0"
ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}"
IsTabStop="False"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Focusable="False">
<UniformGrid.Columns>
<MultiBinding Converter="{StaticResource CalculatorButtonsColumnsConverter}">
<Binding Path="ShowAccount" RelativeSource="{RelativeSource AncestorType=usp:OrderParamsPanel}"/>
<Binding Path="ShowTimeInForce" RelativeSource="{RelativeSource AncestorType=usp:OrderParamsPanel}"/>
</MultiBinding>
</UniformGrid.Columns>
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
HorizontalAlignment="Stretch"
Margin="4,4,0,0"
Content="{Binding}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox
x:Name="account_combo_box"
Margin="0,2,0,4"
ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}"
SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}"
ToolTip="Choose Account"
/>
<ComboBox x:Name="time_in_force_combo_box" Grid.Row="1">
<ComboBox.Style>
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
<Setter Property="ItemsSource">
<Setter.Value>
<x:ArrayExtension Type="{x:Type sys:String}">
<sys:String>Time In Force (N/A)</sys:String>
</x:ArrayExtension>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</Grid>
<usp:AlgoEditorGridView
x:Name="algo_editor"
Grid.Row="4"
AlgoRoots="{Binding AlgoRoots, RelativeSource={RelativeSource TemplatedParent}}"
SelectedAlgoRoot="{Binding SelectedAlgoRoot, RelativeSource={RelativeSource TemplatedParent}}"
AlgoParamRows="{Binding AlgoParamRows, RelativeSource={RelativeSource TemplatedParent}}"
CanUserChangeAlgoRoot="{TemplateBinding CanUserChangeAlgoRoot}"
<c:TickUpDownNullableUInt Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
<Button
x:Name="best_bid_size_button"
Grid.Column="2"
HorizontalAlignment="Stretch"
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.bid}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
/>
<Button
x:Name="best_ask_size_button"
Grid.Column="4"
HorizontalAlignment="Stretch"
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{u:BTSColor MarketData.ask}"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
/>
<ItemsControl Grid.Row="2" Grid.RowSpan="2" Margin="-2,-2,-2,0" ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Focusable="False"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
HorizontalAlignment="Stretch"
Margin="2,4,2,0"
Content="{Binding}"
Focusable="False"
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Margin="0,2,0,4" ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}" SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Choose Account"/>
<ComboBox Grid.Row="1">
<ComboBox.Style>
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
<Setter Property="ItemsSource">
<Setter.Value>
<x:ArrayExtension Type="{x:Type sys:String}">
<sys:String>Time In Force (N/A)</sys:String>
</x:ArrayExtension>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ShowAccount" Value="False">
<Setter TargetName="account_combo_box" Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="ShowTimeInForce" Value="False">
<Setter TargetName="time_in_force_combo_box" Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="ShowCalculatorButtons" Value="False">
<Setter TargetName="calculator_buttons_items_control" Property="Visibility" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
<usp:AlgoEditorView x:Name="algo_editor" Grid.Row="4" DataContext="{Binding AlgoVM, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
<Style TargetType="usp:OrderParamsPanel">
<Setter Property="MinWidth" Value="300"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template" Value="{StaticResource OrderParamsPanelTemplate}"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>
@@ -1,15 +1,10 @@
using bts.api;
using bts.collections;
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Collections;
using System.Linq;
using System.Reactive.Disposables;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui.spark {
@@ -33,25 +28,9 @@ namespace mbts.ui.spark {
SetPriceCanExecute = (s, e) => e.CanExecute = e.Parameter is double,
SetSizeCanExecute = (s, e) => e.CanExecute = e.Parameter is uint;
public static readonly IValueConverter
GeneralVisualStateConverter = ValueConverter.create<bool, string>(is_read_only => is_read_only ? VisualStateNames.General.ReadOnly : VisualStateNames.General.Normal),
BBOVisualStateConverter = ValueConverter.create<bool, string>(show_bbo => show_bbo ? VisualStateNames.BBO.Active : VisualStateNames.BBO.Inactive);
AlgoEditorView _algo_editor;
IDisposable _bbo_button_navigation_hooks;
static OrderParamsPanel() =>
static OrderParamsPanel() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(OrderParamsPanel), new FrameworkPropertyMetadata(typeof(OrderParamsPanel)));
#region IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(IsReadOnly));
public bool IsReadOnly {
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
#endregion
#region Price
public static readonly DependencyProperty PriceProperty =
@@ -123,46 +102,6 @@ namespace mbts.ui.spark {
}
#endregion
#region ShowBBO
public static readonly DependencyProperty ShowBBOProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(ShowBBO), true);
public bool ShowBBO {
get => (bool)GetValue(ShowBBOProperty);
set => SetValue(ShowBBOProperty, value);
}
#endregion
#region ShowCalculatorButtons
public static readonly DependencyProperty ShowCalculatorButtonsProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(ShowCalculatorButtons), true);
public bool ShowCalculatorButtons {
get => (bool)GetValue(ShowCalculatorButtonsProperty);
set => SetValue(ShowCalculatorButtonsProperty, value);
}
#endregion
#region ShowTimeInForce
public static readonly DependencyProperty ShowTimeInForceProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(ShowTimeInForce), true);
public bool ShowTimeInForce {
get => (bool)GetValue(ShowTimeInForceProperty);
set => SetValue(ShowTimeInForceProperty, value);
}
#endregion
#region ShowAccount
public static readonly DependencyProperty ShowAccountProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(ShowAccount), true);
public bool ShowAccount {
get => (bool)GetValue(ShowAccountProperty);
set => SetValue(ShowAccountProperty, value);
}
#endregion
#region Accounts
public static readonly DependencyProperty AccountsProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(Accounts), Enumerable.Empty<string>());
@@ -228,126 +167,22 @@ namespace mbts.ui.spark {
}
#endregion
#region AlgoRoots
public static readonly DependencyProperty AlgoRootsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<IAlgoVM>, OrderParamsPanel>(nameof(AlgoRoots));
#region AlgoVM
public static readonly DependencyProperty AlgoVMProperty =
WPFHelper.create_dp<AlgoDefinitionVM, OrderParamsPanel>(
nameof(AlgoVM),
(o, v) => o._algo_editor?.SetCurrentValue(VisibilityProperty, v != null ? Visibility.Visible : Visibility.Collapsed));
public IReadOnlyObservableCollection<IAlgoVM> AlgoRoots {
get => GetValue(AlgoRootsProperty) as IReadOnlyObservableCollection<IAlgoVM>;
set => SetValue(AlgoRootsProperty, value);
}
#endregion
#region SelectedAlgoRoot
public static readonly DependencyProperty SelectedAlgoRootProperty =
WPFHelper.create_dp<IAlgoVM, OrderParamsPanel>(nameof(SelectedAlgoRoot));
public IAlgoVM SelectedAlgoRoot {
get => GetValue(SelectedAlgoRootProperty) as IAlgoVM;
set => SetValue(SelectedAlgoRootProperty, value);
}
#endregion
#region AlgoParamRows
public static readonly DependencyProperty AlgoParamRowsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<AlgoParamRowVM>, OrderParamsPanel>(nameof(AlgoParamRows));
public IReadOnlyObservableCollection<AlgoParamRowVM> AlgoParamRows {
get => GetValue(AlgoParamRowsProperty) as IReadOnlyObservableCollection<AlgoParamRowVM>;
set => SetValue(AlgoParamRowsProperty, value);
}
#endregion
#region CanUserChangeAlgoRoot
public static readonly DependencyProperty CanUserChangeAlgoRootProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(CanUserChangeAlgoRoot));
public bool CanUserChangeAlgoRoot {
get => (bool)GetValue(CanUserChangeAlgoRootProperty);
set => SetValue(CanUserChangeAlgoRootProperty, value);
}
#endregion
#region ShowAlgoTypeNodes
public static readonly DependencyProperty ShowAlgoTypeNodesProperty =
WPFHelper.create_dp<bool, OrderParamsPanel>(nameof(ShowAlgoTypeNodes));
public bool ShowAlgoTypeNodes {
get => (bool)GetValue(ShowAlgoTypeNodesProperty);
set => SetValue(ShowAlgoTypeNodesProperty, value);
public AlgoDefinitionVM AlgoVM {
get => GetValue(AlgoVMProperty) as AlgoDefinitionVM;
set => SetValue(AlgoVMProperty, value);
}
#endregion
AlgoEditorView _algo_editor;
public override void OnApplyTemplate() {
Disposable.replace(ref _bbo_button_navigation_hooks, new CompositeDisposable());
_algo_editor = GetTemplateChild("algo_editor") as AlgoEditorView;
// default directional navigation is wrong, so diy!
Disposable.replace(ref _bbo_button_navigation_hooks, _connect_bbo_button_navigation(
GetTemplateChild("price_entry_control") as FrameworkElement,
GetTemplateChild("size_tick_up_down") as FrameworkElement,
GetTemplateChild("best_bid_button") as Button,
GetTemplateChild("best_bid_size_button") as Button,
GetTemplateChild("best_ask_button") as Button,
GetTemplateChild("best_ask_size_button") as Button,
GetTemplateChild("calculator_buttons_items_control") as ItemsControl));
}
static IDisposable _connect_bbo_button_navigation(FrameworkElement price, FrameworkElement size, Button best_bid, Button best_bid_size, Button best_ask, Button best_ask_size, ItemsControl calculator_buttons) {
// BBO Orientation is as follows:
// Price Entry | Best Bid Price | Best Ask Price
// Size Entry | Best Bid Size | Best Ask Size
// Button | Button | Button | Button | Button | etc. (calculator buttons)
return Disposable.Create(
NavigationHelper.connect_navigation(price, null, null, best_bid, size, ModifierKeys.Control),
NavigationHelper.connect_navigation(best_bid, price, null, best_ask, best_bid_size),
NavigationHelper.connect_navigation(best_ask, best_bid, null, null, best_ask_size),
NavigationHelper.connect_navigation(size, () => null, () => price, () => best_bid_size, () => calculator_buttons.ItemContainerGenerator.ContainerFromIndex(0) as FrameworkElement, ModifierKeys.Control),
NavigationHelper.connect_navigation(best_bid_size, () => size, () => best_bid, () => best_ask_size, () => get_y_neighbor(best_bid_size)),
NavigationHelper.connect_navigation(best_ask_size, () => best_bid_size, () => best_ask, () => null, () => get_y_neighbor(best_ask_size)),
NavigationHelper.connect_navigation(calculator_buttons, e => e.OriginalSource as FrameworkElement, el => get_adjacent_calc_button(el, -1), el => get_y_neighbor(el, -1), el => get_adjacent_calc_button(el, 1), el => null));
// local methods
FrameworkElement get_y_neighbor(FrameworkElement control, int direction = 1) {
if (control.try_find_ancestor_of_type(out Grid grid)) {
var pt = control.TranslatePoint(new Point(control.ActualWidth / 2, control.ActualHeight / 2), grid);
// this is a hack, but since it is only for keyboard directional navigation, it's not the end of the world if this breaks (should clean this up when time/priorities permit).
// as long as the row heights are roughly uniform, and the controls are vertically stretched, this should be fine.
return grid.ChildAtPoint<Button>(new Point(pt.X, pt.Y + (control.ActualHeight * direction)));
} else return null;
}
FrameworkElement get_adjacent_calc_button(FrameworkElement item, int direction) {
int i = calculator_buttons.ItemContainerGenerator.IndexFromContainer(item.find_ancestor_of_type<ContentPresenter>());
if ((i + direction >= 0) && (i + direction) < calculator_buttons.Items.Count) {
var target_el = calculator_buttons.ItemContainerGenerator.ContainerFromIndex(i + direction) as FrameworkElement;
return target_el?.descendants_of_type<Button>()?.FirstOrDefault();
}
return null;
}
}
public static class VisualStateNames {
public static class BBO {
public const string GroupName = nameof(BBO);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class CalculatorButtons {
public const string GroupName = nameof(CalculatorButtons);
public static readonly string
Normal = $"{GroupName}{nameof(Normal)}",
Stretched = $"{GroupName}{nameof(Stretched)}";
}
public static class General {
public const string GroupName = nameof(General);
public static readonly string
Normal = $"{GroupName}{nameof(Normal)}",
ReadOnly = $"{GroupName}{nameof(ReadOnly)}";
}
_algo_editor?.SetCurrentValue(VisibilityProperty, AlgoVM != null ? Visibility.Visible : Visibility.Collapsed);
}
}
}
@@ -1,4 +1,5 @@
using bts.api;
using bts.api.md.auxiliary;
using bts.spark;
using mbts.persist;
using mbts.spark;
@@ -21,8 +22,8 @@ namespace bts.shell {
[ImportingConstructor]
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache,
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, ErrorReporter error_reporter) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model);
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, MarketDataAuxiliaryModel md_auxiliary_model, ErrorReporter error_reporter) {
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, md_auxiliary_model);
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
@@ -36,8 +37,8 @@ namespace bts.shell {
public int priority => 20;
public IEnumerable<SparkCommand> actions => new [] {
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
SparkCommands.ShowFills(null).complete(() => new FillsWindowFactory.StartupData()),
SparkCommands.ShowOrders.complete(() => OrderBookWindowFactory.OrderBookStartupData.Default),
SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData()),
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
SparkCommands.PickLadder,
SparkCommands.PickOrderTicket,
@@ -1,7 +1,6 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts;
using mbts.collections;
@@ -56,7 +55,7 @@ namespace bts.shell {
void _on_activity(Activity activity) =>
_activity_notifications.process_item(activity);
void _on_orders(TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> update) {
void _on_orders(TableUpdate<(SecurityImmutable?,OrderRecordImmutable),uint> update) {
// must update notification_service_data_cache first, some of the predicate/triggers (esp. ToastNotifications) will use the notification_service_data_cache to lookup order-history
_data_cache.update(update.as_iincremental());
foreach (var o in update.added.Concat(update.changed))
@@ -2,16 +2,19 @@
using bts.api;
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.config;
using mbts.persist;
using mbts.spark;
using mbts.ui.extensions;
using mbts.ui.utility;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
-119
View File
@@ -1,119 +0,0 @@
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
namespace mbts.spark {
class AlgoParamVM<T> : IAlgoParamVM<T>, INotifyPropertyChanged {
readonly IEqualityComparer<T> _comparer;
AlgoParamPermissions _permissions;
bool _is_enabled, _has_value;
T _committed_value, _value;
public AlgoParamVM(string id, string display_name)
: this(id, display_name, default, EqualityComparer<T>.Default) { }
public AlgoParamVM(string id, string display_name, T value)
: this(id, display_name, value, EqualityComparer<T>.Default) { }
public AlgoParamVM(string id, string display_name, T value, IEqualityComparer<T> comparer) {
this.id = id;
this.display_name = display_name;
_value = value;
_committed_value = value;
_comparer = comparer;
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChange<T> value_changed;
public event PropertyChange<AlgoParamPermissions> permissions_changed;
public event Action<bool> is_dirty_changed;
public string id { get; }
public string display_name { get; }
public T value {
get => _value;
set {
if (_set_value(value) && (is_dirty == _comparer.Equals(value, _committed_value)))
_toggle_is_dirty();
}
}
public AlgoParamPermissions permissions {
get => _permissions;
set {
if (_set(ref _permissions, value, out var oldval))
permissions_changed?.Invoke(oldval, value);
}
}
public bool is_enabled { get => _is_enabled; set => _set(ref _is_enabled, value); }
public bool has_value { get => _has_value; set => _set(ref _has_value, value); }
public bool is_dirty { get; private set; }
object IAlgoParamVM.value { get => value; set => this.value = (T)value; }
public void revert() {
if (is_dirty) {
_set_value_core(_committed_value);
_toggle_is_dirty();
}
}
public IObservable<T> subscribe_value() {
return Observable.Create<T>(o => {
o.OnNext(_value);
value_changed += on_value_changed;
return Disposable.Create(() => value_changed -= on_value_changed);
void on_value_changed(T oldval, T newval) =>
o.OnNext(newval);
});
}
public void mark_committed() {
_committed_value = _value;
if (is_dirty)
_toggle_is_dirty();
}
bool _set<TValue>(ref TValue backing, TValue new_value, [CallerMemberName]string prop = "") =>
_set(ref backing, new_value, EqualityComparer<TValue>.Default, prop);
bool _set<TValue>(ref TValue backing, TValue new_value, out TValue old_value, [CallerMemberName]string prop = "") =>
_set(ref backing, new_value, out old_value, EqualityComparer<TValue>.Default, prop);
bool _set<TValue>(ref TValue backing, TValue new_value, out TValue old_value, IEqualityComparer<TValue> comparer, [CallerMemberName]string prop = "") {
old_value = backing;
return _set(ref backing, new_value, comparer, prop);
}
bool _set<TValue>(ref TValue backing, TValue new_value, IEqualityComparer<TValue> comparer, [CallerMemberName]string prop = "") {
if (comparer.Equals(backing, new_value))
return false;
backing = new_value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
return true;
}
bool _set_value(T new_value) {
if (_comparer.Equals(new_value, _value))
return false;
_set_value_core(new_value);
return true;
}
void _set_value_core(T new_value) {
if (value_changed == null) {
_value = new_value;
} else {
value_changed(_value, _value = new_value);
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(value)));
}
void _toggle_is_dirty() {
is_dirty = !is_dirty;
is_dirty_changed?.Invoke(is_dirty);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(is_dirty)));
}
}
}
@@ -28,7 +28,7 @@ namespace bts.spark {
static readonly IEqualityComparer<OrderRecordImmutable> _id_comparer =
bts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint>> orders) {
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<(SecurityImmutable? sec,OrderRecordImmutable order), uint>> orders) {
_bus = bus;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec => _security = sec),
@@ -57,18 +57,15 @@ namespace mbts.spark {
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
readonly SelectorProperty<TimeInForce> _time_in_force;
readonly SelectionModel<AlgoDefinitionVM> _selected_algo_definition;
readonly SetStack<AlgoKey> _selected_algo_stack = new SetStack<AlgoKey>();
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly ListCollectionView<AlgoDefinitionVM> _algo_definitions_view, _favorite_algo_definitions_view;
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(d => d.key);
readonly EditableCollectionModel<AlgoDefinitionTemplateVM, AlgoDefinitionVM> _algo_template_collection_model;
readonly ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey> _favorite_templates = new ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey>(tvm => tvm.key);
readonly ObservableAggregateCollection<AlgoParamRowVM> _algo_param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _algo_metadata = DictionaryHelper.empty<AlgoKey, AlgoDefinitionMetadata>();
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
IDisposable _algo_param_rows_hook;
IReadOnlyDictionary<AlgoKey, int> _favorite_algos_index = DictionaryHelper.empty<AlgoKey, int>();
int _favorite_algos_column_count;
bool _open_algo_popup_on_select, _use_algo_param_grid;
bool _open_algo_popup_on_select;
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineOrderProperties> order_properties) {
_model = model;
@@ -92,9 +89,8 @@ namespace mbts.spark {
return (d.selected_format & sec_fmt) == sec_fmt;
};
_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.selected_format));
_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.group));
_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.name));
algo_definitions_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(AlgoDefinitionVM.group)));
algo_definitions_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(AlgoDefinitionVM.is_factory_definition)));
_favorite_algo_definitions_view = new ListCollectionView<AlgoDefinitionVM>(_algo_definitions);
_favorite_algo_definitions_view.SortDescriptions.Add(new SortDescription(nameof(AlgoDefinitionVM.favorite_index), ListSortDirection.Ascending));
_favorite_algo_definitions_view.LiveSortingProperties.Add(nameof(AlgoDefinitionVM.favorite_index));
@@ -185,7 +181,7 @@ namespace mbts.spark {
security_md_source.Select(smd => model.factory.default_algos(smd.security)).Table(s => s.key).Subscribe(factory_defs => {
using (_selected_algo_definition.begin_update())
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, factory_defs, fd => {
var vm = AlgoDefinitionVM.create(_model, fd, init_format: OrderDataHelper.get_algo_format(security.ContractCase), editing_permissions: AlgoEditingPermissions.AllowEditValues);
var vm = AlgoDefinitionVM.create(_model, fd, init_format: OrderDataHelper.get_algo_format(security.ContractCase));
vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
return vm;
});
@@ -193,7 +189,7 @@ namespace mbts.spark {
model.algo_model.definitions.Subscribe(_definitions_buffer),
_definitions_buffer.OnDispatcher().Subscribe(u => {
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, u, def_data => {
var def_vm = AlgoDefinitionVM.create(model, def_data, true, OrderDataHelper.get_algo_format(security.ContractCase), AlgoEditingPermissions.AllowEditValues);
var def_vm = AlgoDefinitionVM.create(model, def_data, true, OrderDataHelper.get_algo_format(security.ContractCase));
def_vm.selected_format = OrderDataHelper.get_algo_format(security.ContractCase);
def_vm.favorite_index = _get_favorite_algo_index(def_vm.key);
return def_vm;
@@ -209,10 +205,7 @@ namespace mbts.spark {
d.selected_format = fmt;
}),
vm), true)) {
() => {
Disposable.dispose(ref _template_collection_sync);
Disposable.dispose(ref _algo_param_rows_hook);
}
() => Disposable.dispose(ref _template_collection_sync)
};
async Task save_template_async(AlgoDefinitionTemplateVM template) {
using (_preserve_selection_by_index())
@@ -236,6 +229,7 @@ namespace mbts.spark {
}
}
break;
}
}
}
@@ -243,13 +237,8 @@ namespace mbts.spark {
IDisposable _template_collection_sync;
void _selected_algo_definition_Changed(AlgoDefinitionVM old_value, AlgoDefinitionVM new_value, SelectionReason reason) {
Disposable.dispose(ref _template_collection_sync);
Disposable.dispose(ref _algo_param_rows_hook);
if (new_value != null) {
_template_collection_sync = ObservableAutoWrapper.connect_two_way(new_value.templates_with_custom, algo_templates);
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);
} else {
algo_templates.Clear();
}
@@ -275,21 +264,9 @@ namespace mbts.spark {
public TimeInForce selected_time_in_force { get => _time_in_force.selected_item; set => _time_in_force.selected_item = value; }
public CustomOrderDefinitionImmutable? definition => selected_algo_template?.algo_root?.definition;
public int favorite_algos_column_count { get => _favorite_algos_column_count; private set => Set(ref _favorite_algos_column_count, value); }
public bool use_algo_param_grid {
get => _use_algo_param_grid;
set {
if (_use_algo_param_grid != value) {
_use_algo_param_grid = value;
if (value) {
Disposable.replace(ref _algo_param_rows_hook, _connect_algo_param_rows(selected_algo_definition, _algo_param_rows));
} else Disposable.dispose(ref _algo_param_rows_hook);
}
}
}
// items sources
public ObservableCollection<AlgoDefinitionVM> algo_definitions => _algo_definitions;
public ObservableCollection<AlgoDefinitionTemplateVM> algo_templates { get; } = new ObservableCollection<AlgoDefinitionTemplateVM>();
public IReadOnlyObservableCollection<AlgoParamRowVM> algo_param_rows => _algo_param_rows;
public ICollectionView<AlgoDefinitionVM> algo_definitions_view => _algo_definitions_view;
public ICollectionView<AlgoDefinitionVM> favorite_algo_definitions_view => _favorite_algo_definitions_view;
public ICollectionView algo_templates_view { get; }
@@ -332,12 +309,8 @@ namespace mbts.spark {
public AlgoDefinitionVM selected_favorite_algo_definition {
get => selected_algo_definition?.is_favorite ?? false ? selected_algo_definition : null;
set {
if (value != null) {
if (value != null)
_selected_algo_definition.select_explicit(value);
} else if (_selected_algo_stack.TryPeek(out AlgoKey k)) {
if (_algo_definitions.TryGetValue(k, out AlgoDefinitionVM vm))
_selected_algo_definition.select_explicit(vm);
}
RaisePropertyChanged();
}
}
@@ -408,9 +381,6 @@ namespace mbts.spark {
favorite_algos_column_count = value.column_count;
}
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
AlgoParamRowsHelper.connect(def, rows, false);
int? _get_favorite_algo_index(AlgoKey key) =>
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
+3 -33
View File
@@ -3,52 +3,22 @@ using mbts.spark;
using System;
namespace bts.api.orderman {
readonly struct OrderSecurity: IEquatable<OrderSecurity> {
public readonly struct OrderSecurity: IEquatable<OrderSecurity> {
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
this.security = sec;
this.order = order;
piq = null;
algo = null;
meta = null;
}
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
security = t.sec;
order = t.order;
piq = null;
algo = null;
meta = null;
}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
this.security = sec;
this.order = order;
this.piq = piq;
algo = null;
meta = null;
}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta) {
this.security = sec;
this.order = order;
this.piq = piq;
this.algo = algo;
this.meta = meta;
}
//public OrderSecurity(OrderSecurity t) {
// security = t.security;
// order = t.order;
// piq = t.piq;
//}
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
public SecurityImmutable? security { get; }
public OrderRecordImmutable order { get; }
public uint? piq { get; }
public AlgoData? algo { get; }
public AlgoDefinitionMetadata? meta { get; }
public override bool Equals(object obj) => (obj is OrderSecurity os) && Equals(os);
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security && piq == other.piq;
public override int GetHashCode() => (security, order, piq).GetHashCode();
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security;
public override int GetHashCode() => (security, order).GetHashCode();
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
@@ -3,16 +3,13 @@
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class CoverOrderVM : OrderTypeVMBase<CoverOrderDefinition, CoverOrderDefinitionImmutable> {
readonly Property<int?> _hedge_ticks;
readonly Property<int> _hedge_ticks;
public CoverOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) =>
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1, true);
_hedge_ticks = add_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1);
public int? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
protected override void validate() =>
assert(hedge_ticks.HasValue, nameof(hedge_ticks), "Invalid hedge ticks.");
public int hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
}
}
}
@@ -1,13 +1,9 @@
using bts.collections;
using bts.spark;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using AlgoDef = bts.api.orderman.CustomOrderDefinition;
@@ -17,7 +13,6 @@ using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly VersionContainer<AlgoDefi> _definition;
AlgoFormats _format;
AlgoEditingPermissions _editing_permissions;
@@ -32,6 +27,7 @@ namespace mbts.spark {
public IAlgoVM impl { get; private set; }
public AlgoKey id { get; } = new AlgoKey(AlgoType.None);
public AlgoType order_type => _definition.value.CustomCase;
public bool is_bts_type => false;
public OrderMetadata meta => OrderDataHelper.get_metadata(order_type);
public bool can_expand => impl?.can_expand ?? false;
public bool is_expanded {
@@ -43,31 +39,21 @@ namespace mbts.spark {
}
public AlgoEditingPermissions editing_permissions => _editing_permissions;
public string display_name => impl?.display_name ?? "(Default)";
public string abbreviation => display_name;
public AlgoDisplayGroup group => impl?.group ?? AlgoDisplayGroup.BTS;
public AlgoDefi definition => _definition.value;
public ObservableCollection<IAlgoSelector> child_algo_selectors { get; } = new ObservableCollection<IAlgoSelector>();
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<IAlgoSelector> child_algo_selectors_view => child_algo_selectors;
[EditorBrowsable(EditorBrowsableState.Never)]
public dynamic children_vm => impl?.children_vm;
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
public void assign_to(AlgoDef value) => value.MergeFrom(definition.CloneToMutable());
public bool key_match(KeyEventArgs e) => false;
public void load_from(AlgoDefi value) => impl?.load_from(value);
public void load_many(IEnumerable<AlgoDefi> values) => impl?.load_many(values);
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
void IAlgoVM.ensure_init() => impl?.ensure_init();
protected override void Dispose(bool disposing) =>
_clear_impl();
void _clear_impl() {
if (impl != null) {
_algo_params.clear_collections();
impl.definition_changed -= _on_impl_definition_changed;
(impl as IDisposable)?.Dispose();
impl = null;
@@ -84,7 +70,6 @@ namespace mbts.spark {
void IObserver<SecurityMD?>.OnCompleted() { }
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
var orig_group = group;
_security_md = security_md;
if (security_md.HasValue)
_format = OrderDataHelper.get_algo_format(security_md.Value.security.ContractCase);
@@ -97,7 +82,6 @@ namespace mbts.spark {
break;
_clear_impl();
impl = OrderTypeVMFactory.create(new AlgoKey(fmt_type), fmt_type.display_name(), fmt_type, is_expanded: true);
_algo_params.add_collection(impl.algo_params);
(impl as IObserver<OrdersModel>)?.OnNext(_model);
break;
}
@@ -106,8 +90,6 @@ namespace mbts.spark {
impl.definition_changed += _on_impl_definition_changed;
}
_on_impl_definition_changed();
if (group != orig_group)
RaisePropertyChanged(nameof(group));
}
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
@@ -26,8 +26,6 @@ namespace mbts.spark {
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
}
public IEnumerable<string> dynamic_types => _dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType);
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory)
{
@@ -77,15 +75,5 @@ namespace mbts.spark {
}
return param;
}
internal static string get_enumerated_value(DynamicOrderParam param) => param?.AsEnumerated?.EnumValue;
internal static void set_enumerated_value(DynamicOrderParam param, string value) {
if (param == null) {
return;
} else if (param.ValueCase != DynamicOrderParam.ValueOneofCase.AsEnumerated) {
param.AsEnumerated = new DynamicOrderEnumerated();
}
param.AsEnumerated.EnumValue = value ?? string.Empty;
}
}
}
}
@@ -1,25 +1,14 @@
using bts.api.orderman;
using Google.Protobuf.Reflection;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
interface IDynamicOrderParamVM {
DynamicOrderParamDescriptor descriptor { get; }
DynamicOrderParam param { get; set; }
}
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
T value { get; set; }
IObservable<T> subscribe_value();
}
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
abstract class DynamicOrderParamVM : DisposableViewModelBase {
DynamicOrderParam _param;
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
@@ -40,43 +29,43 @@ namespace mbts.spark {
}
protected abstract void on_param_changed();
public static DynamicOrderParamVM create(DynamicOrderParamDescriptor descriptor) {
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
switch (descriptor.ParamType.DescriptorCase) {
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive:
switch (descriptor.ParamType.Primitive) {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return new DynamicOrderParamVM<bool>(descriptor, DynamicOrderParam.AsBoolFieldNumber);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return new DynamicOrderParamVM<double>(descriptor, DynamicOrderParam.AsDoubleFieldNumber);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return new DynamicOrderParamVM<uint>(descriptor, DynamicOrderParam.AsInt32FieldNumber);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return new DynamicOrderParamVM<uint>(descriptor, DynamicOrderParam.AsUint32FieldNumber);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return new DynamicOrderParamVM<string>(descriptor, DynamicOrderParam.AsStringFieldNumber);
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.Primitive, nameof(descriptor));
}
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: return new DynamicOrderEnumeratedVM(descriptor);
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
}
}
}
class DynamicOrderParamVM<TView, TModel> : DynamicOrderParamVM, IDynamicOrderParamVM<TView> {
class DynamicOrderParamVM<T> : DynamicOrderParamVM {
readonly FieldDescriptor _field_desc;
readonly Func<TModel, TView> _convert;
readonly Func<TView, TModel> _convert_back;
TView _value;
public DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, FieldDescriptor field_descriptor, Func<TModel, TView> convert, Func<TView, TModel> convert_back) : base(descriptor) {
_field_desc = field_descriptor;
_convert = convert;
_convert_back = convert_back;
}
public DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, int field_number) : base(descriptor) =>
_field_desc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
public TView value {
get => _value;
public T value {
get => ConvertUtils.try_convert<T>(_field_desc.Accessor.GetValue(param));
set {
_field_desc.Accessor.SetValue(param, _convert_back(_value = value));
_field_desc.Accessor.SetValue(param, value);
RaisePropertyChanged();
}
}
public IObservable<TView> subscribe_value() =>
Observable.Create<TView>(o => {
PropertyChanged += on_prop_changed;
return Disposable.Create(() => PropertyChanged -= on_prop_changed);
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
o.OnNext(value);
});
protected override void on_param_changed() {
_value = _convert(ConvertUtils.try_convert<TModel>(_field_desc.Accessor.GetValue(param)));
RaisePropertyChanged(nameof(value));
}
protected override void on_param_changed() => RaisePropertyChanged(nameof(value));
}
class DynamicOrderEnumeratedVM : DynamicOrderParamVM, IDynamicOrderParamVM<string> {
class DynamicOrderEnumeratedVM : DynamicOrderParamVM {
static readonly FieldDescriptor _field_desc = DynamicOrderParam.Descriptor.FindFieldByNumber(DynamicOrderParam.AsEnumeratedFieldNumber);
public DynamicOrderEnumeratedVM(DynamicOrderParamDescriptor descriptor)
@@ -86,23 +75,19 @@ namespace mbts.spark {
public ObservableCollection<string> elements { get; }
public string selected_element {
get => DynamicOrderHelper.get_enumerated_value(param);
get => param?.ValueCase == DynamicOrderParam.ValueOneofCase.AsEnumerated ? param.AsEnumerated.EnumValue : null;
set {
var orig = selected_element;
DynamicOrderHelper.set_enumerated_value(param, value);
if (selected_element != orig)
if (param == null) {
param = new DynamicOrderParam { AsEnumerated = new DynamicOrderEnumerated() };
} else if (param.ValueCase != DynamicOrderParam.ValueOneofCase.AsEnumerated) {
param.AsEnumerated = new DynamicOrderEnumerated();
}
if (param.AsEnumerated.EnumValue != value) {
param.AsEnumerated.EnumValue = value;
RaisePropertyChanged();
}
}
}
string IDynamicOrderParamVM<string>.value { get => selected_element; set => selected_element = value; }
IObservable<string> IDynamicOrderParamVM<string>.subscribe_value() =>
Observable.Create<string>(o => {
PropertyChanged += on_prop_changed;
return Disposable.Create(() => PropertyChanged -= on_prop_changed);
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
o.OnNext(selected_element);
});
protected override void on_param_changed() {
_sync_elements();
@@ -116,4 +101,4 @@ namespace mbts.spark {
void _sync_elements() =>
elements.sync_with(_get_elements(descriptor));
}
}
}
@@ -1,10 +1,7 @@
using bts;
using bts.api.orderman;
using bts.collections;
using bts.api.orderman;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -20,18 +17,24 @@ namespace mbts.spark {
public DynamicOrderVM(AlgoKey key, string dyn_type, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_dyn_type = add_property(nameof(this.dyn_type), DynamicOrderDefinition.DynamicOrderTypeFieldNumber, dyn_type, permissions: AlgoParamPermissions.None);
_dyn_type = add_property(nameof(this.dyn_type), DynamicOrderDefinition.DynamicOrderTypeFieldNumber, dyn_type);
_parameters = new ObservableKeyedCollection<DynamicOrderParamVM, string>(vm => vm.descriptor.ParamName);
_resources.AddRange(
_dyn_type.Subscribe(_ => {
_sync_dynamic_params();
sync_selectors();
}),
ObservableAutoWrapper.connect_item_hooks(_parameters.AsCollection(), pvm => {
add_property(pvm.descriptor.ParamName, mdef => mdef.OrderParams[pvm.descriptor.ParamName] = pvm.param, mdef => {
if (mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p))
pvm.param = p;
});
return Disposable.Create(() => remove_property(pvm.descriptor.ParamName));
}),
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
p.PropertyChanged += on_param_prop_changed;
return Disposable.Create(() => {
p.PropertyChanged -= on_param_prop_changed;
remove_property(p.descriptor.ParamName);
p.Dispose();
});
}, true));
@@ -39,8 +42,6 @@ namespace mbts.spark {
}
public string dyn_type => _dyn_type.Value;
public override string abbreviation => model == null ? display_name : model.factory.abbreviate_algo(new OneOf<CustomOrderDefinition.CustomOneofCase, string>(dyn_type));
public override AlgoDisplayGroup group => AlgoDisplayGroup.API;
public ObservableCollection<DynamicOrderParamVM> parameters => _parameters;
protected override void on_model_internal() =>
@@ -54,55 +55,13 @@ namespace mbts.spark {
void _sync_dynamic_params() {
if (model != null) {
var desc = model.dynamic_helper.get_descriptor(dyn_type);
_parameters.SyncWithDictionary(desc.ParamDesc.Where(_is_value_param_desc).ToDictionary(p => p.ParamName), _mk_param_vm);
_parameters.SyncWithDictionary(desc.ParamDesc.Where(_is_value_param_desc).ToDictionary(p => p.ParamName), DynamicOrderParamVM.create);
}
}
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
DynamicOrderParamVM _mk_param_vm(DynamicOrderParamDescriptor descriptor) {
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
switch (descriptor.ParamType.DescriptorCase) {
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive:
switch (descriptor.ParamType.Primitive) {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return mk<bool?, bool>(DynamicOrderParam.AsBoolFieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<uint?, uint>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.Primitive, nameof(descriptor));
}
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated:
var pvm = new DynamicOrderEnumeratedVM(descriptor);
attached(pvm, DynamicOrderHelper.get_enumerated_value, DynamicOrderHelper.set_enumerated_value);
return pvm;
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
}
DynamicOrderParamVM<TView, TModel> mk<TView, TModel>(int field_number, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default) {
var fdesc = DynamicOrderParam.Descriptor.FindFieldByNumber(field_number);
var pvm = new DynamicOrderParamVM<TView, TModel>(descriptor, fdesc, convert, convert_back);
return attached<TView, DynamicOrderParamVM<TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
TView get_from_param(DynamicOrderParam param) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
void assign_to_param(DynamicOrderParam param, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
}
TParam attached<T, TParam>(TParam pvm, Func<DynamicOrderParam, T> selector, Action<DynamicOrderParam, T> assign_to_param, T default_value = default) where TParam : DynamicOrderParamVM, IDynamicOrderParamVM<T> {
add_property<T>(
pvm.descriptor.ParamName,
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
(mdef, v) => assign_to_param(mdef.OrderParams.GetOrAdd(pvm.descriptor.ParamName, pn => new DynamicOrderParam()), v),
() => pvm.value,
v => pvm.value = v,
pvm.subscribe_value(),
default_value);
return pvm;
}
}
}
}
}
@@ -1,5 +1,4 @@
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.collections;
using mbts.spark.utilities;
@@ -10,8 +9,6 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -19,75 +16,35 @@ using System.Windows.Input;
using System.Windows.Media;
namespace mbts.spark {
enum AlgoParamPermissions {
None = 0, // Hidden from user (i.e. implicit properties, such as TriggerOrderDefinition.Security)
ReadOnly = 1,
Write = (1 << 1),
// TODO: refactor 'ReadWrite' references to 'Read | Write'
ReadWrite = 1 | (1 << 1)
}
enum AlgoDisplayGroup { Exchange, BTS, Studio, API }
interface IAlgoVM : IPropertyWrapperVM<CustomOrderDefinition, CustomOrderDefinitionImmutable>, IPropertyChangedDemuxer {
event Action definition_changed;
AlgoKey id { get; }
CustomOrderDefinition.CustomOneofCase order_type { get; }
AlgoDisplayGroup group { get; }
bool is_bts_type { get; }
OrderMetadata meta { get; }
AlgoEditingPermissions editing_permissions { get; }
bool can_expand { get; }
bool is_expanded { get; set; }
string display_name { get; }
string abbreviation { get; }
CustomOrderDefinitionImmutable definition { get; }
IReadOnlyObservableCollection<IAlgoParamVM> algo_params { get; }
ObservableCollection<IAlgoSelector> child_algo_selectors { get; }
/// <see cref="child_algo_selectors_view"/> is almost completely redundant to <see cref="child_algo_selectors"/>.
/// However, it serves a very important purpose: the first time its getter is called, lazy-initialization is performed.
/// Therefor, callers should access <see cref="child_algo_selectors"/> for this data.
/// <summary>
/// This property should only be accessed by the view. The getter should trigger lazy-initialization (<see cref="ensure_init"/>) the first time it is accessed.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
ObservableCollection<IAlgoSelector> child_algo_selectors_view { get; }
/// <summary>
/// This property should only be accessed by the view. The getter should trigger lazy-initialization (<see cref="ensure_init"/>) the first time it is accessed.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
dynamic children_vm { get; }
bool key_match(KeyEventArgs e);
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
void load_many(IEnumerable<CustomOrderDefinitionImmutable> definitions);
void ensure_init();
}
interface IDynamicAlgoVMImpl {
IAlgoVM impl { get; }
}
interface IAlgoParamVM {
event Action<bool> is_dirty_changed;
event PropertyChange<AlgoParamPermissions> permissions_changed;
string id { get; }
string display_name { get; }
object value { get; set; }
bool is_enabled { get; set; }
bool is_dirty { get; }
bool has_value { get; }
AlgoParamPermissions permissions { get; set; }
void revert();
void mark_committed();
delegate void AlgoLoaded(bool reset_edit_stack);
interface ILazyAlgo {
bool is_loaded { get; }
}
interface IAlgoParamVM<T> : IAlgoParamVM {
event PropertyChange<T> value_changed;
new T value { get; set; }
}
interface IAlgoSelector : IAlgoParamVM<IAlgoVM>, IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)> {
interface IAlgoSelector : IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)> {
event PropertyChange<IAlgoVM> selected_algo_changed;
IObservable<IAlgoVM> subscribe_selected_algo();
ObservableKeyedCollection<IAlgoVM, AlgoKey> algos { get; }
IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
IAlgoVM selected_algo { get; set; }
@@ -130,36 +87,5 @@ namespace mbts.spark {
return ObservableAutoWrapper.connect_item_hooks(selector.algos, vm =>
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
}
public static IEnumerable<IAlgoParamVM> enumerate_all_params(this IAlgoVM vm) =>
vm == null ? Enumerable.Empty<IAlgoParamVM>() : vm.algo_params.Concat(vm.child_algo_selectors.SelectMany(s => enumerate_all_params(s.selected_algo)));
public static IObservable<bool> subscribe_is_dirty(this IAlgoParamVM param_vm) =>
Observable.Create<bool>(o => {
o.OnNext(param_vm.is_dirty);
param_vm.is_dirty_changed += o.OnNext;
return Disposable.Create(dispose);
void dispose() => param_vm.is_dirty_changed -= o.OnNext;
});
public static IObservable<bool> subscribe_is_dirty(this IAlgoVM vm) =>
Observable.Create<bool>(o => {
var mon = new ReactiveThresholdMonitor<string>();
return Disposable.Create(
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, set_selector_hooks),
ObservableAutoWrapper.connect_item_hooks(vm.algo_params, p => mon.join(p.id, p.subscribe_is_dirty())),
mon.Subscribe(o));
IDisposable set_selector_hooks(IAlgoSelector s) =>
s.subscribe_selected_algo().SubscribeEach(_vm => mon.join(s.id, _vm.subscribe_is_dirty()));
});
public static IObservable<AlgoParamPermissions> subscribe_permissions(this IAlgoParamVM param_vm) =>
Observable.Create<AlgoParamPermissions>(o => {
o.OnNext(param_vm.permissions);
param_vm.permissions_changed += on_permissions_changed;
return Disposable.Create(dispose);
void on_permissions_changed(AlgoParamPermissions oldval, AlgoParamPermissions newval) => o.OnNext(newval);
void dispose() =>param_vm.permissions_changed -= on_permissions_changed;
});
}
}
@@ -7,7 +7,7 @@ namespace mbts.spark {
public IcebergOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_show_quantity = add_property(nameof(show_quantity), IcebergOrderDefinition.ShowQuantityFieldNumber, double.NaN, double.NaN);
_show_quantity = add_property(nameof(show_quantity), IcebergOrderDefinition.ShowQuantityFieldNumber, double.NaN);
}
public double show_quantity { get => _show_quantity.Value; set => _show_quantity.set(value); }
@@ -5,13 +5,13 @@ using JoinDefi = bts.api.orderman.JoinOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class JoinOrderVM : OrderTypeVMBase<JoinDef, JoinDefi> {
readonly Property<QuantityMeasureImmutable?> _join_threshold;
readonly Property<QuantityMeasureImmutable> _join_threshold;
public JoinOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) =>
_join_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(join_threshold), JoinDef.JoinThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
public QuantityMeasureImmutable? join_threshold { get => _join_threshold.Value; set => _join_threshold.set(value); }
public QuantityMeasureImmutable join_threshold { get => _join_threshold.Value; set => _join_threshold.set(value); }
}
}
}
Loaded 100 of 544 files, more files were not shown because too many files have changed in this diff. Show more