Compare commits

..
Author SHA1 Message Date
Eric Harding dd5c680155 work in progress 2019-04-29 11:18:11 -04:00
575 changed files with 8785 additions and 27364 deletions

No files matched your search

+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
-6
View File
@@ -22,9 +22,7 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
DiskGeometryKey = nameof(DiskGeometryKey),
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
DocumentGeometryKey = nameof(DocumentGeometryKey),
@@ -41,7 +39,6 @@ namespace mbts.ui {
PaymentGeometryKey = nameof(PaymentGeometryKey),
PencilGeometryKey = nameof(PencilGeometryKey),
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -118,7 +115,6 @@ namespace mbts.ui {
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
@@ -143,7 +139,6 @@ namespace mbts.ui {
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
public static ImageSource Disk => create_icon(DiskGeometryKey);
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
@@ -163,7 +158,6 @@ namespace mbts.ui {
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
public static ImageSource Star => create_icon(StarGeometryKey);
-63
View File
@@ -40,69 +40,6 @@
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Unlink free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
</sys:String>
<sys:String>
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/freepik
Ttile: Disconnected chains free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
<sys:String>
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
</sys:String>
<sys:String>
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
C164.311,139.575,168.644,142.574,173.171,141.769z
</sys:String>
<sys:String>
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
C34.717,189.582,39.058,192.589,43.561,191.776z
</sys:String>
</x:Array>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/chanut
Ttile: Cancel free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
C59.518,47.514,47.514,59.518,32.759,59.518z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/smashicons
Ttile: File free icon
@@ -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);
@@ -16,7 +16,7 @@ using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -32,7 +32,6 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
}
event Action<MarketViewWindowFactory.StartupData> _state_changed;
@@ -43,12 +42,12 @@ 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;
public string thread_id { get; }
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
@@ -58,15 +57,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> {
@@ -19,7 +19,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
@@ -48,7 +48,7 @@ namespace mbts.spark {
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
@@ -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;
@@ -16,7 +16,7 @@ using System.Windows;
using static bts.shell.SyntheticManagementWindowFactory;
namespace mbts.spark {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "SyntheticManagement";
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
StartupData _state;
@@ -33,20 +33,8 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public void thread_init() {
Log.notnull(_state);
@@ -66,10 +54,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))
}
}
@@ -113,6 +101,16 @@ namespace mbts.spark {
else _view.DataContext = _vm.value;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public override void Dispose() {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
@@ -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)
}
});
@@ -19,6 +19,8 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
@@ -557,27 +559,85 @@
</Grid>
</RadioButton>
</Grid>
<usp:AlgoEditorView
<GroupBox
x:Name="content_host"
Grid.Row="2"
DataContext="{Binding content_source}"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
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>
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
Grid.Column="3"
ToolTipService.ShowOnDisabled="True"
SelectedItem="{Binding selected_definition.algo_root}"
ItemsSource="{Binding selected_definition.algos_view}"
Margin="{StaticResource Margin.LP1}"
Width="140"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding selected_definition.algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
<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>
@@ -1,6 +1,8 @@
using mbts.spark;
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
@@ -16,11 +18,26 @@ namespace mbts.ui.spark {
public AlgoStudioView() {
InitializeComponent();
DataContextChanged += (s, e) => set_controller();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
set_controller();
// local methods
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
public new AlgoStudioVM DataContext {
get => base.DataContext as AlgoStudioVM;
set => base.DataContext = value;
@@ -13,7 +13,7 @@ using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
namespace mbts.spark {
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
public const string
NAME = "Algo Studio",
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
@@ -37,7 +37,7 @@ namespace mbts.spark {
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
_model = model;
_state = state;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
thread_init();
}
public override StartupData SaveState => _state = _vm.create();
@@ -45,12 +45,12 @@ namespace mbts.spark {
public override string window_type => NAME;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public string thread_id => NAME;
public override double MinWidth =>
SPARK_MENU_COLLAPSED_WIDTH +
LAUNCHER_BUTTON_BORDER_WIDTH +
DEFAULT_CONTENT_WIDTH;
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
public string thread_id { get; }
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
@@ -1,9 +1,8 @@
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.ui.utility;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
@@ -11,6 +10,12 @@ namespace mbts.spark {
public readonly string name;
public readonly AlgoStructureKey structure;
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
: this(definition.CustomCase.type_id(), definition) { }
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
: this(name, new AlgoStructureKey(definition)) { }
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
@@ -26,136 +31,71 @@ namespace mbts.spark {
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
}
[ProtoContract]
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
[ProtoMember(1)]
readonly int[] _structure;
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(new[] { (int)base_type }) { }
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
int[] s = new[] { (int)definition.CustomCase };
_build_structure(definition, ref s, 1);
_structure = s;
}
public AlgoStructureKey(int[] structure) =>
_structure = structure;
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 CustomOrderDefinitionImmutable build_def() {
int pos = 0;
var mdef = new CustomOrderDefinition();
_build_def(_structure, ref pos, ref mdef);
return new CustomOrderDefinitionImmutable(mdef);
}
public bool Equals(AlgoStructureKey other) =>
_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);
}
static class AlgoStructureSerializer {
const int
_dynamic_mask = 1 << 31,
_child_mask_offset = 30;
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
_build_structure(definition.CloneToMutable(), ref structure, pos);
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
parsei(structure_key.structure, dynamic_helper);
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
new AlgoStructureKey(to_array(algo, dynamic_type_index));
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
to_array(algo.CloneToMutable(), dynamic_type_index);
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
int[] s = new[] { (int)algo.CustomCase };
_build_structure(algo, ref s, dynamic_type_index);
return s;
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
var cad = OrderDataHelper.get_child_algo_data(algo);
for (int di = 0; di < cad.Count; di++) {
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
int del_mask = 1 << (31 - di);
structure[pos++] = (int)child_algo.CustomCase | del_mask;
_build_structure(child_algo, ref structure, pos);
break;
}
}
ArrayHelper.trim(ref structure, pos);
}
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
parsei(structure, dynamic_helper);
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
if (structure == null || structure.Count == 0)
return new CustomOrderDefinition();
int pos = 1;
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
_build_def(structure, ref pos, ref mdef, dynamic_helper);
return mdef;
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
if (structure == null || structure.Length == 0)
structure = new int[1];
structure[0] = (int)algo.CustomCase;
int p = 1;
_build_structure(algo, ref structure, ref p, dynamic_type_index);
ArrayHelper.trim(ref structure, p);
}
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
} else {
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
for (int i = pos; i < structure.Length; i++) {
var cad = OrderDataHelper.get_child_algo_data(algo);
children = cad.Select((c, i) => (c.get_child(algo), i));
children = children.Where(c => c.child_algo != null);
}
foreach ((var child, int field_pos) in children) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
_build_structure(child, ref structure, ref pos, dynamic_type_index);
}
}
/// <summary>
/// Gets a mask for a particular child-algo field of a particular algo definition.
/// </summary>
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
var local_algo = algo;
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
var dyn_def = algo.Dynamic;
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
dynamic_helper.add_dynamic_params(ref dyn_def);
children = dyn_def.OrderParams
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
} else {
children = OrderDataHelper.get_child_algo_data(algo)
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
}
using (var children_en = children.GetEnumerator()) {
while (pos < structure.Count && children_en.MoveNext()) {
(var assign_child, int field_pos) = children_en.Current;
int mask = _child_algo_mask(field_pos);
if ((structure[pos] & mask) == mask) {
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
var child_algo = OrderDataHelper.create_algo(type);
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
assign_child(child_algo);
for (int di = 0; di < cad.Count; di++) {
int mask = 1 << (31 - di);
if ((structure[i] & mask) == mask) {
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
pos++;
_build_def(structure, ref pos, ref child_algo);
cad[di].assign_child(algo, child_algo);
}
}
}
@@ -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();
@@ -34,37 +34,29 @@ namespace mbts.spark {
}
}
public static void update_persisted_definition_wrappers(ObservableKeyedCollection<IAlgoVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed)
target[d.key].load_from(d.value);
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(new AlgoVMWrapper(factory(d)));
}
}
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> {
@@ -49,8 +46,7 @@ namespace mbts.spark {
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
readonly Property<AlgoFormats> _selected_format;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
AlgoEditingPermissions _editing_permissions;
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
@@ -58,43 +54,29 @@ namespace mbts.spark {
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
bool _connected;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
var rv = new AlgoDefinitionVM(model, data, init_format);
if (connect)
rv.connect();
return rv;
}
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
_permissions_mask.raw = init_permissions;
var resources = new CompositeDisposable();
if (!model.algo_model.ui_configuration.can_modify_definition_values)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
else if (model.algo_model.templates_enabled()) {
resources.Add(_templates.Subscribe((s, e) => {
if (_templates.Count == 0) {
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
} else {
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
}
}));
}
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
_editing_permissions = _get_permissions();
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
var resources = new CompositeDisposable();
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
@@ -105,7 +87,7 @@ namespace mbts.spark {
_is_selected = backing(nameof(is_selected), true);
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
@@ -131,16 +113,28 @@ 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) {
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
_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(() => {
@@ -158,23 +152,16 @@ namespace mbts.spark {
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
break;
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeEach(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}
return Disposable.empty;
}));
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(f);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
return this.load_algo_data_recursive(algo_data, default_algo);
}
}));
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;
on_editing_permissions(_get_permissions());
await algo_root.load_from_async(def_data.value);
_version.increment_version();
_edit_stack.reset(_version.value);
@@ -182,6 +169,11 @@ namespace mbts.spark {
}
}
// local methods
void on_editing_permissions(AlgoEditingPermissions eps) {
_editing_permissions = eps;
foreach (var vm in algo_roots)
vm.set_permissions(eps);
}
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_local_changed() {
@@ -218,20 +210,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>();
@@ -252,51 +230,13 @@ namespace mbts.spark {
// visual states
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
// editing permissions
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
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...)
@@ -308,7 +248,7 @@ namespace mbts.spark {
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
var orig_algo = algo_root?.id;
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
@@ -358,42 +298,6 @@ namespace mbts.spark {
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
public void load_def(CustomOrderDefinitionImmutable def) {
var key = OrderDataHelper.get_shallow_factory_key(def);
if (algo_root?.id == key) {
algo_root.load_from(def);
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
vm.load_from(def);
algo_root = vm;
} else {
algo_roots.Add(vm = _mk_algo_root(def));
algo_root = vm;
}
}
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)
@@ -405,7 +309,7 @@ namespace mbts.spark {
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
public AlgoDefinitionVM to_persisted(AlgoData def) =>
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
new AlgoDefinitionVM(_model, def);
public AlgoData create_algo_def() =>
new AlgoData(algo_root.definition, name, key);
@@ -418,6 +322,16 @@ namespace mbts.spark {
public override int GetHashCode() => key.GetHashCode();
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
AlgoEditingPermissions _get_permissions() {
var permissions = AlgoEditingPermissions.None;
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
}
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
return permissions;
}
void _assert_can_load(AlgoKey data_key) {
if (key.mode != data_key.mode)
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
@@ -445,14 +359,10 @@ 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);
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
void _on_metadata(AlgoDefinitionMetadata metadata) {
using (_refresh_on_local_changes.supress_execution()) {
@@ -553,17 +463,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,216 +0,0 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace mbts.spark {
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> {
static readonly HashSet<string>
_algo_root_echo_props = new HashSet<string> {
nameof(is_expanded),
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
},
_value_echo_props = new HashSet<string> {
nameof(AlgoDefinitionVM.name),
nameof(AlgoDefinitionVM.abbreviation),
nameof(AlgoDefinitionVM.badge_fill),
nameof(AlgoDefinitionVM.can_undo),
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;
AlgoDefinitionVM _value;
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
value = algo_definition_vm;
_dispose = new CompositeDisposable(_value_hooks) {
_child_algo_selectors.clear_collections,
_algo_params.clear_collections,
() => _value?.Dispose()
};
}
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
public AlgoDefinitionVM value {
get => _value;
set {
if (_value != value) {
_value?.Dispose();
_value = value;
_value_hooks.replace(_set_hooks(value));
RaisePropertyChanged(nameof(value));
}
}
}
public AlgoKey id => _value.key;
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
public OrderMetadata meta {
get {
if (_value != null) {
if (_value.custom_badge_color.enabled)
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
else if (_value.algo_root != null)
return _value.algo_root.meta;
}
return OrderDataHelper.get_metadata(order_type);
}
}
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
public bool can_expand => _algo_root?.can_expand ?? false;
public bool is_expanded {
get => _value.algo_root?.is_expanded ?? false;
set {
if (_value != null)
_value.is_expanded = value;
}
}
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;
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();
vm.PropertyChanged += on_value_prop_changed;
return new CompositeDisposable(
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
};
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_algo_root_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
}
}
// permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
}
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
}
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
struct _ObservedData {
public readonly AlgoEditingPermissions permissions;
public readonly OrdersModel model;
public readonly SecurityImmutable? security;
public readonly SecurityMD? security_md;
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
this.permissions = permissions;
this.model = model;
this.security = security;
this.security_md = security_md;
}
public void forward_to(IAlgoVM recipient) {
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
if (model != null)
(recipient as IObserver<OrdersModel>)?.OnNext(model);
if (security_md.HasValue)
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
if (security.HasValue)
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
}
public _ObservedData with(AlgoEditingPermissions? permissions) =>
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
public _ObservedData with(OrdersModel model) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityImmutable? security) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityMD? security_md) =>
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;
}
}
}
}
}
@@ -1,120 +0,0 @@
<UserControl
x:Class="mbts.ui.spark.AlgoEditorView"
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: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/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.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>
<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}"/>
<!--Converters-->
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
<!--Default Styles-->
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
<Setter Property="Padding" Value="8"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
</Style>
<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>
</ResourceDictionary>
</UserControl.Resources>
<GroupBox
x:Name="content_host"
Grid.Row="2"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
ToolTipService.ShowOnDisabled="True"
Width="140"
Margin="{StaticResource Margin.LP1}"
ItemsSource="{Binding algos_view}"
SelectedItem="{Binding algo_root}"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.IsEnabled>
<Binding Path="editing_permissions">
<Binding.Converter>
<cv:EnumHasFlagsConverter>
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
</cv:EnumHasFlagsConverter>
</Binding.Converter>
</Binding>
</ComboBox.IsEnabled>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
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}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
</UserControl>
@@ -1,28 +0,0 @@
using mbts.ui.extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorView : UserControl {
public AlgoEditorView() {
InitializeComponent();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
// local methods
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
}
}
@@ -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;
@@ -17,10 +16,8 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
@@ -34,7 +31,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);
@@ -45,7 +42,7 @@ namespace mbts.spark {
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
@@ -87,7 +84,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[] {
@@ -101,17 +98,16 @@ namespace mbts.spark {
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
grid_settings_manager.load_from(settings_manager.current_settings());
//
_dispose = new CompositeDisposable(
_dispose = Disposable.Create(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
fills_source.OnDispatcher().Subscribe(t => {
_rows.update(t);
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
if (s.grid != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
@@ -124,8 +120,8 @@ namespace mbts.spark {
};
rows_manager.on_settings(fs, s.sorting_criterion);
}),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
fills_source.Connect()
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -157,12 +153,12 @@ namespace mbts.spark {
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
_sort_manager.create();
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
static Columns _col_key(ColumnBase column) =>
((IKeyedColumn<Columns>)column).key;
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
public FilterStates get_filters() =>
_filters_manager.create();
@@ -1,55 +0,0 @@
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.utility;
using System;
using System.Collections.ObjectModel;
namespace mbts.spark {
/// <summary>
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
/// </summary>
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
bool _disposed;
FillsVM _value;
IDisposable _columns_duplex;
public event Action<bool> OnDisposed;
public FillsVM value {
get => _value;
set {
if (value != _value) {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
Disposable.dispose(ref _columns_duplex);
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);
}
}
}
}
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
public void Dispose() {
if (_disposed)
throw new ObjectDisposedException(nameof(FillsVMContainer));
value = null;
OnDisposed?.Invoke(true);
_disposed = true;
}
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
if (!_disposed)
_value?.on_sorting(sender, e);
}
}
}
@@ -18,6 +18,7 @@ using StartupData = bts.shell.FillsWindowFactory.StartupData;
namespace mbts.spark {
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
public const string window_type_name = "Fills";
const string _thread_id = "Fills";
readonly AllowOnce _init_guard = new AllowOnce("Fills");
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -28,11 +29,10 @@ 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;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsWindowFactory.StartupData> _state_changed;
@@ -48,7 +48,7 @@ namespace mbts.spark {
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
@@ -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,15 +23,17 @@ 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;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
_md_auxiliary_model = md_auxiliary_model;
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { 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>
@@ -137,7 +95,7 @@
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
@@ -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",
@@ -21,10 +20,7 @@ namespace mbts.spark {
}
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public MiniOrderBookRowView() : base() {
FocusManager.SetIsFocusScope(this, true);
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
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,14 +20,15 @@ using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
using static bts.shell.OrderBookWindowFactory;
using static mbts.spark.OrderBookView;
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.
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
string sort_member_path { get; }
ListSortDirection? SortDirection { get; set; }
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
IComparer<OrderBookRowVM> comparer { get; }
@@ -80,6 +78,7 @@ namespace mbts.spark {
Add(symbol_column = new SimpleTextColumn<string[]>(
Columns.Symbol,
DefaultColumnHeaders.Symbol,
nameof(OrderBookRowVM.symbol),
r => r.symbol,
DefaultColumnWidths.Symbol,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
@@ -93,6 +92,7 @@ namespace mbts.spark {
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
Columns.Time,
"Time",
nameof(OrderBookRowVM.time_created),
r => r.time_created_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
@@ -104,6 +104,7 @@ namespace mbts.spark {
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
Columns.LastChange,
"Last Change",
nameof(OrderBookRowVM.last_change),
r => r.last_change_text,
DefaultColumnWidths.Time,
new TimeFilterVM<OrderSecurityAlgoMeta>(
@@ -115,6 +116,7 @@ namespace mbts.spark {
Add(user_column = new SimpleTextColumn<string[]>(
Columns.User,
DefaultColumnHeaders.User,
nameof(OrderBookRowVM.user),
r => r.user,
DefaultColumnWidths.User,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
@@ -127,6 +129,7 @@ namespace mbts.spark {
Add(account_column = new SimpleTextColumn<string[]>(
Columns.Account,
DefaultColumnHeaders.Account,
nameof(OrderBookRowVM.account),
r => r.account,
DefaultColumnWidths.Account,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
@@ -141,6 +144,7 @@ namespace mbts.spark {
Add(exchange_column = new SimpleTextColumn<Venue[]>(
Columns.Exchange,
DefaultColumnHeaders.Exchange,
nameof(OrderBookRowVM.exchange),
r => r.exchange.ToString(),
DefaultColumnWidths.Exchange,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
@@ -153,6 +157,7 @@ namespace mbts.spark {
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
Columns.TimeInForce,
DefaultColumnHeaders.TimeInForce,
nameof(OrderBookRowVM.time_in_force),
r => r.time_in_force.ToString(),
DefaultColumnWidths.TimeInForce,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
@@ -171,13 +176,14 @@ namespace mbts.spark {
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
Columns.SubType,
"Sub Type",
nameof(OrderBookRowVM.order_sub_type),
r => r.order_sub_type_text,
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
EnumHelper.get_values<OrderSubType>().ToList(),
ort => ort.order.sub_type(),
nameof(OrderBookRowVM.order_sub_type)),
OrderBookGridView.SubTypeFilterTemplateKey,
SubTypeFilterTemplateKey,
_wrap_comparer(r => r.order_sub_type)));
// side
Add(side_column = new SideColumn(
@@ -199,9 +205,10 @@ namespace mbts.spark {
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.Price,
"Price",
nameof(OrderBookRowVM.price),
r => r.price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new DoubleMinMax<OrderSecurityAlgoMeta>(
ort => ort.order.price(),
nameof(OrderBookRowVM.price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -210,14 +217,14 @@ 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",
nameof(OrderBookRowVM.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,
@@ -226,9 +233,10 @@ namespace mbts.spark {
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
Columns.LastPrice,
"Last Filled",
nameof(OrderBookRowVM.last_price),
r => r.last_price_text,
DefaultColumnWidths.Price,
new PriceMinMax<OrderSecurityAlgoMeta>(
new DoubleMinMax<OrderSecurityAlgoMeta>(
ort => ort.order.last_price(),
nameof(OrderBookRowVM.last_price)),
DataGridFilterKnownTemplates.DoubleMinMax,
@@ -237,6 +245,7 @@ namespace mbts.spark {
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
Columns.Status,
"Status",
nameof(OrderBookRowVM.status),
r => r.status.ToString(),
80,
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
@@ -254,6 +263,7 @@ namespace mbts.spark {
Add(security_key_column = new SimpleTextColumn<string>(
Columns.SecurityKey,
DefaultColumnHeaders.SecurityKey,
nameof(OrderBookRowVM.security_key),
r => r.security_key.ToString(),
DefaultColumnWidths.SecurityKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -265,6 +275,7 @@ namespace mbts.spark {
Add(order_key_column = new SimpleTextColumn<string>(
Columns.OrderKey,
DefaultColumnHeaders.OrderKey,
nameof(OrderBookRowVM.order_key),
r => r.order_key.ToString(),
DefaultColumnWidths.OrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -276,6 +287,7 @@ namespace mbts.spark {
Add(parent_key_column = new SimpleTextColumn<string>(
Columns.ParentKey,
"Parent Key",
nameof(OrderBookRowVM.parent_order_key),
r => r.parent_order_key.ToString(),
DefaultColumnWidths.ParentKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
@@ -283,21 +295,11 @@ namespace mbts.spark {
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
_wrap_comparer(r => r.parent_order_key)));
// attached target order key
Add(attached_target_order_key_column = new SimpleTextColumn<string>(
Columns.AttachedTargetOrderKey,
DefaultColumnHeaders.AttachedTargetOrderKey,
r => r.order.attached_target_order_key().ToString(),
DefaultColumnWidths.AttachedTargetOrderKey,
new RegexFilterVM<OrderSecurityAlgoMeta>(
ort => ort.order.attached_target_order_key().ToString(),
nameof(OrderBookRowVM.parent_order_key)),
DataGridFilterKnownTemplates.Regex,
_wrap_comparer(r => r.order.attached_target_order_key())));
}
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 +314,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; }
@@ -320,13 +323,10 @@ namespace mbts.spark {
public SimpleTextColumn<string> security_key_column { get; }
public SimpleTextColumn<string> order_key_column { get; }
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(),
attached_target_order_key_regex = attached_target_order_key_column.filter.get_state(),
time_created = time_created_column.filter.get_state(),
users = user_column.filter.get_state(),
accounts = account_column.filter.get_state(),
@@ -345,10 +345,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) {
@@ -366,8 +364,6 @@ namespace mbts.spark {
time_in_force_column.filter.load_state(fs.tifs);
exchange_column.filter.load_state(fs.exchanges);
order_key_column.filter.load_state(fs.order_key_regex);
parent_key_column.filter.load_state(fs.parent_key_regex);
attached_target_order_key_column.filter.load_state(fs.attached_target_order_key_regex);
avg_price_column.filter.load_state(fs.avg_price);
last_price_column.filter.load_state(fs.last_price);
status_column.filter.load_state(fs.statuses);
@@ -478,28 +474,19 @@ 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;
protected readonly Func<OrderBookRowVM, string> _get_text;
GlyphContext _root_glyph_context, _child_glyph_context;
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
id = key.ToString();
Width = width;
Header = header;
_get_text = get_text;
sort_member_path = sort_prop;
this.filter = filter;
_filter_template = filter_template;
_cached_sort_direction = SortDirection;
@@ -528,9 +515,9 @@ namespace mbts.spark {
}
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
: base(key, header, get_text, width, filter, filter_template, comparer) { }
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
@@ -540,7 +527,7 @@ namespace mbts.spark {
#region Explicit Column Defs
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
@@ -549,7 +536,9 @@ namespace mbts.spark {
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
sort_member_path = nameof(OrderBookRowVM.quantity);
}
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
@@ -558,7 +547,9 @@ namespace mbts.spark {
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
}
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
@@ -571,6 +562,7 @@ namespace mbts.spark {
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
id = key.ToString();
this.filter = filter;
sort_member_path = nameof(OrderBookRowVM.order_type);
font_weight_binding = RenderUtils.get_font_weight_binding();
_cached_sort_direction = SortDirection;
}
@@ -595,7 +587,8 @@ namespace mbts.spark {
id = key.ToString();
this.filter = filter;
Header = "Last Message";
Width = DefaultColumnWidths.LastMessage;
Width = DIYGrid.DefaultColumnWidths.LastMessage;
sort_member_path = nameof(OrderBookRowVM.last_msg);
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
}
@@ -623,6 +616,7 @@ namespace mbts.spark {
this.filter = filter;
Header = DefaultColumnHeaders.Security;
Width = DefaultColumnWidths.Security;
sort_member_path = nameof(OrderBookRowVM.security);
_cached_sort_direction = SortDirection;
}
@@ -640,7 +634,7 @@ namespace mbts.spark {
toggle_button.SetBinding(ToggleButton.IsCheckedProperty, new Binding(nameof(OrderBookRowVM.is_expanded)));
toggle_button.SetBinding(ToggleButton.MarginProperty, new Binding(nameof(OrderBookRowVM.depth_level)) { Converter = _margin_converter });
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
toggle_button.SetResourceReference(ToggleButton.StyleProperty, OrderBookGridView.ExpandCollapseToggleStyleKey);
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
//
var security_view = new TextBlock { Foreground = font_brush };
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
@@ -651,14 +645,18 @@ 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 +705,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 +713,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 +734,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 +774,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, DIYGrid.IActionableRowVM {
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,12 @@ 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 +103,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();
@@ -142,156 +142,93 @@ namespace mbts.spark {
public ICommand toggle_command => _toggle_expanded_cmd ?? (
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
public ICommand show_order_modify_cmd =>
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
ICommand DIYGrid.IActionableRowVM.default_command => _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_meta = this.order_tuple.meta;
// _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 = 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
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)
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
invalidate = true;
}
if (old_meta?.badge_color != order_tuple.meta?.badge_color) {
_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 +250,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
@@ -0,0 +1,168 @@
<UserControl x:Class="mbts.spark.OrderBookView" x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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: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"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="1080"
FocusManager.IsFocusScope="True"
Background="{StaticResource CommonBackground}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<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:OrderBookView.RowTemplateKey}">
<sp:OrderBookRowView/>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<c:DIYGrid
x:Name="_grid"
Grid.Row="0"
Columns="{Binding columns}"
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Rows="{Binding rows}"
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</c:DIYGrid.Resources>
<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}"
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
/>
</c:DIYGrid.CommandBindings>
</c:DIYGrid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -0,0 +1,26 @@
using mbts.ui.controls;
using System.Windows.Controls;
namespace mbts.spark {
internal partial class OrderBookView : 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 }
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
RowStyleKey = "RowStyle",
RowTemplateKey = "RowTemplateKey",
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
DataContext = vm;
}
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
public OrderBookRowView() : base() { }
}
}
@@ -1,65 +1,70 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.orderbook;
using bts.spark;
using bts.view;
using mbts.persist;
using mbts.ui;
using mbts.ui.converters;
using mbts.ui.extensions;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
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 IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
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 {
const string _window_type = "OrderBook", _default_title = "Order Book";
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "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;
OrderBookWindowFactory _factory;
SparkCommand[] _commands;
SparkCommand _show_fills_command
, _modify_order_command
, _cancel_buy_orders_command
, _cancel_sell_orders_command
, _cancel_all_orders_command
, _cancel_selected_orders_command
, _recreate_selected_orders_command
, _toggle_collapse_all_command
, _toggle_expand_all_command
, _show_grid_settings_command;
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;
_notification_service = notification_service;
_state = state;
_settings_manager = settings_manager;
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;
public string thread_id { get; }
public string thread_id => ORDERBOOK_THREAD_ID;
public override void Dispose() {
_commands.OfType<IDisposable>().DisposeAll();
Disposable.dispose(ref _attachment_exit_sub);
Disposable.dispose(ref _vm);
}
@@ -68,18 +73,19 @@ 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()));
_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(),
Icons.ModifyOrder,
_vm.toggle_show_right_panel_cmd,
OrderBookView.KeyboardShortcuts.ToggleShowRightPanel) {
group = CommandGroups.Modify,
show_in_toolbar = false
};
_modify_order_command = SparkCommands.ShowModifyOrder.complete(
() => _vm.current_order.Value.Key,
can_execute: () => {
if (_vm.current_order.HasValue) {
return _vm.current_order.Value.State.Status == bts.api.orderman.OrderStatus.Working;
}
return false;
}
);
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
_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)),
wbids => wbids.Any(),
() => {
@@ -91,9 +97,8 @@ namespace mbts.spark {
return resp == BTSMessageBoxButtons.Yes;
}
);
cancel_buy_orders_command.group = CommandGroups.Cancel;
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
_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)),
wasks => wasks.Any(),
() => {
@@ -105,9 +110,8 @@ namespace mbts.spark {
return resp == BTSMessageBoxButtons.Yes;
}
);
cancel_sell_orders_command.group = CommandGroups.Cancel;
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
_cancel_all_orders_command = SparkCommands.CancelOrders.complete(
_model.working_orders.Select(os => os.Select(o => o.Key)),
worders => worders.Any(),
() => {
@@ -119,58 +123,41 @@ namespace mbts.spark {
return resp == BTSMessageBoxButtons.Yes;
}
);
cancel_all_orders_command.group = CommandGroups.Cancel;
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
_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(
_recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
() =>
_vm.selected_orders.Where(x => passes_recreate_search_criteria(x))
_vm.selected_orders.Where(x => _passes_recreate_search_criteria(x))
.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))
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",
Icons.AttachAlgos,
_vm.begin_attach_cmd,
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
attach_selected_orders_command.group = CommandGroups.Modify;
var detach_selected_orders_command = new SparkCommandCommand(
"Detach Selected",
Icons.DetachAlgos,
_vm.detach_selected_orders_cmd,
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
detach_selected_orders_command.group = CommandGroups.Modify;
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
_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",
_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>(
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
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,61 +168,32 @@ 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,
cancel_selected_orders_command,
cancel_buy_orders_command,
cancel_sell_orders_command,
cancel_all_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))
_commands = new SparkCommand[] {
_show_fills_command,
_modify_order_command,
_cancel_selected_orders_command,
_cancel_buy_orders_command,
_cancel_sell_orders_command,
_cancel_all_orders_command,
_recreate_selected_orders_command,
_toggle_collapse_all_command,
_toggle_expand_all_command,
_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.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;
}
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
!record.has_parent() &&
record.State.Quantity > record.State.FilledQuantity;
void vm_selected_order_changed(OrderRecordImmutable? e) {
cancel_selected_orders_command.raiseCanExecuteChanged();
recreate_selected_orders_command.raiseCanExecuteChanged();
attach_selected_orders_command.raiseCanExecuteChanged();
detach_selected_orders_command.raiseCanExecuteChanged();
}
_vm.selected_order_changed += _vm_selected_order_changed;
}
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;
}));
private bool _passes_recreate_search_criteria(OrderRecordImmutable record) {
return (record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error)
&& !record.has_parent()
&& record.State.Quantity > record.State.FilledQuantity;
}
static class CommandGroups {
public const string
Modify = nameof(Modify),
Cancel = nameof(Cancel),
Auxiliary = nameof(Auxiliary);
private void _vm_selected_order_changed(OrderRecordImmutable? e) {
_modify_order_command.raiseCanExecuteChanged();
_cancel_selected_orders_command.raiseCanExecuteChanged();
_recreate_selected_orders_command.raiseCanExecuteChanged();
}
}
}
@@ -1,15 +1,24 @@
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.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using static mbts.spark.OrderBookView;
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 +46,223 @@ namespace bts.shell {
var w = await load(startup_data);
_bus.post(SparkTags.ShowWindow, w);
}
[ProtoContract]
public class OrderBookStartupData {
//[ProtoMember(5)]
//public readonly List<SortingCriteria> sort_criteria;
[ProtoMember(10)]
public FilterStates filters;
[ProtoMember(15)]
[Obsolete]
public GridSettings grid;
[ProtoMember(20)]
[Obsolete]
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
[ProtoMember(25)]
public bool use_defaults;
public OrderBookStartupData() { }
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
this.filters = filters;
this.use_defaults = use_defaults;
#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, 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 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;
}
}
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
};
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;
public bool Equals(FilterStates other) =>
other != null &&
security_criteria.safe_equals(other.security_criteria) &&
order_key_regex == other.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);
}
[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) {
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;
}
}
}
@@ -1,63 +0,0 @@
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.controls.adorners;
using mbts.ui.extensions;
using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
namespace mbts.spark {
partial class OrderBookView {
public class OrderBookAttachmentTargetAdorner : DIYGridRowHighlighterAdorner, IDisposable {
IDisposable _attach_op;
bool _is_active, _is_disposed;
#region IsActive
public static readonly DependencyProperty IsActiveProperty =
WPFHelper.create_dp<bool, OrderBookAttachmentTargetAdorner>(
nameof(IsActive),
FrameworkPropertyMetadataOptions.AffectsRender,
(o, v) => {
o._is_active = v;
Disposable.dispose(ref o._attach_op);
if (v && !o._is_disposed) {
o.IsHitTestVisible = true;
o._attach_op = Disposable.Create(
o.SubscribePreviewMouseDown((s, e) => {
if (e.ChangedButton == MouseButton.Left) {
if (e.ClickCount > 1 && o.TargetRow is OrderBookRowVM r) {
e.Handled = true;
o._raise_target_row_selected(r);
}
}
}),
o.AdornedElement.SubscribeMouseHoverRow<OrderBookRowVM>().Subscribe(row => o.TargetRow = row));
} else {
o.IsHitTestVisible = false;
o.TargetRow = null;
}
},
false);
public bool IsActive {
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public event Action<OrderBookRowVM> TargetRowSelected;
#endregion
public OrderBookAttachmentTargetAdorner(DIYGrid grid) : base(grid) { }
public void Dispose() {
if (!_is_disposed) {
_is_disposed = true;
Disposable.dispose(ref _attach_op);
}
}
void _raise_target_row_selected(OrderBookRowVM row) => TargetRowSelected?.Invoke(row);
}
}
}
@@ -1,171 +0,0 @@
<c:DIYGrid
x:Class="mbts.spark.OrderBookGridView"
x:ClassModifier="internal"
x:Name="_grid"
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:usp="clr-namespace:mbts.ui.spark"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<c:DIYGrid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Resources (Brushes, Colors, etc.)-->
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<!--Converters-->
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
<!--DataTemplates-->
<DataTemplate x:Key="{x:Static sp:OrderBookGridView.SubTypeFilterTemplateKey}">
<usp:FilterContentControl
ApplyCommandParameter="{Binding}"
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
ClearCommandParameter="{Binding}"
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
>
<c:ListBoxEx
ItemsSource="{Binding items}"
Selection="{Binding selected_items}"
SelectionMode="Multiple"
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
>
<c:ListBoxEx.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
</DataTemplate>
</c:ListBoxEx.ItemTemplate>
</c:ListBoxEx>
</usp:FilterContentControl>
</DataTemplate>
<!--Styles-->
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</c:DIYGrid.Resources>
<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.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}"/>
</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}}"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</c:DIYGrid.RowStyle>
<c:DIYGrid.RowTemplate>
<DataTemplate>
<sp:OrderBookRowView/>
</DataTemplate>
</c:DIYGrid.RowTemplate>
</c:DIYGrid>
@@ -1,54 +0,0 @@
using mbts.ui.controls;
namespace mbts.spark {
internal partial class OrderBookGridView : DIYGrid {
public const string
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
RowStyleKey = "RowStyle",
RowTemplateKey = "RowTemplateKey",
CVRowStyleKey = "CVRowStyle",
CVRowTemplateKey = "CVRowTemplateKey";
// 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
}
public OrderBookGridView() =>
InitializeComponent();
}
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
public OrderBookRowView() : base() { }
}
}
@@ -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>
@@ -1,591 +0,0 @@
<UserControl
x:Class="mbts.spark.OrderBookView"
x:ClassModifier="internal"
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}"
>
<UserControl.Resources>
<ResourceDictionary>
<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>
<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}"/>
<Setter Property="BorderBrush" Value="{StaticResource Control.MediumGrayBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Bid Price ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Bid Size ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Ask Price ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding security, StringFormat='Best Ask Size ({0})'}"/>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<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>
<Grid x:Name="ContentRoot">
<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: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}}">
<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)">
<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>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding right_panel_width, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}"/>
</Grid.ColumnDefinitions>
<sp:OrderBookGridView
x:Name="orders_grid"
Grid.ColumnSpan="2"
Background="Transparent"
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>
<GridSplitter
x:Name="right_panel_width_splitter"
Grid.Column="1"
Panel.ZIndex="1"
HorizontalAlignment="Left"
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.RowDefinitions>
<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>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState Name="Tab">
<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)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Stack"/>
</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}"/>
</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)
</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...'}"
/>
</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>
</Grid>
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
<StatusBarItem HorizontalAlignment="Right">
<TextBlock>
<Run Text="Orders:"/>
<Run Text="{Binding rows.Count, Mode=OneWay}" />
<Run Text="of"/>
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
</TextBlock>
</StatusBarItem>
</StatusBar>
</Grid>
</AdornerDecorator>
</UserControl>
@@ -1,270 +0,0 @@
using bts.api.orderman;
using bts.utils;
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.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;
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;
}
});
public static readonly IMultiValueConverter
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;
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;
}
}
}
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();
}
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();
}
}
}
}
public new OrderBookVM DataContext {
get => base.DataContext as OrderBookVM;
set => base.DataContext = value;
}
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;
}
};
}
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 static class VisualStateNames {
public static class RightPanel {
public const string GroupName = nameof(RightPanel);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class OrderDescription {
public const string GroupName = nameof(OrderDescription);
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 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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: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.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
<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}"
/>
</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>
</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>
@@ -1,353 +0,0 @@
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 {
class OrderParamsPanel : Control {
public const string
BestBidPriceButtonStyleKey = nameof(BestBidPriceButtonStyleKey),
BestBidSizeButtonStyleKey = nameof(BestBidSizeButtonStyleKey),
BestAskPriceButtonStyleKey = nameof(BestAskPriceButtonStyleKey),
BestAskSizeButtonStyleKey = nameof(BestAskSizeButtonStyleKey),
SizeCalculatorButtonStyleKey = nameof(SizeCalculatorButtonStyleKey);
public static readonly RoutedCommand SetPriceCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler SetPriceExecuted =
(s, e) => {
if (s is OrderParamsPanel opp)
opp.SetCurrentValue(PriceProperty, ConvertUtils.try_convert(e.Parameter, out double d) ? d : double.NaN);
};
public static readonly CanExecuteRoutedEventHandler
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() =>
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 =
WPFHelper.create_dp<double, OrderParamsPanel>(nameof(Price), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, double.NaN);
public double Price {
get => (double)GetValue(PriceProperty);
set => SetValue(PriceProperty, value);
}
#endregion
#region Size
public static readonly DependencyProperty SizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(Size), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public uint? Size {
get => GetValue(SizeProperty) as uint?;
set => SetValue(SizeProperty, value);
}
#endregion
#region SizePresets
public static readonly DependencyProperty SizePresetsProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(SizePresets), Enumerable.Empty<string>());
public IEnumerable SizePresets {
get => GetValue(SizePresetsProperty) as IEnumerable;
set => SetValue(SizePresetsProperty, value);
}
#endregion
#region BestBidPrice
public static readonly DependencyProperty BestBidPriceProperty =
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestBidPrice));
public double? BestBidPrice {
get => GetValue(BestBidPriceProperty) as double?;
set => SetValue(BestBidPriceProperty, value);
}
#endregion
#region BestBidSize
public static readonly DependencyProperty BestBidSizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestBidSize));
public uint? BestBidSize {
get => GetValue(BestBidSizeProperty) as uint?;
set => SetValue(BestBidSizeProperty, value);
}
#endregion
#region BestAskPrice
public static readonly DependencyProperty BestAskPriceProperty =
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestAskPrice));
public double? BestAskPrice {
get => GetValue(BestAskPriceProperty) as double?;
set => SetValue(BestAskPriceProperty, value);
}
#endregion
#region BestAskSize
public static readonly DependencyProperty BestAskSizeProperty =
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestAskSize));
public uint? BestAskSize {
get => GetValue(BestAskSizeProperty) as uint?;
set => SetValue(BestAskSizeProperty, value);
}
#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>());
public IEnumerable Accounts {
get => GetValue(AccountsProperty) as IEnumerable;
set => SetValue(AccountsProperty, value);
}
#endregion
#region SelectedAccount
public static readonly DependencyProperty SelectedAccountProperty =
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedAccount));
public object SelectedAccount {
get => GetValue(SelectedAccountProperty);
set => SetValue(SelectedAccountProperty, value);
}
#endregion
#region TimeInForces
public static readonly DependencyProperty TimeInForcesProperty =
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(TimeInForces), Enumerable.Empty<string>());
public IEnumerable TimeInForces {
get => GetValue(TimeInForcesProperty) as IEnumerable;
set => SetValue(TimeInForcesProperty, value);
}
#endregion
#region SelectedTimeInForce
public static readonly DependencyProperty SelectedTimeInForceProperty =
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedTimeInForce));
public object SelectedTimeInForce {
get => GetValue(SelectedTimeInForceProperty);
set => SetValue(SelectedTimeInForceProperty, value);
}
#endregion
#region Security
public static readonly DependencyProperty SecurityProperty =
WPFHelper.create_dp<SecurityImmutable?, OrderParamsPanel>(
nameof(Security),
(o, v) => o.CoerceValue(ContractSpecProperty));
public SecurityImmutable? Security {
get => GetValue(SecurityProperty) as SecurityImmutable?;
set => SetValue(SecurityProperty, value);
}
#endregion
#region ContractSpec
public static readonly DependencyProperty ContractSpecProperty =
WPFHelper.create_dp<ContractSpecificationImmutable?, OrderParamsPanel>(
nameof(ContractSpec),
null,
(d, base_value) => d.Security?.ContractSpec ?? base_value ?? SecurityDataHelper.Defaults.ContractSpecificationImmutable);
public ContractSpecificationImmutable? ContractSpec {
get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?;
set => SetValue(ContractSpecProperty, value);
}
#endregion
#region AlgoRoots
public static readonly DependencyProperty AlgoRootsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<IAlgoVM>, OrderParamsPanel>(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, 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);
}
#endregion
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)}";
}
}
}
}
@@ -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;
@@ -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)));
}
}
}
@@ -79,12 +79,10 @@ namespace mbts.spark {
RequestClose?.Invoke(this, new EventArgs());
}
});
bbo = new BBOVM();
_dispose = Disposable.Create(
size_calculator,
properties_vm,
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
bbo,
bbo = new BBOVM(security_md_source),
order_source.OnDispatcher().Subscribe(ort => {
key = ort.order.Key;
if (DoubleHelper.IsNaNOrInfinity(price))
+1 -3
View File
@@ -49,7 +49,7 @@ namespace mbts.spark {
) {
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
bbo = new BBOVM();
bbo = new BBOVM(security_md_source);
send_order = new DelegateCommand<Side>(side => {
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
@@ -59,8 +59,6 @@ namespace mbts.spark {
});
set_price_command = new DelegateCommand<double>(set_price);
_dispose = Disposable.Create(
bbo,
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
size_calculator = new CalculatorLogic(0, calculator_buttons),
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
);
@@ -2,7 +2,6 @@
using bts.api.core;
using bts.api.orderman;
using bts.shell;
using bts.utils;
using mbts;
using mbts.spark;
using mbts.ui.controls;
@@ -84,18 +83,25 @@ namespace bts.spark {
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
double old_price = old_order.State.Price, new_price = new_order.State.Price;
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
return StringBuilderEx.join(prefix, change_descs());
//
string prefix() => $"({old_order.Key}) Changed";
IEnumerable<string> change_descs() {
if (old_price != new_price)
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
if (old_size != new_size)
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
if (old_attached_target_key != new_attached_target_key)
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
string modified_size_partial_description(int working_size) {
return $"size from {old_size} to {new_size}, working {working_size}";
}
string modified_price_partial_description() {
return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
}
if (old_price != new_price || old_size != new_size) {
StringBuilder sb = new StringBuilder($"({old_order.Key}) Changed ");
if (old_price != new_price) {
sb.Append(modified_price_partial_description());
if (old_size != new_size)
sb.Append($", {modified_size_partial_description(new_order.working_quantity())}");
}
else
sb.Append(modified_size_partial_description(new_order.working_quantity()));
return sb.ToString();
}
else
return string.Empty;
}
static string _header(uint key) => $"({key})";
@@ -178,31 +184,21 @@ namespace bts.spark {
format_cancel_error(order, security?.ContractSpec, error));
}
internal void update_order(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
_log_order_update(order, price, size, algo_changed, "change from");
internal void update_order_failed(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
_log_order_update(order, price, size, algo_changed, "failed to change from");
void _log_order_update(OrderRecordImmutable order, double price, uint size, bool algo_changed, string change_desc_prefix) {
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
// nothing actually changed
return;
}
const string algo_change_text = "change algo parameters";
internal void update_order(OrderRecordImmutable order, double price, uint size) {
var security = try_get_security(order.Def.SecurityKey);
var spec = security?.ContractSpec;
var desc_builder = new StringBuilderEx();
if (price != order.price() || size != order.working_quantity()) {
desc_builder.Add(order.State.Quantity.ToString(), "@", spec.format(order.State.Price), "to ", size.ToString(), "@", spec.format(price));
if (algo_changed)
desc_builder.Add("; ", algo_change_text);
} else if (algo_changed) {
desc_builder.Add(algo_change_text);
}
desc_builder.Insert(0, $"{change_desc_prefix} ");
desc_builder.Insert(0, $"({order.Key}) ");
log_status(ActivityLogCategory.modify, security, desc_builder.ToString());
log_status(ActivityLogCategory.modify,
security,
$"({order.Key}) change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
}
internal void update_order_failed(OrderRecordImmutable order, double price, uint size) {
var security = try_get_security(order.Def.SecurityKey);
var spec = security?.ContractSpec;
log_status(ActivityLogCategory.modify,
security,
$"({order.Key}) failed to change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
}
}
}
@@ -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();
}
}
@@ -373,9 +346,9 @@ namespace mbts.spark {
_time_in_force.observe_current(start_immediately);
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
_model.algo_model.templates_enabled() ?
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
_model.algo_model.templates_enabled() ?
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
public OrderDefinition create(Side side) {
if (HasErrors)
@@ -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;
+1 -18
View File
@@ -3,34 +3,21 @@ 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;
@@ -41,10 +28,6 @@ namespace bts.api.orderman {
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;
@@ -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;
@@ -15,9 +11,8 @@ using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel> {
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 {
@@ -41,33 +37,29 @@ namespace mbts.spark {
impl.is_expanded = value;
}
}
public AlgoEditingPermissions editing_permissions => _editing_permissions;
public bool can_edit_values => impl?.can_edit_values ?? false;
public bool can_edit_structure => impl?.can_edit_structure ?? false;
public bool can_save_values => impl?.can_save_values ?? false;
public bool can_save_structure => impl?.can_save_structure ?? false;
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 void set_permissions(AlgoEditingPermissions editing_permissions) {
_editing_permissions = editing_permissions;
impl?.set_permissions(editing_permissions);
}
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 +76,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 +88,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,15 +96,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() { }
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) {
Set(ref _editing_permissions, editing_permissions, nameof(this.editing_permissions));
(impl as IObserver<AlgoEditingPermissions>)?.OnNext(editing_permissions);
}
void _on_impl_definition_changed() {
@@ -124,7 +105,6 @@ namespace mbts.spark {
definition_changed?.Invoke();
if (order_type != orig_def.CustomCase)
RaisePropertyChanged(nameof(order_type), nameof(meta));
RaisePropertyChanged(nameof(children_vm));
}
}
}
}
@@ -2,7 +2,6 @@ using bts.api;
using bts.api.orderman;
using bts.utils;
using Google.Protobuf.Reflection;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -12,9 +11,8 @@ using System.Reactive.Linq;
namespace mbts.spark {
[Export]
class DynamicOrderHelper {
public readonly IMaskIndexInt32<string> dynamic_type_index;
public class DynamicOrderHelper
{
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
@@ -23,11 +21,8 @@ namespace mbts.spark {
private DynamicOrderHelper(OrderService order_service)
{
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
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)
{
@@ -35,9 +30,9 @@ namespace mbts.spark {
{
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
var ddef = dorder.Dynamic;
//Add only the descriptor here for reflection later
add_dynamic_params(ref ddef);
foreach(var p in d.ParamDesc)
dorder.Dynamic.OrderParams.Add( p.ParamName, new DynamicOrderParam() );
defs.Add(new CustomOrderDefinitionImmutable(dorder));
}
}
@@ -47,45 +42,39 @@ namespace mbts.spark {
internal DynamicOrderDefinition create_def(string dynamic_type) {
var desc = get_descriptor(dynamic_type);
var def = new DynamicOrderDefinition { DynamicOrderType = dynamic_type };
add_dynamic_params(ref def);
var def = new DynamicOrderDefinition();
foreach (var p in desc.ParamDesc) {
if (_is_child_algo_desc(p)) {
def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
} else if (_is_value_param_desc(p)) {
var value_case = _get_value_case(p.ParamType.Primitive);
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
var dop = new DynamicOrderParam();
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
def.OrderParams[p.ParamName] = dop;
}
}
}
return def;
}
internal void add_dynamic_params(ref DynamicOrderDefinition dynamic_def) {
var desc = get_descriptor(dynamic_def.DynamicOrderType);
foreach (var p in desc.ParamDesc)
dynamic_def.OrderParams[p.ParamName] = init_param(p);
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
switch (primitive_descriptor_case) {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return DynamicOrderParam.ValueOneofCase.AsBool;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DynamicOrderParam.ValueOneofCase.AsBool;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return DynamicOrderParam.ValueOneofCase.AsInt32;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return DynamicOrderParam.ValueOneofCase.AsUint32;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return DynamicOrderParam.ValueOneofCase.AsString;
default: throw ExceptionBuilder.Argument.unexpected_case(primitive_descriptor_case, nameof(primitive_descriptor_case));
}
}
internal static DynamicOrderParam init_param(DynamicOrderParamDescriptor descriptor) {
var param = new DynamicOrderParam();
switch (descriptor.ParamType.DescriptorCase) {
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Array: param.AsArray = new DynamicOrderArray(); break;
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: param.AsEnumerated = new DynamicOrderEnumerated { EnumValue = descriptor.ParamType.Enumerated.Element.FirstOrDefault() }; break;
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive:
switch (descriptor.ParamType.Primitive) {
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: param.AsBool = false; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: param.AsDouble = 0.0; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
}
break;
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Structure: param.AsStruct = new DynamicOrderStruct(); break;
}
return param;
}
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
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;
}
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);
}
}
@@ -1,34 +1,15 @@
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; }
}
abstract class DynamicOrderParamVM : ViewModelBase {
DynamicOrderParam _param = new DynamicOrderParam();
interface IDynamicOrderParamVM<T> : IDynamicOrderParamVM {
T value { get; set; }
IObservable<T> subscribe_value();
}
abstract class DynamicOrderParamVM : DisposableViewModelBase, IDynamicOrderParamVM {
DynamicOrderParam _param;
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor) =>
this.descriptor = descriptor;
_param = param;
}
public DynamicOrderParamDescriptor descriptor { get; set; }
public DynamicOrderParam param {
@@ -40,80 +21,38 @@ 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));
}
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> {
static readonly FieldDescriptor _field_desc = DynamicOrderParam.Descriptor.FindFieldByNumber(DynamicOrderParam.AsEnumeratedFieldNumber);
public DynamicOrderEnumeratedVM(DynamicOrderParamDescriptor descriptor)
: base(descriptor) =>
elements = new ObservableCollection<string>(_get_elements(descriptor));
public ObservableCollection<string> elements { get; }
public string selected_element {
get => DynamicOrderHelper.get_enumerated_value(param);
set {
var orig = selected_element;
DynamicOrderHelper.set_enumerated_value(param, value);
if (selected_element != orig)
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();
RaisePropertyChanged(nameof(selected_element));
}
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
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,27 +17,28 @@ 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();
});
return Disposable.Create(() => p.PropertyChanged -= on_param_prop_changed);
}, true));
void on_param_prop_changed(object sender, PropertyChangedEventArgs e) => raise_definition_changed();
}
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 +52,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,38 @@ 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; }
bool can_edit_values { get; }
bool can_edit_structure { get; }
bool can_save_values { get; }
bool can_save_structure { get; }
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);
void set_permissions(AlgoEditingPermissions editing_permissions);
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 +90,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); }
}
}
}
@@ -7,23 +7,23 @@ using LeanDefi = bts.api.orderman.LeanOrderDefinitionImmutable;
namespace mbts.spark {
static partial class OrderTypeVMFactory {
class LeanOrderVM : OrderTypeVMBase<LeanDef, LeanDefi> {
readonly Property<QuantityMeasureImmutable?> _cross_threshold;
readonly Property<uint?> _max_chase_ticks;
readonly Property<LeanDef.Types.OnToward?> _trade_toward_behavior;
readonly Property<QuantityMeasureImmutable> _cross_threshold;
readonly Property<uint> _max_chase_ticks;
readonly Property<LeanDef.Types.OnToward> _trade_toward_behavior;
public LeanOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
: base(key, display_name, editing_permissions) {
_trade_toward_behavior = add_nullable_property(nameof(trade_toward_behavior), LeanDef.TradeTowardBehaviorFieldNumber, LeanDef.Types.OnToward.NoChange);
_trade_toward_behavior = add_property(nameof(trade_toward_behavior), LeanDef.TradeTowardBehaviorFieldNumber, LeanDef.Types.OnToward.NoChange);
_cross_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(cross_threshold), LeanDef.CrossThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
_max_chase_ticks = add_nullable_property(nameof(max_chase_ticks), LeanDef.MaxChaseTicksFieldNumber, 2u);
_max_chase_ticks = add_property(nameof(max_chase_ticks), LeanDef.MaxChaseTicksFieldNumber, 2u);
}
// trade toward behavior
public LeanDef.Types.OnToward? trade_toward_behavior { get => _trade_toward_behavior.Value; set => _trade_toward_behavior.set(value); }
public LeanDef.Types.OnToward trade_toward_behavior { get => _trade_toward_behavior.Value; set => _trade_toward_behavior.set(value); }
public IEnumerable<LeanDef.Types.OnToward> trade_toward_types => EnumHelper.get_values_except<LeanDef.Types.OnToward>();
// auxiliary
public QuantityMeasureImmutable? cross_threshold { get => _cross_threshold.Value; set => _cross_threshold.set(value); }
public uint? max_chase_ticks { get => _max_chase_ticks.Value; set => _max_chase_ticks.set(value); }
public QuantityMeasureImmutable cross_threshold { get => _cross_threshold.Value; set => _cross_threshold.set(value); }
public uint max_chase_ticks { get => _max_chase_ticks.Value; set => _max_chase_ticks.set(value); }
}
}
}
Loaded 100 of 575 files, more files were not shown because too many files have changed in this diff. Show more