Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eb3299006 | ||
|
|
7be1b658b4 | ||
|
|
06094f9ecb | ||
|
|
110ed184b9 | ||
|
|
7e4d447572 | ||
|
|
e3616e3514 | ||
|
|
3cc8921684 | ||
|
|
c9b3cd1560 | ||
|
|
5edd274144 | ||
|
|
596d800cfb | ||
|
|
7b7ae9d7e2 | ||
|
|
a1663c9d06 | ||
|
|
a8e043c600 | ||
|
|
d02fba8145 | ||
|
|
aacb19990b | ||
|
|
8686287def | ||
|
|
50232dec55 | ||
|
|
c236b30a50 | ||
|
|
7881601066 | ||
|
|
14990f2d8b | ||
|
|
1c97374e4d | ||
|
|
ef386080d9 | ||
|
|
21c9bfe910 | ||
|
|
cd053ee4db | ||
|
|
ecacafca5a | ||
|
|
e3361e53ee | ||
|
|
9c8024da29 | ||
|
|
16ddcb33a3 | ||
|
|
7ef89ef7c6 | ||
|
|
77e9c1b6de | ||
|
|
711302262b | ||
|
|
9ad3c5e34f | ||
|
|
253941d686 | ||
|
|
4413eb5ac6 | ||
|
|
4e9e25e883 | ||
|
|
cc2943494f | ||
|
|
859b9ac369 | ||
|
|
5365febd92 | ||
|
|
cdb40d5b3d |
No files matched your search
@@ -5,12 +5,14 @@ using bts.spark;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
|
||||
namespace bts.shell {
|
||||
@@ -30,16 +32,23 @@ namespace bts.shell {
|
||||
_error_reporter = error_reporter;
|
||||
_dispose = Disposable.Create(
|
||||
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => _md_map = m),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketAtPositionTag).OnDispatcher().Subscribe(_show_order_ticket_at_position)
|
||||
);
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
void _show_order_window_at_position(Window window, Point window_start_position) {
|
||||
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
|
||||
window.Resources[kv.Key] = kv.Value;
|
||||
window.ShowAtPosition(window_start_position, true);
|
||||
}
|
||||
|
||||
void _show_order_window(Window window) {
|
||||
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(_model.algo_model.ui_configuration))
|
||||
window.Resources[kv.Key] = kv.Value;
|
||||
@@ -60,6 +69,14 @@ namespace bts.shell {
|
||||
});
|
||||
}
|
||||
|
||||
void _show_order_ticket_at_position((OrderDefinitionImmutable def, double price, uint size, Point window_start_position) ops) {
|
||||
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
|
||||
_show_order_window_at_position(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons), true, ops.def.Side)
|
||||
}, ops.window_start_position);
|
||||
}
|
||||
|
||||
void _show_modify_order(uint order_key) {
|
||||
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
|
||||
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace bts.shell
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
|
||||
public MarketShellModule(IBus bus, IWindowManager wm, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, wm, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
TimeAndSalesWindowFactory _tsf;
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM {
|
||||
@@ -35,13 +36,15 @@ namespace mbts.spark {
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
readonly MarketViewVM _owner;
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model, MarketViewVM owner) : base(smd) {
|
||||
_bus = bus;
|
||||
_owner = owner;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
@@ -75,16 +78,16 @@ namespace mbts.spark {
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
bid_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidMergedSize));
|
||||
bid_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidDirectSize));
|
||||
bid_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.BidImpliedSize));
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids));
|
||||
ask_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Ask));
|
||||
ask_merged_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskMergedSize));
|
||||
ask_direct_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskDirectSize));
|
||||
ask_implied_size_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.AskImpliedSize));
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks));
|
||||
bid_price_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.Bid, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
bid_merged_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.BidMergedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
bid_direct_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.BidDirectSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
bid_implied_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.BidImpliedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
working_bids_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingBids), () => working_bids_text != string.Empty);
|
||||
ask_price_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.Ask, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
ask_merged_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.AskMergedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
ask_direct_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.AskDirectSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
ask_implied_size_click = new DelegateCommand<(Side side, HyperlinkCell hlc)>((args) => _show_order_ticket(MarketViewView.Columns.AskImpliedSize, args), (args) => args.side != Side.Undefined && args.hlc != null);
|
||||
working_asks_click = new DelegateCommand(() => _show_order_book(MarketViewView.Columns.WorkingAsks), () => working_asks_text != string.Empty);
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
@@ -217,38 +220,39 @@ namespace mbts.spark {
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
void _show_order_ticket(MarketViewView.Columns col, (Side side, HyperlinkCell hlc) args) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
|
||||
switch (col) {
|
||||
case MarketViewView.Columns.Bid:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, 0u));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, 0u, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.BidMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_merged_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, bid_merged_size, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.BidDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_direct_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, bid_direct_size, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.BidImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), bid_price, bid_implied_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, bid_price, bid_implied_size, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.Ask:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, 0u));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, 0u, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.AskMergedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Buy), ask_price, ask_merged_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, ask_merged_size, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.AskDirectSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_direct_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, ask_direct_size, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.AskImpliedSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order.with(side:Side.Sell), ask_price, ask_implied_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, ask_price, ask_implied_size, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.Last:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, 0u));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, last_price, 0u, args.hlc));
|
||||
break;
|
||||
case MarketViewView.Columns.LastSize:
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (order, last_price, last_size));
|
||||
_owner._raise_open_order_ticket_at_position((order.with(side: args.side), security, last_price, last_size, args.hlc));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
@@ -22,6 +23,9 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
public event Action<(OrderDefinitionImmutable order_def, SecurityImmutable security, double price, uint size, HyperlinkCell hlc)> open_order_ticket_at_position;
|
||||
internal void _raise_open_order_ticket_at_position((OrderDefinitionImmutable order_def, SecurityImmutable security, double price, uint size, HyperlinkCell hlc) args) => open_order_ticket_at_position?.Invoke(args);
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
@@ -41,17 +45,17 @@ namespace mbts.spark {
|
||||
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.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, Side.Buy, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, 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);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, Side.Buy, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, Side.Buy, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, Side.Sell, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, Side.Sell, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, Side.Sell, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -77,20 +81,20 @@ namespace mbts.spark {
|
||||
horizontal_alignment = h_alignment
|
||||
});
|
||||
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
|
||||
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, Side market_side, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, market_side, 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, 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, Side market_side, 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, market_side, 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, 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, Side market_side, 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, market_side, h_alignment);
|
||||
|
||||
void add_hl_col(
|
||||
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new HyperlinkElementColumn<Row, Columns>(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template) {
|
||||
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, Side market_side, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new MarketViewHyperlinkElementColumn(
|
||||
col_key, binding, new Binding(command_prop), filter, filter_template, market_side) {
|
||||
// it's necessary to have this id populated for grid settings
|
||||
id = bts.shell.MarketViewWindowFactory.col_id(col_key),
|
||||
Header = header,
|
||||
@@ -271,7 +275,7 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model);
|
||||
protected override Row row_factory(SecurityMD smd) => new Row(_bus, smd, _orders_model, this);
|
||||
protected override void on_row_removed(Row row) => row.Dispose();
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.security), true); }
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
readonly IWindowManager _wm;
|
||||
MarketViewVM _vm;
|
||||
MarketViewView _view;
|
||||
MarketViewWindowFactory.StartupData _state;
|
||||
@@ -33,11 +34,12 @@ namespace mbts.spark {
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager, IWindowManager wm)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
_wm = wm;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
@@ -104,6 +106,20 @@ namespace mbts.spark {
|
||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
|
||||
var tab_height = Application.Current.TryFindResource("TabHeight") as Double? ?? 30;
|
||||
_vm.open_order_ticket_at_position += (args) => {
|
||||
if (_view.try_find_ancestor_of_type<System.Windows.Controls.Grid>(out var grid) && grid.Name == "_grid") {
|
||||
var pos = Application.Current.Dispatcher.Invoke(() => _wm.GetPosition(this));
|
||||
var coords = args.hlc.TranslatePoint(new Point(0, 0), grid);
|
||||
/* X = (Floating Window left pos) + (Floating Window BorderThickness) + (window_border left border) */
|
||||
coords.X += pos.left + 1 + 7;
|
||||
/* Y = (Floating Window top pos) + (Floating Window BorderThickness) + (window_border top border) + tab height + row_height */
|
||||
coords.Y += pos.top + 1 + 1 + tab_height + args.hlc.ActualHeight;
|
||||
|
||||
_bus.post(SparkTags.ShowOrderTicketAtPositionTag, (args.order_def, args.price, args.size, coords));
|
||||
}
|
||||
};
|
||||
_view._grid.Sorting += on_grid_sorting;
|
||||
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
|
||||
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
|
||||
|
||||
@@ -21,10 +21,12 @@ namespace bts.shell {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly IWindowManager _wm;
|
||||
|
||||
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
_bus = bus;
|
||||
_wm = wm;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
|
||||
@@ -39,7 +41,7 @@ namespace bts.shell {
|
||||
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
|
||||
|
||||
IShellWindow _create(StartupData state) =>
|
||||
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid));
|
||||
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid), _wm);
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
|
||||
@@ -31,9 +31,16 @@ namespace mbts.spark {
|
||||
|
||||
class NewOrderTicketVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly Property<SecurityImmutable> _security;
|
||||
readonly bool _is_broker_mode;
|
||||
|
||||
public NewOrderTicketVM(OrdersModel model, SecurityMD security_md, OrderDefinitionImmutable def, double price, uint size, IObservable<int[]> calculator_buttons)
|
||||
: this(model, Observable.Return(security_md), Observable.Return(new InlineOrderTicketProperties(model, def, price, size)), calculator_buttons) { }
|
||||
readonly Property<bool> _buy_is_visible;
|
||||
// need this one too b/c the determination is made off of _is_broker_mode as well
|
||||
readonly Property<bool> _sell_is_visible;
|
||||
readonly Property<Side> _broker_mode_side;
|
||||
|
||||
|
||||
public NewOrderTicketVM(OrdersModel model, SecurityMD security_md, OrderDefinitionImmutable def, double price, uint size, IObservable<int[]> calculator_buttons, bool is_broker_mode = false, Side broker_mode_side = Side.Undefined)
|
||||
: this(model, Observable.Return(security_md), Observable.Return(new InlineOrderTicketProperties(model, def, price, size)), calculator_buttons, is_broker_mode, broker_mode_side) { }
|
||||
|
||||
public NewOrderTicketVM(
|
||||
OrdersModel model,
|
||||
@@ -45,9 +52,15 @@ namespace mbts.spark {
|
||||
OrdersModel model,
|
||||
IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineOrderTicketProperties> props_source,
|
||||
IObservable<int[]> calculator_buttons
|
||||
IObservable<int[]> calculator_buttons,
|
||||
bool is_broker_mode = false,
|
||||
Side init_broker_mode_side = Side.Undefined
|
||||
) {
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
_is_broker_mode = is_broker_mode;
|
||||
_broker_mode_side = backing(nameof(broker_mode_side), () => init_broker_mode_side);
|
||||
_buy_is_visible = backing(nameof(buy_is_visible), () => !is_broker_mode || (is_broker_mode && broker_mode_side == Side.Buy), nameof(broker_mode_side));
|
||||
_sell_is_visible = backing(nameof(sell_is_visible), () => !is_broker_mode || (is_broker_mode && broker_mode_side == Side.Sell), nameof(broker_mode_side));
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
|
||||
bbo = new BBOVM();
|
||||
send_order = new DelegateCommand<Side>(side => {
|
||||
@@ -58,6 +71,17 @@ namespace mbts.spark {
|
||||
}
|
||||
});
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
set_price_and_side_command = new DelegateCommand<Tuple<double, Side>>((pair) => {
|
||||
(price, broker_mode_side) = pair;
|
||||
RaisePropertyChanged(nameof(price));
|
||||
});
|
||||
set_size_and_price_and_side_command = new DelegateCommand<Tuple<uint, double, Side>>((triplet) =>
|
||||
{
|
||||
size_calculator.set_value_command?.Execute(triplet.Item1);
|
||||
price = triplet.Item2;
|
||||
RaisePropertyChanged(nameof(price));
|
||||
broker_mode_side = triplet.Item3;
|
||||
});
|
||||
_dispose = Disposable.Create(
|
||||
bbo,
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
@@ -86,6 +110,12 @@ namespace mbts.spark {
|
||||
public BBOVM bbo { get; }
|
||||
public ICommand send_order { get; }
|
||||
public ICommand set_price_command { get; }
|
||||
public ICommand set_price_and_side_command { get; }
|
||||
public ICommand set_size_and_price_and_side_command { get; }
|
||||
public bool buy_is_visible { get => _buy_is_visible.Value; private set => _buy_is_visible.Value = value; }
|
||||
public bool sell_is_visible { get => _sell_is_visible.Value; private set => _sell_is_visible.Value = value; }
|
||||
public bool is_broker_mode => _is_broker_mode;
|
||||
public Side broker_mode_side { get => _broker_mode_side.Value; private set => _broker_mode_side.Value = value; }
|
||||
|
||||
bool _validate_price() {
|
||||
if (DoubleHelper.IsNaNOrInfinity(price)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<UserControl x:Class="mbts.ui.spark.OrderModificationControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
@@ -86,7 +87,14 @@
|
||||
CommitOnEnter="True"
|
||||
Focusable="False"
|
||||
Value="{Binding size_calculator.value}"
|
||||
/>
|
||||
>
|
||||
<c:TickUpDownNullableUInt.Resources>
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Setter Property="u:TextBoxBehavior.FocusOnLoaded" Value="True"/>
|
||||
<Setter Property="u:TextBoxBehavior.IncrementDecrementOnMouseClick" Value="True" />
|
||||
</Style>
|
||||
</c:TickUpDownNullableUInt.Resources>
|
||||
</c:TickUpDownNullableUInt>
|
||||
</Grid>
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -94,17 +102,29 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Button Command="{Binding set_price_and_side_command}" Tag="{u:Enum Buy, Type=msys:Side}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{StaticResource TupleConverter}">
|
||||
<Binding Path="bbo.bid"/>
|
||||
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Button Grid.Column="2" Command="{Binding set_price_and_side_command}" Tag="{u:Enum Sell, Type=msys:Side}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{StaticResource TupleConverter}">
|
||||
<Binding Path="bbo.ask"/>
|
||||
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Grid.Column="2">
|
||||
@@ -114,15 +134,36 @@
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Content="{Binding bbo.bid_size}" Command="{Binding size_calculator.set_value_command}"
|
||||
CommandParameter="{Binding bbo.bid_size}" Foreground="{u:BTSColor MarketData.bid}"
|
||||
/>
|
||||
Content="{Binding bbo.bid_size}" Command="{Binding set_size_and_price_and_side_command}"
|
||||
Foreground="{u:BTSColor MarketData.bid}">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{StaticResource TupleConverter}">
|
||||
<Binding Path="bbo.bid_size"/>
|
||||
<Binding Path="bbo.bid"/>
|
||||
<Binding>
|
||||
<Binding.Source>
|
||||
<msys:Side>Buy</msys:Side>
|
||||
</Binding.Source>
|
||||
</Binding>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Content="{Binding bbo.ask_size}" Command="{Binding size_calculator.set_value_command}"
|
||||
CommandParameter="{Binding bbo.ask_size}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
/>
|
||||
Content="{Binding bbo.ask_size}" Command="{Binding set_size_and_price_and_side_command}"
|
||||
Foreground="{u:BTSColor MarketData.ask}">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{StaticResource TupleConverter}">
|
||||
<Binding Path="bbo.ask_size"/>
|
||||
<Binding Path="bbo.ask"/>
|
||||
<Binding>
|
||||
<Binding.Source>
|
||||
<msys:Side>Sell</msys:Side>
|
||||
</Binding.Source>
|
||||
</Binding>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.RowSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}">
|
||||
Style="{StaticResource BTSWindowTitleTemplate}"
|
||||
Title="Order Ticket">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
@@ -23,9 +24,12 @@
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Visibility" Value="{Binding sell_is_visible, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Setter Property="Grid.Column" Value="2" />
|
||||
<Setter Property="Grid.ColumnSpan" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Sell</TextBlock>
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Sell</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
@@ -56,6 +60,16 @@
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding is_broker_mode}" Value="True" />
|
||||
<Condition Binding="{Binding sell_is_visible}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3" />
|
||||
<Setter Property="Grid.Column" Value="0" />
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}" />
|
||||
@@ -64,6 +78,8 @@
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Visibility" Value="{Binding buy_is_visible, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Setter Property="Grid.ColumnSpan" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{u:BTSColor UI.alternate_text}">Buy</TextBlock>
|
||||
@@ -97,6 +113,15 @@
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding is_broker_mode}" Value="True" />
|
||||
<Condition Binding="{Binding buy_is_visible}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Grid.ColumnSpan" Value="3" />
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@@ -115,9 +140,9 @@
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3" />
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
|
||||
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
|
||||
</Grid>
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
|
||||
<Button Grid.Row="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
|
||||
</Grid>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
|
||||
@@ -330,6 +330,13 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static Side opposite(this Side side) {
|
||||
switch (side) {
|
||||
case Side.Buy: return Side.Sell;
|
||||
case Side.Sell: return Side.Buy;
|
||||
default: return Side.Undefined;
|
||||
}
|
||||
}
|
||||
public static AggressorSide to_aggressor(this Side side) {
|
||||
switch (side) {
|
||||
case Side.Buy: return AggressorSide.Buy;
|
||||
|
||||
@@ -8,7 +8,7 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.SparkBusData;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
@@ -30,6 +30,7 @@ namespace mbts.spark
|
||||
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
|
||||
public static ITag<SecurityMD> ShowOrderTicketFromSecurityTag = new SparkTag<SecurityMD>(nameof(ShowOrderTicketFromSecurityTag));
|
||||
public static ITag<(OrderDefinitionImmutable def, double price, uint size)> ShowOrderTicketTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size)>(nameof(ShowOrderTicketTag));
|
||||
public static ITag<(OrderDefinitionImmutable def, double price, uint size, Point window_start_position)> ShowOrderTicketAtPositionTag = new SparkTag<(OrderDefinitionImmutable def, double price, uint size, Point window_start_position)>(nameof(ShowOrderTicketAtPositionTag));
|
||||
#endregion
|
||||
|
||||
#region Selection/Utility
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
@@ -10,6 +11,7 @@ namespace mbts.ui {
|
||||
void remove(IShellWindow window);
|
||||
bool contains(IShellWindow window);
|
||||
void clear();
|
||||
WindowPosition GetPosition(IShellWindow shell_window);
|
||||
|
||||
// Bring to front of window stack and z-order
|
||||
void MakeVisible(IShellWindow window);
|
||||
|
||||
@@ -12,6 +12,9 @@ using FontFamily = System.Windows.Media.FontFamily;
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
public class HyperlinkCell : FrameworkElement, ICommandSource {
|
||||
static Brush _hover_background_highlight = new SolidColorBrush(Colors.Yellow) { Opacity = 0.5 }.frozen();
|
||||
static Brush _hover_foreground_brush = new SolidColorBrush(Colors.Black).frozen();
|
||||
static Pen _hover_underline_pen = new Pen(_hover_foreground_brush, 1);
|
||||
// these fields each represent properties that are accessed frequently,
|
||||
// they are stored locally to reduce repetative DependencyProperty lookups
|
||||
Thickness _padding;
|
||||
@@ -49,12 +52,14 @@ namespace mbts.ui.controls {
|
||||
_font_size = FontSize;
|
||||
_text = Text;
|
||||
_glyph_context = GlyphContext.create(FontFamily, FontWeight);
|
||||
AddHandler(MouseDownEvent, new RoutedEventHandler(on_hyperlink_click_command), true);
|
||||
IsMouseDirectlyOverChanged += (s, e) => {
|
||||
if (_is_mouse_over_text != IsMouseDirectlyOver)
|
||||
_toggle_mouse_over_text();
|
||||
_toggle_mouse_cursor();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Background
|
||||
public static readonly DependencyProperty BackgroundProperty =
|
||||
WPFHelper.create_dp<Brush, HyperlinkCell>(
|
||||
@@ -221,32 +226,53 @@ namespace mbts.ui.controls {
|
||||
|
||||
#endregion
|
||||
|
||||
#region EnableHoverEffect
|
||||
public static readonly DependencyProperty EnableHoverEffectProperty =
|
||||
WPFHelper.create_dp<bool, HyperlinkCell>(nameof(EnableHoverEffect), false);
|
||||
|
||||
public bool EnableHoverEffect {
|
||||
get => (bool)GetValue(EnableHoverEffectProperty);
|
||||
set => SetValue(EnableHoverEffectProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Paraphrased/Simplified from https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Documents/Hyperlink.cs
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
// Note that OnMouseDown doesn't appear to fire when used in a MarketViewHyperlinkElementColumn due to the
|
||||
// InputBindings, we think, setting e.Handled to true.
|
||||
// Instead, we AddHandler to the MouseDownEvent in the constructor and use the
|
||||
// callback below, on_hyperlink_click_command, to reset Handled
|
||||
// so that it continues propagating down to the ListBoxItem. This in turn,
|
||||
// causes the ListBoxItem to be selected, which is then reflected in the
|
||||
// RowSelectionAdorner.
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e) {
|
||||
base.OnMouseDown(e);
|
||||
_check_mouse_over_text(e);
|
||||
if (_is_mouse_over_text) {
|
||||
// Hyperlink should take focus when left mouse button is clicked on it
|
||||
// This is consistent with all ButtonBase controls and current Win32 behavior
|
||||
Focus();
|
||||
|
||||
// It is possible that the mouse state could have changed during all of
|
||||
// the call-outs that have happened so far.
|
||||
if (e.ButtonState == MouseButtonState.Pressed) {
|
||||
// Capture the mouse, and make sure we got it.
|
||||
CaptureMouse();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
// It is possible that the mouse state could have changed during all of
|
||||
// the call-outs that have happened so far.
|
||||
if (e.ButtonState == MouseButtonState.Pressed) {
|
||||
// Capture the mouse, and make sure we got it.
|
||||
CaptureMouse();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) {
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
private void on_hyperlink_click_command(object sender, RoutedEventArgs e) {
|
||||
e.Handled = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseButtonEventArgs e) {
|
||||
base.OnMouseUp(e);
|
||||
_check_mouse_over_text(e);
|
||||
if (IsMouseCaptured)
|
||||
ReleaseMouseCapture();
|
||||
if (_can_execute && _is_mouse_over_text)
|
||||
if (_can_execute)
|
||||
CommandHelper.ExecuteCommandSource(this);
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -279,12 +305,21 @@ namespace mbts.ui.controls {
|
||||
var rs = RenderSize;
|
||||
var rect = new Rect(0, 0, rs.Width, rs.Height);
|
||||
var bg = Background;
|
||||
if (bg != null)
|
||||
drawingContext.DrawRectangle(bg, null, rect);
|
||||
if (bg != null) {
|
||||
if (_glyph_run != null && IsMouseDirectlyOver && EnableHoverEffect) {
|
||||
drawingContext.DrawRectangle(_hover_background_highlight, null, rect);
|
||||
} else {
|
||||
drawingContext.DrawRectangle(bg, null, rect);
|
||||
}
|
||||
}
|
||||
if (_glyph_run != null) {
|
||||
drawingContext.PushClip(new RectangleGeometry(rect));
|
||||
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
|
||||
if (_is_mouse_over_text) {
|
||||
if (IsMouseDirectlyOver) {
|
||||
if (EnableHoverEffect) {
|
||||
drawingContext.DrawGlyphRun(_hover_foreground_brush, _glyph_run);
|
||||
} else {
|
||||
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
|
||||
}
|
||||
// draw underline
|
||||
Point pt;
|
||||
switch (_horizontal_alignment) {
|
||||
@@ -299,9 +334,15 @@ namespace mbts.ui.controls {
|
||||
pt = new Point(0.5 * (ActualWidth - _glyph_width), 0.5 * (ActualHeight + _font_size) + 1);
|
||||
break;
|
||||
}
|
||||
drawingContext.DrawLine(_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
|
||||
|
||||
if (EnableHoverEffect) {
|
||||
drawingContext.DrawLine(_hover_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
|
||||
} else {
|
||||
drawingContext.DrawLine(_underline_pen, pt, new Point(pt.X + _glyph_width, pt.Y));
|
||||
}
|
||||
} else {
|
||||
drawingContext.DrawGlyphRun(_foreground, _glyph_run);
|
||||
}
|
||||
|
||||
drawingContext.Pop();
|
||||
}
|
||||
}
|
||||
@@ -331,10 +372,15 @@ namespace mbts.ui.controls {
|
||||
|
||||
void _toggle_mouse_over_text() {
|
||||
_is_mouse_over_text = !_is_mouse_over_text;
|
||||
if (_is_mouse_over_text)
|
||||
_toggle_mouse_cursor();
|
||||
}
|
||||
|
||||
void _toggle_mouse_cursor() {
|
||||
if (_glyph_run != null && IsMouseDirectlyOver) {
|
||||
SetCurrentValue(CursorProperty, Cursors.Hand);
|
||||
else
|
||||
} else {
|
||||
ClearValue(CursorProperty);
|
||||
}
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
+41
-7
@@ -1,9 +1,11 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.converters;
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.spark;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Input;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
@@ -12,19 +14,21 @@ namespace mbts.ui.controls {
|
||||
readonly BindingBase _binding, _command_binding;
|
||||
|
||||
public HyperlinkElementColumn(
|
||||
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template) : base(key) {
|
||||
TKey key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template, bool enable_hover_effect = false) : base(key) {
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
_binding = binding;
|
||||
_command_binding = command_binding;
|
||||
this.enable_hover_effect = enable_hover_effect;
|
||||
}
|
||||
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public string filter_template { get; }
|
||||
public Thickness padding { get; set; } = _default_padding;
|
||||
|
||||
public bool enable_hover_effect { get; set; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var hl = new HyperlinkCell { Padding = padding };
|
||||
var hl = new HyperlinkCell { Padding = padding, EnableHoverEffect = enable_hover_effect };
|
||||
hl.SetBinding(HyperlinkCell.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
hl.SetBinding(HyperlinkCell.TextProperty, _binding);
|
||||
hl.SetBinding(HyperlinkCell.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
@@ -36,5 +40,35 @@ namespace mbts.ui.controls {
|
||||
return hl;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MarketViewHyperlinkElementColumn : HyperlinkElementColumn<MarketViewVM.Row, MarketViewView.Columns> {
|
||||
|
||||
readonly Side _market_side;
|
||||
|
||||
public MarketViewHyperlinkElementColumn(MarketViewView.Columns key, BindingBase binding, BindingBase command_binding, IDataGridColumnFilter filter, string filter_template, Side market_side) : base(key, binding, command_binding, filter, filter_template, true) {
|
||||
_market_side = market_side;
|
||||
}
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var hl = base.view_factory();
|
||||
hl.InputBindings.Add(new MouseBinding(HyperLinkRoutedCommands.rc, new MouseGesture(MouseAction.LeftClick)) { CommandParameter = _market_side });
|
||||
hl.InputBindings.Add(new MouseBinding(HyperLinkRoutedCommands.rc, new MouseGesture(MouseAction.RightClick)) { CommandParameter = _market_side.opposite() });
|
||||
hl.CommandBindings.Add(new CommandBinding(HyperLinkRoutedCommands.rc, HyperLinkRoutedCommands.create_command_with_coordinates<Side>()));
|
||||
return hl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class HyperLinkRoutedCommands {
|
||||
|
||||
public static RoutedCommand rc = new RoutedCommand();
|
||||
public static ExecutedRoutedEventHandler create_command_with_coordinates<T>() =>
|
||||
(s, e) => {
|
||||
if (s is HyperlinkCell hl && e.Parameter is T orig_param) {
|
||||
hl.Command.TryExecuteIfCan((orig_param, hl));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,9 @@
|
||||
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
|
||||
BorderThickness="0"
|
||||
>
|
||||
<TextBox.ContextMenu>
|
||||
<ContextMenu Visibility="Collapsed" />
|
||||
</TextBox.ContextMenu>
|
||||
<TextBox.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:TextBoxBehavior.SelectAllCommand}" Executed="{x:Static u:TextBoxBehavior.SelectAllCommandExecutedHandler}"/>
|
||||
</TextBox.CommandBindings>
|
||||
|
||||
@@ -429,7 +429,11 @@
|
||||
Name="PART_TextBox"
|
||||
u:TextBoxBehavior.EnableArrowKeyTraverse="True"
|
||||
BorderThickness="0"
|
||||
/>
|
||||
>
|
||||
<TextBox.ContextMenu>
|
||||
<ContextMenu Visibility="Collapsed" />
|
||||
</TextBox.ContextMenu>
|
||||
</TextBox>
|
||||
</c:TickUpDown>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
|
||||
@@ -312,5 +312,17 @@ namespace mbts.ui {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IDisposable ShowAtPosition(this Window window, Point coords, bool dispose_vm_on_close = false) {
|
||||
if (dispose_vm_on_close) window.Closed += (s, e) => (window.DataContext as IDisposable)?.Dispose();
|
||||
window.set_position(new WindowPosition() { top = (int)coords.Y, left = (int)coords.X });
|
||||
window.Show();
|
||||
return Disposable.Create(() => {
|
||||
// not very elegant, but safe and effective
|
||||
if (window != null) {
|
||||
try { window.Close(); } catch { }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,3 +15,73 @@ Now update securities to add an exchange listed spread
|
||||
Double click each of the security cells and verify a ladder comes up
|
||||
Right click in the grid and bring up each form, close them
|
||||
Test each Spark Menu (toolbar) item other than update securities with a security row selected, make sure context from the selected row is respected if applicable
|
||||
|
||||
=== "broker mode" functionality test steps circa Sep 2019 (MV and corresponding OrderTicket)
|
||||
|
||||
-- MV Hyperlink cell highlighting --
|
||||
|
||||
Bid/Ask column test - Verify price columns reflect a hover effect when the mouse is over their cells
|
||||
The background will change to yellow
|
||||
The foreground hyperlink cell text will change to black
|
||||
The cursor changes to a hand anywhere within the cell (not only over the hyperlink text itself)
|
||||
|
||||
Qty (M) column test - Verify the Qty (Merged) column reflect a hover effect
|
||||
same as above
|
||||
|
||||
Working column test - Verify the Working columns reflect a hover effect
|
||||
Note: the hover effect should only occur over cell that actually have text
|
||||
Clicking on an empty working column cell should *not* launch an OrderBook
|
||||
|
||||
-- broker mode OrderTicket --
|
||||
|
||||
"Broker mode" general notes:
|
||||
Order Tickets should appear immediately below the cell from which they were clicked
|
||||
The qty box has focus an is ready to be typed into immediately
|
||||
The qty box has the same left/right mouse behavior as the ladder qty for quick increment/decrement
|
||||
There is only 1 big Buy/Sell button on the bottom
|
||||
|
||||
Mousebutton action to join/cross
|
||||
Left click on a bid price cell
|
||||
A buy-only ticket launches (i.e. your intent is to join the bid)
|
||||
Right click on a bid price cell
|
||||
A sell-only ticket launches (i.e. your intent is to cross the bid)
|
||||
Left click on a ask price cell
|
||||
A sell-only ticket launches (i.e. your intent is to join the ask)
|
||||
Right click on a sell price cell
|
||||
A buy-only ticket launches (i.e. your intent is to cross the ask)
|
||||
|
||||
Order Ticket price buttons
|
||||
Launch a buy order ticket by joining a bid
|
||||
Click on the Ask Price button
|
||||
The price field should update to reflect the ask price at the time the button was clicked
|
||||
The single Buy button is replaced with a Sell button
|
||||
Now, click the "1" qty button
|
||||
Click Sell
|
||||
Verify a Sell Order was actually sent at the appropriate price and size
|
||||
Now, click on the Bid Price button
|
||||
The price field should update to reflect the bid price at the time the button was clicked
|
||||
The single Ask button is replaced with a Buy button
|
||||
Click Buy
|
||||
Verify a Buy Order was actually sent at the appropriate price and size
|
||||
Close the ticket
|
||||
Extra credit - repeat the above test by first lauching a sell order and swap everything else
|
||||
|
||||
Order Ticket size buttons
|
||||
Launch a buy order ticket by joining a bid
|
||||
Click on the Ask Size button
|
||||
The price field should update to reflect the ask price at the time the button was clicked
|
||||
The size field should update to reflect the ask size at the time the button was clicked
|
||||
The single Buy button is replaced with a Sell button
|
||||
Click Sell
|
||||
Verify a Sell order was sent at the appropriate price and size
|
||||
Now, click on the Bid Size button
|
||||
The price field should update to reflect the bid price at the time the button was clicked
|
||||
The size field should update to reflect the bid size at the time the button was clicked
|
||||
The single Sell button is replaced with a Buy button
|
||||
Click Buy
|
||||
Verify a Buy order was actually sent at the appopriate price and size
|
||||
Close the ticket
|
||||
Extra credit - repeat the above test by first lauching a sell order and swap everything else
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user