Compare commits
181
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
719d460372 | ||
|
|
7b0549c4cb | ||
|
|
2ee0568159 | ||
|
|
df907fcb96 | ||
|
|
b945f9f090 | ||
|
|
4cebe8b6be | ||
|
|
c899904f3a | ||
|
|
8e6ccd5b1b | ||
|
|
007054f2b8 | ||
|
|
43822134e3 | ||
|
|
68c1415cf8 | ||
|
|
d9b25fb778 | ||
|
|
0a3e12e012 | ||
|
|
8b33c7fb5c | ||
|
|
831b042c35 | ||
|
|
c9af55f59d | ||
|
|
056176ff47 | ||
|
|
297a925f22 | ||
|
|
11ca6326f3 | ||
|
|
efa78ee5fc | ||
|
|
c52b98d17c | ||
|
|
06506be398 | ||
|
|
d951942a95 | ||
|
|
1f99cc68f8 | ||
|
|
d4f27a65f5 | ||
|
|
2b3901fdbe | ||
|
|
fa51d7f441 | ||
|
|
2bc54b0dbb | ||
|
|
5b767482d7 | ||
|
|
8594708073 | ||
|
|
ac4cdc0e2d | ||
|
|
c66dfdaf27 | ||
|
|
dcd42367e1 | ||
|
|
1fd4212a4e | ||
|
|
aa5b88e1a2 | ||
|
|
3f86968937 | ||
|
|
0385376d04 | ||
|
|
914ec09b4f | ||
|
|
fd106559da | ||
|
|
a1270fb928 | ||
|
|
1f8b0bf1e6 | ||
|
|
292acd64e6 | ||
|
|
453f70259a | ||
|
|
18a80516d5 | ||
|
|
1ebd0e8d62 | ||
|
|
c09ef31061 | ||
|
|
613ba8004d | ||
|
|
a1ffc24ed9 | ||
|
|
c40055dd2f | ||
|
|
163b77ff84 | ||
|
|
1b1129320a | ||
|
|
589476eb02 | ||
|
|
fd0e1dfb0e | ||
|
|
e95eb4a749 | ||
|
|
8fb13bd3c3 | ||
|
|
9cfa229b04 | ||
|
|
72e7901145 | ||
|
|
c2788e4d61 | ||
|
|
0b5d2bd4dc | ||
|
|
98006a07e8 | ||
|
|
420273b861 | ||
|
|
09ed017bf2 | ||
|
|
8116700737 | ||
|
|
f902f163de | ||
|
|
d06bef463b | ||
|
|
38db7b28e0 | ||
|
|
485991b046 | ||
|
|
63cd653ffd | ||
|
|
cb0ec5f3b9 | ||
|
|
64e7921280 | ||
|
|
e5d31d3c89 | ||
|
|
219da9b22a | ||
|
|
b3bff8ecc3 | ||
|
|
c1fb53ff06 | ||
|
|
220af155a9 | ||
|
|
e7286e305d | ||
|
|
770b857f90 | ||
|
|
4b9e4b25c7 | ||
|
|
0f87973ee2 | ||
|
|
b6790aa7f5 | ||
|
|
6adbfda6c4 | ||
|
|
fef5b4edaa | ||
|
|
e8cccdb1c0 | ||
|
|
19dc7f8915 | ||
|
|
69f61757c4 | ||
|
|
666765ffbb | ||
|
|
82a9b4521a | ||
|
|
7c329ceebe | ||
|
|
66d05c63af | ||
|
|
bc3ab563bf | ||
|
|
ed23bc7e2b | ||
|
|
3dda3e7f75 | ||
|
|
ff461d42b8 | ||
|
|
d678d202c1 | ||
|
|
ad0b1756b1 | ||
|
|
f34f718c72 | ||
|
|
213fb1de59 | ||
|
|
e8d799a0f6 | ||
|
|
bef46390be | ||
|
|
4edd804b3c | ||
|
|
86733747f1 | ||
|
|
5202300fb1 | ||
|
|
7aae61decd | ||
|
|
64f78e6077 | ||
|
|
318ea5909c | ||
|
|
83190b437a | ||
|
|
5a54b37af7 | ||
|
|
12dd3909bf | ||
|
|
5ddb0f92ea | ||
|
|
69f3f1f467 | ||
|
|
be42f9e583 | ||
|
|
6d069f5174 | ||
|
|
7a31adcc1e | ||
|
|
512175966d | ||
|
|
67de7c5a33 | ||
|
|
8add53a559 | ||
|
|
3e7e572763 | ||
|
|
0d020e9821 | ||
|
|
fb1d70b08c | ||
|
|
c2f6b01567 | ||
|
|
4a758c7554 | ||
|
|
5c79a08f0e | ||
|
|
4b7adbe0c9 | ||
|
|
164c88fa60 | ||
|
|
b57d6cce85 | ||
|
|
e47706eb1b | ||
|
|
c2e91b03f6 | ||
|
|
114c9f3c27 | ||
|
|
40805be11e | ||
|
|
b21cd840c2 | ||
|
|
4854a06eda | ||
|
|
44e7cfb4a5 | ||
|
|
a7da930e69 | ||
|
|
d64df27188 | ||
|
|
93534eb801 | ||
|
|
0af5b78318 | ||
|
|
c652032ddf | ||
|
|
7bd868b753 | ||
|
|
1bc4510862 | ||
|
|
e452413b92 | ||
|
|
62c7886807 | ||
|
|
b1fb964648 | ||
|
|
ee19f7c7da | ||
|
|
a0bf7779f5 | ||
|
|
cd41632b53 | ||
|
|
95a5791867 | ||
|
|
ecd384501e | ||
|
|
0483460ac9 | ||
|
|
0bc24fa7b7 | ||
|
|
af365177d0 | ||
|
|
ac60a1447e | ||
|
|
e8b5d4f8fd | ||
|
|
cb157dbce1 | ||
|
|
370b4bcde9 | ||
|
|
6756b23104 | ||
|
|
c5f7ee0ed3 | ||
|
|
16eeddec7d | ||
|
|
445bbb10f5 | ||
|
|
70c006f797 | ||
|
|
307d3d9466 | ||
|
|
3a5810d8ac | ||
|
|
9ba2ba1d75 | ||
|
|
29c772cbb6 | ||
|
|
0b03180c4d | ||
|
|
1518906b7f | ||
|
|
31fe708907 | ||
|
|
4b2f39c536 | ||
|
|
040960d33b | ||
|
|
a28d5883f7 | ||
|
|
8a1d49733e | ||
|
|
0da6b2cc3e | ||
|
|
3f245cd886 | ||
|
|
570f85377a | ||
|
|
8afb35e564 | ||
|
|
a38a6057e0 | ||
|
|
dcd8984b33 | ||
|
|
6647b28a9f | ||
|
|
fb314f0eb1 | ||
|
|
8a965d41ff | ||
|
|
7d75d87e6b | ||
|
|
f13a3325bc |
No files matched your search
@@ -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/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;content/system_requirements/_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)
|
||||
@@ -4,6 +4,30 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.3.0
|
||||
### User Visible Changes
|
||||
- Introduced multiple settings profiles for the ladder
|
||||
- This allows multiple standard configuration for the ladder, optionally with security specific variations
|
||||
- Made Cover order more independent from its hedging chidren:
|
||||
- Cover continues to operate even if an hedging child is canceled
|
||||
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed manual fills to appear in day PnL rather than in the SOD records
|
||||
- Add buy/sell coloring to fill rows in fills window
|
||||
- Streamlined the mini order book:
|
||||
- Display of the mini order book popup is quicker
|
||||
- Only exchange orders are displayed
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
|
||||
- A checkbox column has been added to make clearer which rows are selected
|
||||
### API Changes
|
||||
- Enhanced the API to allow algos to implement custom cancel handling
|
||||
- Allowed algos to control their visibility in the ladder through the API
|
||||
### Internal Changes
|
||||
- Added system requirements to Spark documentation
|
||||
- Implemented keycodes
|
||||
- Temporarily rejected stop and stop limit orders, pending bug fixes
|
||||
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
@@ -18,6 +42,9 @@ insert_anchor_links = "right"
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
- Added security symbol column to fills window
|
||||
- Re-organized ladder settings dialog to make it clearer and easier to use
|
||||
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
@@ -349,7 +376,7 @@ insert_anchor_links = "right"
|
||||
- This is configurable from a ladder's settings as well
|
||||
### Customized settings in all windows
|
||||
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows␍
|
||||
|
||||
## 1.3.1 Through 1.3.4
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
+++
|
||||
title = "System Requirements"
|
||||
weight = 4
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
|
||||
### Minimum Workstation
|
||||
* New XPS Tower
|
||||
* 8th Generation Intel® Core™ i7-8700 6-Core Processor (12M Cache, up to 4.6 GHz)
|
||||
* 16GB, 2666MHz, DDR4 up to 64GB
|
||||
* Windows 10 Professional 64-bit
|
||||
* NVIDIA® GeForce® GTX 1070 with 8GB GDDR5 Graphics Memory
|
||||
* 1 x 2.5" 256GB SATA Solid State Drive
|
||||
|
||||
### Recommended Workstation
|
||||
* Similar or better than:
|
||||
* Dell Precision Tower 5820
|
||||
* Intel Xeon W-2145 3.7GHz, 4.5GHz Turbo, 8C, 11M Cache, HT, (140W) DDR4-2666
|
||||
* Windows 10 Professional 64-bit
|
||||
* NVIDIA® Quadro® P1000, 4GB, 4 mDP
|
||||
* 16GB (4x4GB) 2133MHz DDR4 RDIMM ECC
|
||||
* 2 x 2.5" 256GB SATA Solid State Drive running RAID1
|
||||
|
||||
|
||||
### BTS Recommended Server Specifications
|
||||
* Dell R640
|
||||
* 2x Intel Xeon Gold 6148 2.40GHz 20-Core Processors. For better performance BTS recommends Intel® Xeon® Platinum 8268 2.9G, 24 Core CPU
|
||||
* 192GB RAM 2666MHz DDR4
|
||||
* 3x 480GB SSD RAID 5 HBA Minicard
|
||||
* iDRAC 9 Enterprise
|
||||
* Embedded Broadcom NIC
|
||||
* Solarflare SFN8522-R2
|
||||
* Redundant Power Supply
|
||||
|
||||
### BTS Minimum Server Specifications
|
||||
* Dell R620
|
||||
* Chassis with up to 8, 2.5” Hard Drives, up to 2 PCIe Slots (With Optional Riser)
|
||||
* 2 x Intel® E5-2670 (16 total cores/ 32 total threads)
|
||||
* 64GB total memory
|
||||
* RAID 1 for H330/H730/H730P
|
||||
* PERC H730 RAID Controller, 1GB NV Cache
|
||||
* 3 x 300GB 10K RPM SATA 6Gbps 2.5in Hot-plug Hard Drive (RAID 5)
|
||||
* iDRAC8 Enterprise, integrated Dell Remote Access Controller, Enterprise (varies by hosting company)
|
||||
* Solarflare Ultra SFN7122F Dual-Port 10GbE (10 GB connectivity requirements, copper vs optical, varies by hosting company).
|
||||
* Dual, Hot-plug, Redundant Power Supply (1+1), 750W
|
||||
* Performance BIOS Setting
|
||||
* ReadyRails Sliding Rails Without Cable Management Arm (varies by hosting company)
|
||||
|
||||
### Edge Tablet Requirements
|
||||
* Similar or better than:
|
||||
* Microsoft Surface Pro 4 Or Motion R12
|
||||
* Intel i7
|
||||
* 8 GB DDR4 RAM
|
||||
@@ -1,7 +1,6 @@
|
||||
#/bin/bash
|
||||
#python -m mkdocs build -d site
|
||||
|
||||
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
|
||||
./run_zola.sh build
|
||||
|
||||
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
|
||||
@@ -14,7 +13,3 @@ sudo mv ./public/* /var/www/documentation/
|
||||
EOF
|
||||
|
||||
rm -r public
|
||||
cd ./content/bts_spark_sdk/server/
|
||||
find . -not -name '_index.md' -delete
|
||||
cd ../..
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
|
||||
namespace bts.shell {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
@@ -48,14 +49,14 @@ namespace bts.shell {
|
||||
void _show_order_ticket(SecurityMD smd) =>
|
||||
_show_order_window(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd,
|
||||
Observable.Return(_model.factory.default_ladder_settings(smd.security)))
|
||||
Observable.Return(LadderSettings.Default))
|
||||
});
|
||||
|
||||
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
|
||||
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
|
||||
_show_order_window(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
|
||||
Observable.Return(_model.factory.default_ladder_settings(smd.security).calculator_buttons))
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ namespace bts.shell {
|
||||
_show_order_window(new OrderModificationDialog {
|
||||
DataContext = new ExistingOrderTicketVM(_model,
|
||||
Observable.Return((md,ori.order)),
|
||||
Observable.Return(_model.factory.default_ladder_settings(ori.sec.Value).calculator_buttons)
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace mbts.ui {
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
|
||||
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
@@ -147,6 +148,7 @@ namespace mbts.ui {
|
||||
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
|
||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
|
||||
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
|
||||
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
|
||||
</sys:String>
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace mbts.spark {
|
||||
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(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -156,7 +156,7 @@ namespace mbts.spark {
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -49,13 +49,8 @@ namespace mbts.spark {
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new TimeAndSalesColumn(
|
||||
col_key
|
||||
, (r) => _get_aggressor_brush(r)
|
||||
, filter
|
||||
, filter_template
|
||||
)
|
||||
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
|
||||
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace mbts.spark {
|
||||
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(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
s => s.time_and_sales_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -82,7 +82,7 @@ namespace mbts.spark {
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace mbts.spark {
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -87,7 +87,7 @@ namespace mbts.spark {
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
[ProtoContract]
|
||||
@@ -14,6 +16,11 @@ namespace mbts.spark {
|
||||
[ProtoMember(3)]
|
||||
readonly uint? _persisted_key;
|
||||
|
||||
public static readonly AlgoID[] exchange_algo_keys = utilities.OrderDataHelper.outright_order_types
|
||||
.Concat(new CustomOrderDefinition.CustomOneofCase[] { CustomOrderDefinition.CustomOneofCase.Spreader })
|
||||
.Select(cooc => new AlgoID(cooc))
|
||||
.ToArray();
|
||||
|
||||
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
|
||||
_factory_key = factory_key;
|
||||
_dynamic_key = null;
|
||||
@@ -32,26 +39,34 @@ namespace mbts.spark {
|
||||
_persisted_key = persisted_key;
|
||||
}
|
||||
|
||||
public static AlgoID from_order_def(OrderDefinitionImmutable order_def) {
|
||||
switch (order_def.CustomDef.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(order_def.CustomDef.CustomCase);
|
||||
public static AlgoID from_custom_def(CustomOrderDefinitionImmutable custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(custom_def.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(custom_def.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(custom_def.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
public static AlgoID from_algo_key(AlgoKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (key.factory.structure.structure.Count > 0)
|
||||
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
|
||||
break;
|
||||
return AlgoStructureSerializer.to_algo_id(key.factory.structure, dynamic_type_index);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
public static AlgoKey to_algo_key(AlgoID id, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (id._factory_key.HasValue)
|
||||
return new AlgoKey(id._factory_key.Value);
|
||||
if (id._persisted_key.HasValue)
|
||||
return new AlgoKey(new AlgoPersistedKey(id._persisted_key.Value));
|
||||
if (id._dynamic_key != null)
|
||||
return new AlgoKey(new AlgoNameStructureKey(id._dynamic_key, AlgoStructureSerializer.structure_key(dynamic_type_index.to_int(id._dynamic_key))));
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoID other) =>
|
||||
_factory_key == other._factory_key &&
|
||||
_dynamic_key == other._dynamic_key &&
|
||||
|
||||
@@ -73,6 +73,9 @@ namespace mbts.spark {
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
|
||||
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static AlgoStructureKey structure_key(int dynamic_type) =>
|
||||
new AlgoStructureKey(new[] { dynamic_type | _dynamic_mask });
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
@@ -103,6 +106,13 @@ namespace mbts.spark {
|
||||
return mdef;
|
||||
}
|
||||
|
||||
public static AlgoID to_algo_id(AlgoStructureKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (key.structure.Count == 0)
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
int i = key.structure[0];
|
||||
return BitHelper.remove_flags(ref i, _dynamic_mask) ? new AlgoID(dynamic_type_index.from_int(i)) : new AlgoID((CustomOrderDefinition.CustomOneofCase)i);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
|
||||
@@ -61,6 +61,14 @@ namespace mbts.spark {
|
||||
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
|
||||
comparer: Comparer<SecurityImmutable>.Default
|
||||
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
|
||||
// symbol
|
||||
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
|
||||
DiscreteValuesFilterVM.Create<FillTuple, string>(
|
||||
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ft => ft.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
DefaultColumnWidths.Symbol);
|
||||
// order type
|
||||
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
|
||||
add_col_expl(Columns.OrderType,
|
||||
@@ -190,6 +198,7 @@ namespace mbts.spark {
|
||||
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
|
||||
case Columns.Side: return wrap_default(ft => ft.fill.Side);
|
||||
case Columns.Size: return wrap(ft => ft.fill.LastShares);
|
||||
case Columns.Symbol: return wrap(ft => ft.security?.Symbol ?? string.Empty);
|
||||
case Columns.TimeInForce: return wrap_default(ft => ft.order?.time_in_force() ?? TimeInForce.Undefined);
|
||||
case Columns.User: return wrap(ft => ft.order?.user() ?? string.Empty);
|
||||
default: goto case Columns.FillTime;
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:api="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:bts="clr-namespace:mbts.ui.controls"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -15,6 +19,10 @@
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<cv:ValueMap x:Key="SideToBrushConverter" Default="{u:Brush Transparent}">
|
||||
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{x:Static semi_transparent_bid_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
|
||||
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{x:Static semi_transparent_ask_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
|
||||
</cv:ValueMap>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
@@ -41,10 +49,10 @@
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView/>
|
||||
</DataTemplate>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView Background="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
|
||||
@@ -7,7 +7,23 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for FillsView.xaml
|
||||
/// </summary>
|
||||
partial class FillsView : UserControl {
|
||||
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey }
|
||||
public enum Columns {
|
||||
FillTime,
|
||||
User,
|
||||
Account,
|
||||
Exchange,
|
||||
Security,
|
||||
Symbol,
|
||||
TimeInForce,
|
||||
OrderType,
|
||||
Side,
|
||||
Price,
|
||||
Size,
|
||||
FillKind,
|
||||
FillKey,
|
||||
OrderKey,
|
||||
SecurityKey
|
||||
}
|
||||
|
||||
public FillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace mbts.spark {
|
||||
_commands = new List<SparkCommand> {
|
||||
// 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(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -72,7 +72,7 @@ namespace mbts.spark {
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -66,6 +66,10 @@ namespace bts.shell.fills {
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
|
||||
[ProtoMember(15)]
|
||||
[DIYGridColumnID(nameof(Columns.Symbol))]
|
||||
public string[] symbol { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -86,6 +90,7 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace bts.shell.fills {
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
@@ -62,6 +63,7 @@ namespace bts.shell.fills {
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
|
||||
@@ -286,8 +286,8 @@ namespace mbts.spark {
|
||||
volume_column = VolumeColumnSettings.Default,
|
||||
calculator_buttons = new[] { 1, 5, 10, 25, 50, 100 },
|
||||
scroll_bar_visible = false,
|
||||
default_order_type = CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
default_time_in_force = TimeInForce.Day,
|
||||
default_algo = new AlgoID(CustomOrderDefinition.CustomOneofCase.Limit),
|
||||
animate_recenter = true,
|
||||
badge_style = BadgeStyle.Normal,
|
||||
bid_break_line = new LadderMarketBreakLine { color = 0, show = true },
|
||||
@@ -327,7 +327,15 @@ namespace mbts.spark {
|
||||
|
||||
public enum BadgeStyle { Normal, Alternate }
|
||||
|
||||
public enum NetChangeTerms { None, Dollars, Ticks }
|
||||
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
|
||||
public enum NetChangeTerms {
|
||||
[Description("Standard Ticks")]
|
||||
None,
|
||||
[Description("Net Change Dollars")]
|
||||
Dollars,
|
||||
[Description("Net Change Ticks")]
|
||||
Ticks
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public double row_height { get; set; }
|
||||
@@ -354,6 +362,10 @@ namespace mbts.spark {
|
||||
public bool scroll_bar_visible { get; set; }
|
||||
// USED [ProtoMember(11)]
|
||||
// USED [ProtoMember(12)]
|
||||
[ProtoMember(51)]
|
||||
public uint default_account { get; set; }
|
||||
[ProtoMember(52)]
|
||||
public AlgoID default_algo { get; set; }
|
||||
[ProtoMember(13)]
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type { get; set; }
|
||||
[ProtoMember(14)]
|
||||
@@ -364,6 +376,8 @@ namespace mbts.spark {
|
||||
public bool animate_recenter { get; set; } = true;
|
||||
[ProtoMember(17)]
|
||||
public BadgeStyle badge_style { get; set; }
|
||||
[ProtoMember(53)]
|
||||
public bool auto_clear { get; set; }
|
||||
|
||||
[ProtoMember(18)]
|
||||
public LadderMarketBreakLine bid_break_line { get; set; }
|
||||
@@ -459,11 +473,13 @@ namespace mbts.spark {
|
||||
piq_column.safe_equals(other.piq_column) &&
|
||||
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
|
||||
scroll_bar_visible == other.scroll_bar_visible &&
|
||||
default_order_type == other.default_order_type &&
|
||||
default_account == other.default_account &&
|
||||
default_algo == other.default_algo &&
|
||||
default_time_in_force == other.default_time_in_force &&
|
||||
size == other.size &&
|
||||
animate_recenter == other.animate_recenter &&
|
||||
badge_style == other.badge_style &&
|
||||
auto_clear == other.auto_clear &&
|
||||
bid_break_line.safe_equals(other.bid_break_line) &&
|
||||
ask_break_line.safe_equals(other.ask_break_line) &&
|
||||
mid_break_line.safe_equals(other.mid_break_line) &&
|
||||
@@ -512,7 +528,6 @@ namespace mbts.spark {
|
||||
EqualityComparer.safe_get_hash_code(piq_column),
|
||||
EqualityComparer.safe_get_hash_code(calculator_buttons),
|
||||
scroll_bar_visible.GetHashCode(),
|
||||
default_order_type.GetHashCode(),
|
||||
default_time_in_force.GetHashCode(),
|
||||
size.GetHashCode(),
|
||||
animate_recenter.GetHashCode(),
|
||||
|
||||
@@ -4,13 +4,16 @@ using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -52,37 +55,175 @@ namespace bts.shell {
|
||||
public async override Task<IShellWindow> load(LadderSaveState state) {
|
||||
await Wait.until_true(() => _securities.ContainsKey(state.security_key), _ladder_conf.security_timeout);
|
||||
if (_securities.TryGetValue(state.security_key, out SecurityMD security)) {
|
||||
return create_ladder_window(security, state.settings);
|
||||
return create_ladder_window(security, state.settings, state.settings_id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static LadderSaveState _get_state(LadderVM vm) =>
|
||||
new LadderSaveState { security_key = vm.security.Id, settings = vm.get_save_state() };
|
||||
LadderSaveState _get_state(LadderVM vm, uint id) {
|
||||
LadderSettings s = vm.get_save_state();
|
||||
if (id != 0) {
|
||||
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
|
||||
// profile was deleted
|
||||
id = 0;
|
||||
} else {
|
||||
var curr_profile = _fit_to_sec(result.value, vm.security.ContractCase);
|
||||
if (!s.Equals(curr_profile)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
|
||||
}
|
||||
|
||||
int _ladder_count = 0;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
|
||||
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
// resources
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
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 profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
|
||||
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, sanitize_ladder_settings(lp.value), lp.security_criteria))),
|
||||
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
|
||||
LadderSettings last_value = fit_to_sec(state_settings);
|
||||
var settings_subject = new BehaviorSubject<LadderSettings>(last_value);
|
||||
// vm
|
||||
var vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_subject.Where(s => s != null));
|
||||
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;
|
||||
vm.request_settings_dialog += open_settings_dialog;
|
||||
bool initialized = false;
|
||||
var settings_subscription = _settings_hub.subscribe_default_settings().Subscribe(on_user_settings);
|
||||
vm.OnDisposed += disposing => {
|
||||
Disposable.dispose(ref active_settings_dialog);
|
||||
Disposable.dispose(ref settings_subject);
|
||||
};
|
||||
// view
|
||||
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;
|
||||
return lv;
|
||||
// local methods
|
||||
LadderSettings fit_to_sec(LadderSettings s) =>
|
||||
_fit_to_sec(s, md.security.ContractCase);
|
||||
|
||||
void on_user_settings(UserSettings us) {
|
||||
if (initialized) {
|
||||
if (settings_id == 0) {
|
||||
// not on a profile, don't respond to default settings changes
|
||||
return;
|
||||
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
if (!last_value.Equals(vm.get_save_state())) {
|
||||
settings_id = 0;
|
||||
} else {
|
||||
settings_subject.OnNext(last_value = fit_to_sec(profile.value));
|
||||
}
|
||||
} else {
|
||||
// profile was deleted; keep the current settings and switch to the 'Local' profile.
|
||||
settings_id = 0;
|
||||
}
|
||||
} else if (_settings_hub.initialized) {
|
||||
initialized = true;
|
||||
if (settings_id != 0) {
|
||||
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
last_value = fit_to_sec(profile.value);
|
||||
} else {
|
||||
settings_id = 0;
|
||||
}
|
||||
}
|
||||
if (settings_id == 0 && last_value == null) {
|
||||
foreach (var lp in _settings_hub.current.ladder_profiles) {
|
||||
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
|
||||
// In the meantime, simply use the first match.
|
||||
if (lp.security_criteria.includes(md.security)) {
|
||||
last_value = fit_to_sec(lp.value);
|
||||
settings_id = lp.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_value == null)
|
||||
last_value = fit_to_sec(_settings_hub.current.ladder);
|
||||
}
|
||||
settings_subject.OnNext(last_value);
|
||||
}
|
||||
}
|
||||
|
||||
void open_settings_dialog() {
|
||||
// dialog vm
|
||||
var dialog_vm = new ProfiledShellSettingsDialogVM<LadderSettings, LadderSettingsVM, LadderSettingsProfile>(
|
||||
profile_service,
|
||||
us => us.ladder ?? LadderSettings.Default,
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
|
||||
svm.load(s);
|
||||
return svm;
|
||||
},
|
||||
fit_to_sec,
|
||||
vm.get_save_state(),
|
||||
UserSettings.FileExtensions.LADDER,
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
var dialog = new ShellSettingsDialog {
|
||||
DataContext = dialog_vm,
|
||||
Title = $"Ladder Settings - '{vm.security}'"
|
||||
};
|
||||
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
dialog.Closed += on_closed;
|
||||
Disposable.replace(ref active_settings_dialog, Disposable.Create(destroy_all));
|
||||
dialog.move_to_mouse(-500, -500);
|
||||
dialog.Show();
|
||||
// local methods
|
||||
void apply(SettingsProfile<LadderSettings> profile) {
|
||||
settings_id = profile.id;
|
||||
last_value = profile.value;
|
||||
settings_subject.OnNext(profile.value);
|
||||
}
|
||||
// idempotent
|
||||
void destroy_all() {
|
||||
destroy_window();
|
||||
destroy_vm();
|
||||
}
|
||||
// idempotent
|
||||
void destroy_window() {
|
||||
if (dialog == null) return;
|
||||
try {
|
||||
var temp = dialog;
|
||||
dialog = null;
|
||||
temp.Closed -= on_closed;
|
||||
temp.Close();
|
||||
} catch (Exception ex) {
|
||||
on_error("closing window", ex);
|
||||
}
|
||||
}
|
||||
// idempotent
|
||||
void destroy_vm() {
|
||||
if (dialog_vm == null) return;
|
||||
try {
|
||||
var temp = dialog_vm;
|
||||
dialog_vm = null;
|
||||
temp.applied -= apply;
|
||||
temp.RequestClose -= on_request_close;
|
||||
temp.Dispose();
|
||||
} catch (Exception ex) {
|
||||
on_error("disposing vm", ex);
|
||||
}
|
||||
}
|
||||
void on_closed(object sender, EventArgs e) => destroy_all();
|
||||
void on_request_close(object sender, EventArgs e) => destroy_all();
|
||||
void on_error(string action_desc, Exception error) =>
|
||||
Log.Ui.warn($"LadderSettingsDialog error {action_desc}: {error}.");
|
||||
}
|
||||
});
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{id:00}" : "";
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
|
||||
return ShellWindow.create(
|
||||
module, md.security.ToString(), _window_type, view,
|
||||
(v,s) => save(_get_state(((LadderView)v).vm), s),
|
||||
(v,s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: thread);
|
||||
// if there are new props, make sure they are not null, invalid, etc.
|
||||
@@ -125,26 +266,19 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: whenever we re-implement the linked ladder, need to add some field to the LadderSaveState to indicate the owning window
|
||||
//void show_linked(IObservable<SecurityMD> keystream) {
|
||||
// var view = new LadderView();
|
||||
// IDisposable sub = null;
|
||||
// var window =
|
||||
// ShellWindow.create(module, "loading", view,
|
||||
// Disposable.Create(() => {
|
||||
// (view.DataContext as LadderVM)?.Dispose();
|
||||
// sub?.Dispose();
|
||||
// }), s => {
|
||||
// var vm = view.DataContext as LadderVM;
|
||||
// if (vm != null) save(_get_state(vm), s);
|
||||
// }, Icons.Ladder);
|
||||
// sub =
|
||||
// keystream
|
||||
// .Select(smd => new LadderVM(_bus, new LadderModel(smd, _orders_model),
|
||||
// Observable.Return(_orders_model.factory.default_ladder_settings(smd.security)))
|
||||
// ).Aggregate((old, vm) => { old.Dispose(); return vm; })
|
||||
// .Subscribe(vm => { view.DataContext = vm; window.Title = vm.security.ToString(); });
|
||||
//}
|
||||
LadderSettings _fit_to_sec(LadderSettings s, Security.ContractOneofCase sec_type) {
|
||||
if (s == null) return null;
|
||||
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
|
||||
if (algo_key.mode == AlgoKey.Mode.Factory) {
|
||||
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
|
||||
if (fitted_algo.HasValue) {
|
||||
// be sure to clone ('s' may be shared)
|
||||
s = SparkUtils.clone_proto(s);
|
||||
s.default_algo = AlgoID.from_custom_def(fitted_algo.Value);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
readonly struct LadderConfig {
|
||||
public readonly uint max_open_ladders;
|
||||
@@ -167,5 +301,7 @@ namespace bts.shell {
|
||||
public UInt32 security_key;
|
||||
[ProtoMember(2)]
|
||||
public mbts.spark.LadderSettings settings;
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
@@ -297,7 +298,8 @@ namespace mbts.spark {
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_owner.security),
|
||||
price = price_filter,
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
order_types = AlgoID.exchange_algo_keys
|
||||
},
|
||||
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
|
||||
use_defaults: false
|
||||
@@ -335,6 +337,7 @@ namespace mbts.spark {
|
||||
}
|
||||
obvm.columns.Insert(0, new OrderBookVM.UpdateColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CancelColumn(nameof(OrderBookRowVM.cancel_command)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
|
||||
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -8,10 +10,14 @@ using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
@@ -19,19 +25,30 @@ using System.Windows.Media;
|
||||
using TextUtils = mbts.ui.utility.text.TextUtils;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderSettingsVM : ViewModelBase, IWindowSettingsVM<LadderSettings>, ITemplateSelectorKey, IDisposable {
|
||||
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
|
||||
readonly Property<LadderSettings.BadgeStyle> _badge_style;
|
||||
readonly Property<uint> _alternate_order_size;
|
||||
readonly DelegateProperty<double> _row_height;
|
||||
Func<double> _get_order_entry_panel_width, _get_zoom_factor;
|
||||
readonly UserProperty<AccountTuple?> _default_account;
|
||||
readonly UserProperty<AlgoDefinitionVM> _default_algo;
|
||||
readonly DeferrableActionWrapper _update;
|
||||
readonly ObservableKeyedCollection<AccountTuple?, uint> _accounts = new ObservableKeyedCollection<AccountTuple?, uint>(AccountTuple.nullable_key_fn);
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly OrdersModel _model;
|
||||
uint? _desired_account;
|
||||
AlgoID? _desired_algo;
|
||||
TimeInForce _default_time_in_force;
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
_bid = new LadderColumnSettingsVM("Bid"),
|
||||
_price = new LadderColumnSettingsVM("Price"),
|
||||
_ask = new LadderColumnSettingsVM("Ask"),
|
||||
_last = new LadderColumnSettingsVM("Last", true),
|
||||
_piq = new LadderColumnSettingsVM("PIQ", true);
|
||||
_last = new LadderColumnSettingsVM("Last", LadderSettingsView.LastColumnTemplateKey, true),
|
||||
_piq = new LadderColumnSettingsVM("PIQ", can_toggle_show: true);
|
||||
VolumeColumnVM _volume = new VolumeColumnVM();
|
||||
LadderMarketBreakLineVM
|
||||
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
|
||||
@@ -59,25 +76,18 @@ namespace mbts.spark {
|
||||
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
|
||||
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
|
||||
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
|
||||
readonly Property<bool>
|
||||
_show_in_net_change,
|
||||
_show_in_net_change_dollars,
|
||||
_show_in_net_change_ticks;
|
||||
|
||||
public LadderSettingsVM(OrdersModel model, bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
|
||||
this.is_synthetic = is_synthetic;
|
||||
favorite_algos_vm = new FavoriteAlgosVM(model);
|
||||
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
|
||||
_model = model;
|
||||
is_synthetic = security.ContractCase == Security.ContractOneofCase.Synthetic;
|
||||
favorite_algos_vm = new FavoriteAlgosVM(_algo_definitions);
|
||||
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
|
||||
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
|
||||
_badge_style = backing<LadderSettings.BadgeStyle>(nameof(badge_style));
|
||||
_badge_style.Changed += _badge_style_Changed;
|
||||
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
|
||||
_get_order_entry_panel_width = get_left_panel_width;
|
||||
_get_zoom_factor = get_zoom_factor;
|
||||
_show_in_net_change = backing<bool>(nameof(show_in_net_change));
|
||||
_show_in_net_change_dollars = backing<bool>(nameof(show_in_net_change_dollars));
|
||||
_show_in_net_change_ticks = backing<bool>(nameof(show_in_net_change_ticks));
|
||||
|
||||
_default_account = user_backing<AccountTuple?>(nameof(default_account));
|
||||
_default_algo = user_backing<AlgoDefinitionVM>(nameof(default_algo));
|
||||
// be sure to initialize backings before this measure_row_height call
|
||||
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
|
||||
_orders.font_size_changed += on_font_size_changed;
|
||||
@@ -86,10 +96,47 @@ namespace mbts.spark {
|
||||
_ask.font_size_changed += on_font_size_changed;
|
||||
_last.font_size_changed += on_font_size_changed;
|
||||
_piq.font_size_changed += on_font_size_changed;
|
||||
|
||||
// accounts
|
||||
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
|
||||
CustomSort = Comparer<AccountTuple>.Default.to_nullable().to_non_generic()
|
||||
};
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
|
||||
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
|
||||
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
|
||||
_try_select_desired_account();
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, connect_favorite_algo),
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(update => {
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, algo_factory);
|
||||
_try_select_desired_algo();
|
||||
})
|
||||
) { dispose_resources };
|
||||
// local methods
|
||||
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) => _update.execute();
|
||||
void on_favorite_algo_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => _update.execute();
|
||||
IDisposable connect_favorite_algo(FavoriteAlgoItem item) {
|
||||
item.PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
void dispose_resources() {
|
||||
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
|
||||
favorite_algos_vm.Dispose();
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged -= on_prop_changed;
|
||||
}
|
||||
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, false);
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
private void _badge_style_Changed(LadderSettings.BadgeStyle oldval, LadderSettings.BadgeStyle newval) {
|
||||
if (newval == LadderSettings.BadgeStyle.Alternate && _orders.font_size == 10) {
|
||||
_orders.font_size = 7;
|
||||
@@ -110,6 +157,23 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
// price format
|
||||
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
|
||||
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
|
||||
// time in force
|
||||
public TimeInForce default_time_in_force { get => _default_time_in_force; set => Set(ref _default_time_in_force, value); }
|
||||
public IEnumerable<TimeInForce> time_in_force_options => EnumHelper.get_values_except(TimeInForce.Undefined);
|
||||
// account
|
||||
public AccountTuple? default_account { get => _default_account.Value; set => _default_account.Value = value; }
|
||||
public ICollectionView<AccountTuple?> accounts_view { get; }
|
||||
// algo
|
||||
public AlgoDefinitionVM default_algo { get => _default_algo.Value; set => _default_algo.Value = value; }
|
||||
public IReadOnlyObservableCollection<AlgoDefinitionVM> algo_definitions => _algo_definitions;
|
||||
// left-panel (components)
|
||||
public bool auto_clear { get => _auto_clear; set => Set(ref _auto_clear, value); }
|
||||
public double order_entry_panel_width { get => _order_entry_panel_width; set => Set(ref _order_entry_panel_width, value); }
|
||||
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
|
||||
// favorites
|
||||
public FavoriteAlgosVM favorite_algos_vm { get; }
|
||||
|
||||
public double row_height => _row_height.Value;
|
||||
@@ -175,79 +239,108 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public bool show_in_net_change {
|
||||
get => _show_in_net_change.Value;
|
||||
set {
|
||||
// when unchecking, clear out dollars and ticks
|
||||
if (!value) {
|
||||
show_in_net_change_dollars = false;
|
||||
show_in_net_change_ticks = false;
|
||||
}
|
||||
// when checking, by defalt check dollars
|
||||
if (value) {
|
||||
show_in_net_change_dollars = true;
|
||||
}
|
||||
_show_in_net_change.Value = value;
|
||||
}
|
||||
}
|
||||
public bool show_in_net_change_dollars {
|
||||
get => _show_in_net_change_dollars.Value;
|
||||
set => _show_in_net_change_dollars.Value = value;
|
||||
}
|
||||
|
||||
public bool show_in_net_change_ticks {
|
||||
get => _show_in_net_change_ticks.Value;
|
||||
set => _show_in_net_change_ticks.Value = value;
|
||||
}
|
||||
|
||||
|
||||
public string key => GridSettingsTemplateKeys.Ladder;
|
||||
|
||||
IEnumerable<INotifyPropertyChanged> _observable_components {
|
||||
get {
|
||||
// columns
|
||||
yield return _orders;
|
||||
yield return _bid;
|
||||
yield return _price;
|
||||
yield return _ask;
|
||||
yield return _last;
|
||||
yield return _piq;
|
||||
yield return _volume;
|
||||
yield return _volume.general;
|
||||
// market break lines
|
||||
yield return _bid_mkt_line;
|
||||
yield return _ask_mkt_line;
|
||||
yield return _mid_mkt_line;
|
||||
yield return _session_low_mkt_line;
|
||||
yield return _session_high_mkt_line;
|
||||
yield return _session_range_fill;
|
||||
// left panel
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _day_visibility;
|
||||
yield return _high_visibility;
|
||||
yield return _low_visibility;
|
||||
yield return _open_visibility;
|
||||
yield return _settle_visibility;
|
||||
yield return _volume_visibility;
|
||||
yield return _account_visibility;
|
||||
yield return _tif_visibility;
|
||||
yield return _order_type_visibility;
|
||||
yield return _account_position_visibility;
|
||||
yield return _day_position_visibility;
|
||||
yield return _size_preset_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
yield return _clear_visibility;
|
||||
yield return _cancel_buys_visibility;
|
||||
yield return _cancel_sells_visibility;
|
||||
yield return _cancel_all_visibility;
|
||||
// other
|
||||
yield return size_display;
|
||||
yield return favorite_algos_vm;
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(LadderSettings settings) {
|
||||
_current = settings;
|
||||
badge_style = settings.badge_style;
|
||||
alternate_order_size = settings.alternate_order_size;
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
_ask.assign_from(_current.ask_column);
|
||||
_price.assign_from(_current.price_column);
|
||||
_volume.assign_from(_current.volume_column);
|
||||
_last.assign_from(_current.last_column);
|
||||
_piq.assign_from(_current.piq_column);
|
||||
_bid_mkt_line.assign_from(_current.bid_break_line);
|
||||
_ask_mkt_line.assign_from(_current.ask_break_line);
|
||||
_mid_mkt_line.assign_from(_current.mid_break_line);
|
||||
_session_low_mkt_line.assign_from(_current.session_low_line);
|
||||
_session_high_mkt_line.assign_from(_current.session_high_line);
|
||||
_session_range_fill.assign_from(_current.session_range_fill);
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_day_visibility.show = settings.day_visible;
|
||||
_high_visibility.show = settings.high_visible;
|
||||
_low_visibility.show = settings.low_visible;
|
||||
_open_visibility.show = settings.open_visible;
|
||||
_settle_visibility.show = settings.settle_visible;
|
||||
_volume_visibility.show = settings.volume_visible;
|
||||
_account_visibility.show = settings.account_visible;
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_day_position_visibility.show = settings.day_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
_cancel_buys_visibility.show = settings.cancel_buys_visible;
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
// do not read the order entry panel width here from settings
|
||||
// because it could have changed from the UI
|
||||
// there might be a better way of doing this, like how
|
||||
// does the widths of the ladder grid column pull this off?
|
||||
show_in_net_change = settings.net_change != LadderSettings.NetChangeTerms.None;
|
||||
show_in_net_change_dollars = settings.net_change == LadderSettings.NetChangeTerms.Dollars;
|
||||
show_in_net_change_ticks = settings.net_change == LadderSettings.NetChangeTerms.Ticks;
|
||||
using (_update.defer_execution()) {
|
||||
_current = settings;
|
||||
badge_style = settings.badge_style;
|
||||
alternate_order_size = settings.alternate_order_size;
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
// columns
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
_ask.assign_from(_current.ask_column);
|
||||
_price.assign_from(_current.price_column);
|
||||
_volume.assign_from(_current.volume_column);
|
||||
_last.assign_from(_current.last_column);
|
||||
_piq.assign_from(_current.piq_column);
|
||||
// market break lines
|
||||
_bid_mkt_line.assign_from(_current.bid_break_line);
|
||||
_ask_mkt_line.assign_from(_current.ask_break_line);
|
||||
_mid_mkt_line.assign_from(_current.mid_break_line);
|
||||
_session_low_mkt_line.assign_from(_current.session_low_line);
|
||||
_session_high_mkt_line.assign_from(_current.session_high_line);
|
||||
_session_range_fill.assign_from(_current.session_range_fill);
|
||||
// left-panel (defaults)
|
||||
default_time_in_force = settings.default_time_in_force;
|
||||
// - account
|
||||
_desired_account = settings.default_account;
|
||||
_try_select_desired_account(true);
|
||||
// - algo
|
||||
_desired_algo = settings.default_algo;
|
||||
_try_select_desired_algo(true);
|
||||
// - components
|
||||
auto_clear = settings.auto_clear;
|
||||
order_entry_panel_width = settings.order_entry_panel_initial_width;
|
||||
zoom_factor = settings.zoom_factor;
|
||||
// left-panel (show/hide)
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_day_visibility.show = settings.day_visible;
|
||||
_high_visibility.show = settings.high_visible;
|
||||
_low_visibility.show = settings.low_visible;
|
||||
_open_visibility.show = settings.open_visible;
|
||||
_settle_visibility.show = settings.settle_visible;
|
||||
_volume_visibility.show = settings.volume_visible;
|
||||
_account_visibility.show = settings.account_visible;
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_day_position_visibility.show = settings.day_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
_cancel_buys_visibility.show = settings.cancel_buys_visible;
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
price_format = settings.net_change;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderSettings get_result() {
|
||||
@@ -257,6 +350,7 @@ namespace mbts.spark {
|
||||
clone.favorite_algos = favorite_algos_vm.create();
|
||||
clone.size_display = size_display.create();
|
||||
clone.calculator_buttons = size_presets.Select(vm => (int)vm.value).ToArray();
|
||||
// columns, row height (the latter determined by columns font size)
|
||||
clone.orders_column = _orders.get_settings();
|
||||
clone.bid_column = _bid.get_settings();
|
||||
clone.price_column = _price.get_settings();
|
||||
@@ -264,13 +358,28 @@ namespace mbts.spark {
|
||||
clone.volume_column = _volume.get_settings();
|
||||
clone.last_column = _last.get_settings();
|
||||
clone.piq_column = _piq.get_settings();
|
||||
clone.row_height = LadderRender.measure_row_height(clone);
|
||||
// market break lines
|
||||
clone.bid_break_line = _bid_mkt_line.get_settings();
|
||||
clone.ask_break_line = _ask_mkt_line.get_settings();
|
||||
clone.mid_break_line = _mid_mkt_line.get_settings();
|
||||
clone.session_low_line = _session_low_mkt_line.get_settings();
|
||||
clone.session_high_line = _session_high_mkt_line.get_settings();
|
||||
clone.session_range_fill = _session_range_fill.get_settings();
|
||||
clone.row_height = LadderRender.measure_row_height(clone);
|
||||
// left-panel (defaults)
|
||||
clone.default_time_in_force = default_time_in_force;
|
||||
// - account
|
||||
if (default_account.HasValue)
|
||||
clone.default_account = default_account.Value.id;
|
||||
// - algo
|
||||
clone.default_algo = default_algo != null ?
|
||||
AlgoID.from_algo_key(default_algo.key, _model.dynamic_helper.dynamic_type_index) :
|
||||
(_desired_algo ?? LadderSettings.Default.default_algo);
|
||||
// - components
|
||||
clone.auto_clear = auto_clear;
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.day_visible = _day_visibility.show;
|
||||
clone.high_visible = _high_visibility.show;
|
||||
clone.low_visible = _low_visibility.show;
|
||||
@@ -288,25 +397,21 @@ namespace mbts.spark {
|
||||
clone.cancel_buys_visible = _cancel_buys_visibility.show;
|
||||
clone.cancel_sells_visible = _cancel_sells_visibility.show;
|
||||
clone.cancel_all_visible = _cancel_all_visibility.show;
|
||||
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.zoom_factor = _get_zoom_factor();
|
||||
clone.net_change = _create_net_change_terms(show_in_net_change_dollars, show_in_net_change_ticks);
|
||||
clone.net_change = price_format;
|
||||
return clone;
|
||||
|
||||
LadderSettings.NetChangeTerms _create_net_change_terms(bool dollars, bool ticks) {
|
||||
if (dollars) {
|
||||
return LadderSettings.NetChangeTerms.Dollars;
|
||||
}
|
||||
if (ticks) {
|
||||
return LadderSettings.NetChangeTerms.Ticks;
|
||||
}
|
||||
return LadderSettings.NetChangeTerms.None;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Dispose() => favorite_algos_vm.Dispose();
|
||||
public IDisposable defer_updates() => _update.defer_execution();
|
||||
|
||||
IDisposable IObservable<LadderSettings>.Subscribe(IObserver<LadderSettings> observer) {
|
||||
result_changed += notify;
|
||||
return Disposable.Create(() => result_changed -= notify);
|
||||
void notify() => observer.OnNext(get_result());
|
||||
}
|
||||
|
||||
LadderSettings IValueFactory<LadderSettings>.create() => get_result();
|
||||
void IValueFactory<LadderSettings>.load_from(LadderSettings value) => load(value);
|
||||
|
||||
IMutableValueWrapperVM<uint> _create_size_preset_vm() => _create_size_preset_vm(0);
|
||||
IMutableValueWrapperVM<uint> _create_size_preset_vm(uint init) {
|
||||
@@ -314,6 +419,20 @@ namespace mbts.spark {
|
||||
vm.PropertyChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
|
||||
return vm;
|
||||
}
|
||||
|
||||
void _try_select_desired_account(bool bypass_setter = false) {
|
||||
if (_desired_account.HasValue && (!_default_account.setter_called || bypass_setter) && _accounts.TryGetValue(_desired_account.Value, out var acc_tup)) {
|
||||
_desired_account = null;
|
||||
_default_account.bypass_setter(acc_tup);
|
||||
}
|
||||
}
|
||||
|
||||
void _try_select_desired_algo(bool bypass_setter = false) {
|
||||
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value, _model.dynamic_helper.dynamic_type_index), out var vm)) {
|
||||
_desired_algo = null;
|
||||
_default_algo.bypass_setter(vm);
|
||||
}
|
||||
}
|
||||
|
||||
public class LadderColumnSettingsVM : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly Property<double> _font_size;
|
||||
@@ -323,11 +442,13 @@ namespace mbts.spark {
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
readonly DelegateProperty<string> _font_display_name;
|
||||
readonly Property<bool> _show;
|
||||
readonly bool _can_toggle_visibility;
|
||||
readonly string _template_key;
|
||||
uint _width;
|
||||
|
||||
public LadderColumnSettingsVM(string name, bool can_toggle_visibility = false) {
|
||||
public LadderColumnSettingsVM(string name, string template_key = null, bool can_toggle_show = false) {
|
||||
this.name = name;
|
||||
_can_toggle_visibility = can_toggle_visibility;
|
||||
_template_key = template_key;
|
||||
this.can_toggle_show = can_toggle_show;
|
||||
foreground_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
background_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
_show = backing(nameof(show), true);
|
||||
@@ -340,24 +461,33 @@ namespace mbts.spark {
|
||||
_font_display_name = backing(nameof(font_display_name),
|
||||
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
|
||||
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
|
||||
toggle_show_details_cmd = new DelegateCommand(() => {
|
||||
show_details = !show_details;
|
||||
RaisePropertyChanged(nameof(show_details));
|
||||
});
|
||||
}
|
||||
|
||||
public event Action<double, double> font_size_changed;
|
||||
|
||||
public string name { get; }
|
||||
public Typeface typeface { get => _typeface.Value; set => _typeface.Value = value; }
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public string font_display_name => _font_display_name.Value;
|
||||
public double font_size { get => _font_size.Value; set => _font_size.Value = value; }
|
||||
public uint width { get; set; }
|
||||
public uint width { get => _width; set => Set(ref _width, value); }
|
||||
public bool show { get => _show.Value; set => _show.Value = value; }
|
||||
public bool can_toggle_show { get; }
|
||||
public bool show_details { get; private set; }
|
||||
public Color foreground { get => _foreground.Value; set => _foreground.Value = value; }
|
||||
public Color background { get => _background.Value; set => _background.Value = value; }
|
||||
public ObservableCollection<ColorPaletteVM> foreground_palettes { get; }
|
||||
public ObservableCollection<ColorPaletteVM> background_palettes { get; }
|
||||
public event Action<double, double> font_size_changed;
|
||||
|
||||
public virtual string sample_text => "999-56789.01234";
|
||||
string ITemplateSelectorKey.key => _can_toggle_visibility ? ui.spark.LadderSettingsView.ColumnVisibilityToggleTemplateKey : null;
|
||||
|
||||
public ICommand toggle_show_details_cmd { get; }
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
public void assign_from(LadderColumnSettings settings) {
|
||||
typeface = new Typeface(
|
||||
@@ -370,7 +500,6 @@ namespace mbts.spark {
|
||||
show = settings.show;
|
||||
foreground = settings.foreground.rgb_color();
|
||||
background = settings.background.rgb_color();
|
||||
RaisePropertyChanged(nameof(width), nameof(show));
|
||||
}
|
||||
|
||||
public LadderColumnSettings get_settings() {
|
||||
@@ -395,7 +524,7 @@ namespace mbts.spark {
|
||||
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
|
||||
public Brush badge_background { get; } = Brushes.White;
|
||||
|
||||
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnSampleTemplateKey;
|
||||
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnTemplateKey;
|
||||
}
|
||||
|
||||
public class LadderMarketBreakLineVM : ViewModelBase {
|
||||
@@ -463,7 +592,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", true);
|
||||
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", LadderSettingsView.VolumeColumnTemplateKey, true);
|
||||
public VolumeAtPriceAnimationVM buy { get; }
|
||||
public VolumeAtPriceAnimationVM sell { get; }
|
||||
public VolumeAtPriceAnimationVM passive { get; }
|
||||
@@ -566,17 +695,15 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FavoriteAlgosVM : DisposableViewModelBase, IValueFactoryVM<LadderFavoriteAlgosSettings> {
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
|
||||
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
|
||||
int _column_count = 2;
|
||||
|
||||
public FavoriteAlgosVM(OrdersModel model) {
|
||||
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
|
||||
_algo_definitions = algo_definitions;
|
||||
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
|
||||
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
|
||||
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
|
||||
_dispose = model.algo_model.definitions.OnDispatcher().Subscribe(update => {
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, d => AlgoDefinitionVM.create(model, d, false));
|
||||
});
|
||||
}
|
||||
|
||||
public ObservableCollection<FavoriteAlgoItem> items { get; }
|
||||
@@ -596,11 +723,17 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public LadderFavoriteAlgosSettings create() =>
|
||||
new LadderFavoriteAlgosSettings(_raw_items.Where(fai => fai.algo_definition != null).Select(fai => fai.algo_definition.key).ToArray(), column_count);
|
||||
new LadderFavoriteAlgosSettings(_raw_items
|
||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||
.Select(k => k.Value)
|
||||
.ToArray(),
|
||||
column_count);
|
||||
|
||||
FavoriteAlgoItem _item_factory() =>
|
||||
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic(),
|
||||
Filter = obj => obj is AlgoDefinitionVM vm && vm.key.is_persisted
|
||||
});
|
||||
}
|
||||
|
||||
@@ -611,7 +744,7 @@ namespace mbts.spark {
|
||||
|
||||
protected FavoriteAlgoItem() { }
|
||||
|
||||
public FavoriteAlgoItem(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
|
||||
public FavoriteAlgoItem(IObservableCollection<AlgoDefinitionVM> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
|
||||
selector = new KeyedItemSelector<AlgoDefinitionVM, AlgoKey>(this, nameof(algo_definition), algo_definitions, d => d.key);
|
||||
this.algo_definitions_view = algo_definitions_view;
|
||||
}
|
||||
@@ -624,4 +757,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -36,32 +34,29 @@ namespace mbts.spark {
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
double _position, _day_position;
|
||||
bool _show_account_position, _show_day_position;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
LadderSettings _current_settings;
|
||||
|
||||
public static LadderVM create(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
|
||||
var non_shared_wsm = settings_manager.as_non_shared();
|
||||
return new LadderVM(bus, model, settings_manager, non_shared_wsm, init, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
}
|
||||
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model) {
|
||||
var default_settings = LadderSettings.Default;
|
||||
return create_readonly(bus, model, new WindowSettingsManager<LadderSettings>.DisconnectedImpl(default_settings), default_settings);
|
||||
}
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, IObservable<LadderSettings> settings) =>
|
||||
create_readonly(bus, model, LadderSettings.Default, settings);
|
||||
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) =>
|
||||
new LadderVM(bus, model, settings_manager, settings_manager.as_non_shared(), init);
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings);
|
||||
|
||||
LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, IWindowSettingsManagerNonShared<LadderSettings> non_shared_wsm, LadderSettings init, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_current_settings = init;
|
||||
this.settings = settings;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
_settings_manager = non_shared_wsm;
|
||||
this.order_properties_vm = order_properties_vm;
|
||||
_high = backing(nameof(high), double.NaN);
|
||||
_low = backing(nameof(low), double.NaN);
|
||||
@@ -81,12 +76,14 @@ namespace mbts.spark {
|
||||
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
|
||||
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), false);
|
||||
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
|
||||
_status = backing(nameof(status_message), "");
|
||||
_status_brush = backing(nameof(status_background), _transparent_status_brush);
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
/// first things first: assign our <see cref="_current_settings"/>
|
||||
(s => _current_settings = s),
|
||||
// brush props
|
||||
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
|
||||
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
|
||||
@@ -113,6 +110,19 @@ namespace mbts.spark {
|
||||
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}
|
||||
size_calculator.buttons = s.calculator_buttons;
|
||||
_auto_clear.set(s.auto_clear);
|
||||
// column widths
|
||||
_orders_column_width.set(s.orders_column.width);
|
||||
_bid_column_width.set(s.bid_column.width);
|
||||
_price_column_width.set(s.price_column.width);
|
||||
_ask_column_width.set(s.ask_column.width);
|
||||
_last_column_width.set(s.last_column.width);
|
||||
_piq_column_width.set(s.piq_column.width);
|
||||
_volume_column_width.set(s.volume_column.general.width);
|
||||
// left panel/zoom
|
||||
_left_panel_width.set(s.order_entry_panel_initial_width);
|
||||
_zoom_factor.set(s.zoom_factor);
|
||||
}
|
||||
};
|
||||
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
@@ -128,30 +138,13 @@ namespace mbts.spark {
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
show_book_command = new DelegateCommand(_show_book);
|
||||
show_ticket_command = new DelegateCommand(_show_ticket);
|
||||
settings_command = new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<LadderSettings, LadderSettingsVM>(
|
||||
settings_manager, s => s.ladder, UserSettings.FileExtensions.LADDER,
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(model.order_gateway, security.ContractCase == Security.ContractOneofCase.Synthetic, () => left_panel_width, () => zoom_factor);
|
||||
svm.load(s);
|
||||
return svm;
|
||||
}, s => s.with_window_specifics(get_save_state().get_window_specifics()));
|
||||
var dialog = new ui.spark.SimpleGridSettingsDialog {
|
||||
DataContext = vm,
|
||||
Title = $"Ladder Settings - '{security}'"
|
||||
};
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}, -500, -500);
|
||||
settings_command = new DelegateCommand(() => request_settings_dialog?.Invoke());
|
||||
|
||||
orders = new ObservableCollection<LadderOrderVM>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(settings_observer),
|
||||
settings_command,
|
||||
non_shared_wsm,
|
||||
settings.OnDispatcher().Subscribe(settings_observer),
|
||||
get_order_properties_editor_hooks(),
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
// we need to hook this up to model.orders which is filtered for just the security
|
||||
model.orders.OnDispatcher().Subscribe(working => {
|
||||
@@ -176,7 +169,7 @@ namespace mbts.spark {
|
||||
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
||||
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
||||
} else {
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, settings.Select(s => s.badge_style).OnDispatcher());
|
||||
_slot_price_vs_orders[slot_price_target] = slot_vm;
|
||||
}
|
||||
|
||||
@@ -250,7 +243,7 @@ namespace mbts.spark {
|
||||
|
||||
public IBus bus => _bus;
|
||||
|
||||
public event Action on_tick;
|
||||
public event Action on_tick, request_settings_dialog;
|
||||
|
||||
public MDConsumerHandle md => _model.md;
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
@@ -294,7 +287,7 @@ namespace mbts.spark {
|
||||
public double day_position => _day_position;
|
||||
public ulong volume => _volume.Value;
|
||||
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
|
||||
/// Important: <see cref="order_properties_vm"/> can be null.
|
||||
public OrderPropertiesEditorVM order_properties_vm { get; }
|
||||
public ObservableCollection<LadderOrderVM> orders { get; }
|
||||
@@ -302,11 +295,11 @@ namespace mbts.spark {
|
||||
public ICommand send_order_command { get; }
|
||||
public ICommand show_book_command { get; }
|
||||
public ICommand show_ticket_command { get; }
|
||||
public ICommand settings_command { get; }
|
||||
public Brush bid_background => _bid_background.view;
|
||||
public Brush bid_foreground => _bid_foreground.view;
|
||||
public Brush ask_background => _ask_background.view;
|
||||
public Brush ask_foreground => _ask_foreground.view;
|
||||
public DialogCommand settings_command { get; }
|
||||
public bool show_day => _show_day.view;
|
||||
public bool show_high => _show_high.view;
|
||||
public bool show_low => _show_low.view;
|
||||
@@ -347,7 +340,7 @@ namespace mbts.spark {
|
||||
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
var order = op.order;
|
||||
double p = order.price();
|
||||
@@ -366,10 +359,26 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public LadderSettings get_save_state() {
|
||||
var state = _settings_manager.current_settings();
|
||||
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor, piq_column_width));
|
||||
state.use_defaults = _settings_manager.instance_settings == null;
|
||||
return state;
|
||||
var rv = _current_settings.with_window_specifics(
|
||||
new LadderSettings.WindowSpecifics(
|
||||
left_panel_width,
|
||||
orders_column_width,
|
||||
bid_column_width,
|
||||
price_column_width,
|
||||
ask_column_width,
|
||||
last_column_width,
|
||||
volume_column_width,
|
||||
zoom_factor,
|
||||
piq_column_width));
|
||||
rv.auto_clear = auto_clear;
|
||||
if (order_properties_vm != null) {
|
||||
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
|
||||
if (order_properties_vm.selected_account.HasValue)
|
||||
rv.default_account = order_properties_vm.selected_account.Value.id;
|
||||
if (order_properties_vm.selected_algo_definition != null)
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key, _model.order_gateway.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void key_down(KeyEventArgs e) {
|
||||
@@ -384,20 +393,18 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void send_order((double price, Side? side, MouseButton buton) args) {
|
||||
if (args.buton == MouseButton.Middle) return;
|
||||
var size_from_alternate = args.buton == MouseButton.Right;
|
||||
if (order_properties_vm == null)
|
||||
public void send_order((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm == null || args.button == MouseButton.Middle)
|
||||
return;
|
||||
var s = _settings_manager.current_settings();
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && _current_settings.alternate_order_size == 0)) {
|
||||
return;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
uint size = size_from_alternate ? _current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +337,7 @@ namespace mbts.spark {
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
if (bs.HasValue) {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
return;
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
@@ -344,6 +345,7 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
|
||||
@@ -139,6 +139,7 @@ namespace mbts.spark {
|
||||
// properly Dispose of it later when the popup is closed.
|
||||
DataContext = obvm,
|
||||
Child = view,
|
||||
// StaysOpen true for debugging
|
||||
StaysOpen = false,
|
||||
PlacementTarget = popup_placement_target,
|
||||
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -10,10 +10,15 @@ namespace mbts.ui.spark {
|
||||
/// </summary>
|
||||
partial class LadderSettingsView : UserControl {
|
||||
public const string
|
||||
OrdersColumnSampleTemplateKey = nameof(OrdersColumnSampleTemplateKey),
|
||||
ColumnVisibilityToggleTemplateKey = nameof(ColumnVisibilityToggleTemplateKey),
|
||||
OrdersColumnTemplateKey = nameof(OrdersColumnTemplateKey),
|
||||
VolumeColumnTemplateKey = nameof(VolumeColumnTemplateKey),
|
||||
LastColumnTemplateKey = nameof(LastColumnTemplateKey),
|
||||
FavoriteAlgoAddButtonTemplateKey = nameof(FavoriteAlgoAddButtonTemplateKey);
|
||||
|
||||
public static readonly IMultiValueConverter LadderColumnToggleShowToolTipConverter =
|
||||
ValueConverter.create<bool, bool, string, string>((can_toggle_show, show, col_name) =>
|
||||
can_toggle_show ? $"{(show ? "Hide" : "Show")} {col_name}" : $"{col_name} cannot be {(show ? "hidden" : "shown")}.");
|
||||
|
||||
public LadderSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
@@ -288,8 +288,11 @@
|
||||
/>
|
||||
</Grid.LayoutTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="60" MaxWidth="150"/>
|
||||
<ColumnDefinition
|
||||
Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="{x:Static spark:LadderView.MinLeftPanelWidth}"
|
||||
MaxWidth="{x:Static spark:LadderView.MaxLeftPanelWidth}"
|
||||
/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -309,11 +312,14 @@
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<StackPanel x:Name="_slider_stack_panel" Margin="5,5,5,0">
|
||||
<Slider x:Name="_ui_scaler" Value="{Binding zoom_factor}"
|
||||
Minimum="{StaticResource MinZoomFactor}"
|
||||
Maximum="{StaticResource MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Slider
|
||||
x:Name="_ui_scaler"
|
||||
Value="{Binding zoom_factor}"
|
||||
Minimum="{x:Static spark:LadderView.MinZoomFactor}"
|
||||
Maximum="{x:Static spark:LadderView.MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</StackPanel>
|
||||
<Grid Margin="2,4,2,0">
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -11,6 +11,12 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderView : UserControl {
|
||||
public const double
|
||||
MinLeftPanelWidth = 60,
|
||||
MaxLeftPanelWidth = 150,
|
||||
MinZoomFactor = 0.5,
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", total_pos_text = "total";
|
||||
@@ -40,8 +46,6 @@ namespace mbts.spark {
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
readonly double _max_zoom, _min_zoom;
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
InitializeComponent();
|
||||
_column_headers_presenter.Columns = new GridViewColumnCollection();
|
||||
@@ -50,19 +54,6 @@ namespace mbts.spark {
|
||||
this.vm = vm;
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
if (TryFindResource("MaxZoomFactor") is double found_max) {
|
||||
_max_zoom = found_max;
|
||||
} else {
|
||||
// is there a better way of not specifying the constant twice ?
|
||||
// once in the resource dictionary here and again in the xaml, seems like there has to be a better way
|
||||
_max_zoom = 1.5;
|
||||
}
|
||||
// ditto
|
||||
if (TryFindResource("MinZoomFactor") is double found_min) {
|
||||
_min_zoom = found_min;
|
||||
} else {
|
||||
_min_zoom = 0.5;
|
||||
}
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
|
||||
this.PreviewMouseDown += LadderView_PreviewMouseDown;
|
||||
@@ -78,7 +69,7 @@ namespace mbts.spark {
|
||||
var delta = (e.Delta > 0) ? 0.1 : -0.1;
|
||||
var proposed_scaler_value = _ui_scaler.Value + delta;
|
||||
// Here we enforce the same contraints that are on the gui slider regarding min and max
|
||||
if (_min_zoom < proposed_scaler_value && proposed_scaler_value < _max_zoom) {
|
||||
if (MinZoomFactor < proposed_scaler_value && proposed_scaler_value < MaxZoomFactor) {
|
||||
_ui_scaler.Value = proposed_scaler_value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
|
||||
@@ -99,7 +99,16 @@
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
|
||||
<!-- We added this IsSelected after seeing some strangeness when bringing up the Mini OB,
|
||||
then clicking the attach button, then performing a click to modify a param in the popup, the grid
|
||||
would lose the appearance of its selected row. I think this was actually a result of a bad path
|
||||
in the SelectionHighlight expression below, but that leaves me questioning how if it was bad (and
|
||||
it was, I confirmed in Snoop) did the Grid successfully show a highlighted row in the first place.
|
||||
|
||||
At any rate, I have a strong suspicion this Setter is no longer necessary, but I am too exhausted
|
||||
and scared to remove it at this point.
|
||||
-->
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
<DataTemplate x:Key="{x:Static sp:MiniOrderBookView.RowTemplateKey}">
|
||||
<sp:MiniOrderBookRowView/>
|
||||
@@ -121,10 +130,9 @@
|
||||
Rows="{Binding rows}"
|
||||
RowStyle="{StaticResource {x:Static sp:MiniOrderBookView.RowStyleKey}}"
|
||||
RowTemplate="{StaticResource {x:Static sp:MiniOrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
RowHeight="20"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace mbts.spark {
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
// (algo items)
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key, model.dynamic_helper.dynamic_type_index)).OnDispatcher();
|
||||
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
|
||||
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
|
||||
// order type
|
||||
@@ -176,7 +176,7 @@ namespace mbts.spark {
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
@@ -188,7 +188,7 @@ namespace mbts.spark {
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
root_algo_type_items_view.CustomSort = algo_items_comp;
|
||||
@@ -724,6 +724,33 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public class CheckColumn : FrameworkElementColumnBase<Columns> {
|
||||
readonly BindingBase _checked_binding;
|
||||
public CheckColumn(string checked_property) : base(Columns.Check) {
|
||||
id = key.ToString();
|
||||
Width = 30;
|
||||
_checked_binding = new Binding(checked_property);
|
||||
|
||||
var header_cb = new CheckBox();
|
||||
header_cb.ToolTip = "Select / Unselect All";
|
||||
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
|
||||
this.Header = header_cb;
|
||||
}
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var grid = new Grid() {
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
var cb = new CheckBox();
|
||||
cb.Name = "check_col";
|
||||
cb.SetBinding(CheckBox.IsCheckedProperty, _checked_binding);
|
||||
|
||||
grid.Children.Add(cb);
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
|
||||
public class CancelColumn : FrameworkElementColumnBase<Columns> {
|
||||
readonly BindingBase _command_binding;
|
||||
|
||||
@@ -812,7 +839,10 @@ namespace mbts.spark {
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
attach_grid.Children.Add(attach_button);
|
||||
attach_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
|
||||
attach_button.Click += (s, e) => {
|
||||
popup.Child = create_popup_child();
|
||||
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);
|
||||
@@ -829,84 +859,9 @@ namespace mbts.spark {
|
||||
|
||||
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);
|
||||
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;
|
||||
// set to true for debugging
|
||||
popup.StaysOpen = false;
|
||||
popup.PlacementTarget = attach_button;
|
||||
popup.Placement = PlacementMode.Bottom;
|
||||
@@ -914,7 +869,6 @@ namespace mbts.spark {
|
||||
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Detach Button
|
||||
@@ -950,6 +904,83 @@ namespace mbts.spark {
|
||||
the_whole_thing.Children.Add(popup);
|
||||
|
||||
return the_whole_thing;
|
||||
|
||||
UIElement create_popup_child() {
|
||||
#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
|
||||
|
||||
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))) {
|
||||
// set to true for debugging
|
||||
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,
|
||||
};
|
||||
return border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,17 +1001,21 @@ namespace mbts.spark {
|
||||
public override FrameworkElement view_factory() {
|
||||
var popup = new System.Windows.Controls.Primitives.Popup();
|
||||
|
||||
#region Attach Button
|
||||
#region Update Button
|
||||
|
||||
// create the attach button and assign the menu to it
|
||||
// create the update 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
|
||||
Focusable = false,
|
||||
ToolTip = "Update"
|
||||
};
|
||||
update_button.Click += (s, e) => {
|
||||
popup.Child = create_popup_child();
|
||||
popup.IsOpen = !popup.IsOpen;
|
||||
};
|
||||
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);
|
||||
@@ -995,89 +1030,14 @@ namespace mbts.spark {
|
||||
row_vm_update_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
|
||||
update_button.SetBinding(Button.CommandProperty, row_vm_update_command_binding);
|
||||
|
||||
// update button is only visible for orders under the control of an algo
|
||||
var update_button_visibility_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.root_algo_order_type))) {
|
||||
Converter = ValueConverter.create<CustomOrderDefinition.CustomOneofCase, Visibility>(ord_type => utilities.OrderDataHelper.outright_order_types.Contains(ord_type) ? Visibility.Hidden : Visibility.Visible)
|
||||
};
|
||||
update_button_visibility_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
|
||||
update_button.SetBinding(Button.VisibilityProperty, update_button_visibility_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;
|
||||
@@ -1086,8 +1046,9 @@ namespace mbts.spark {
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
|
||||
});
|
||||
popup.Closed += (s, e) => {
|
||||
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm)
|
||||
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm) {
|
||||
ob_vm.is_attaching_orders = false;
|
||||
}
|
||||
};
|
||||
|
||||
var the_whole_thing = new Grid();
|
||||
@@ -1095,6 +1056,83 @@ namespace mbts.spark {
|
||||
the_whole_thing.Children.Add(popup);
|
||||
|
||||
return the_whole_thing;
|
||||
|
||||
UIElement create_popup_child() {
|
||||
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)));
|
||||
|
||||
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))) {
|
||||
// set to true for debugging
|
||||
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,
|
||||
};
|
||||
return border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,16 @@ namespace mbts.spark {
|
||||
// for mini-ob
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
Action<bool> _on_selection_changed;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded) {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
_is_expanded = is_expanded;
|
||||
prospective_quantity = this.quantity;
|
||||
prospective_price = this.price;
|
||||
_on_selection_changed = on_selection_changed;
|
||||
}
|
||||
|
||||
//// This takes care of the case where the OB has all rows visible
|
||||
@@ -118,7 +120,11 @@ namespace mbts.spark {
|
||||
public int depth_level { get => _depth_level; set => Set(ref _depth_level, value); }
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set => Set(ref _is_selected, value);
|
||||
set {
|
||||
if (Set(ref _is_selected, value)) {
|
||||
_on_selection_changed(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public uint prospective_quantity {
|
||||
get => _prospective_quantity;
|
||||
@@ -190,14 +196,18 @@ namespace mbts.spark {
|
||||
public ICommand attach_command => _attach_command ?? (
|
||||
_attach_command = new DelegateCommand(() => {
|
||||
// the _select me will trigger the OBVM.attach_vm to get primed
|
||||
_select_me();
|
||||
if (!is_selected) {
|
||||
_select_me();
|
||||
}
|
||||
_owner.begin_attach_algos();
|
||||
})
|
||||
);
|
||||
|
||||
public ICommand update_command => _update_command ?? (
|
||||
_update_command = new DelegateCommand(() => {
|
||||
_select_me();
|
||||
if (!is_selected) {
|
||||
_select_me();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -233,12 +243,10 @@ namespace mbts.spark {
|
||||
|
||||
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,
|
||||
@@ -290,6 +298,10 @@ namespace mbts.spark {
|
||||
// root display name
|
||||
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
|
||||
RaisePropertyChanged(nameof(root_algo_display_name));
|
||||
// root order type
|
||||
if (old_order_tuple.root_algo.value.CustomCase != order_tuple.root_algo.value.CustomCase) {
|
||||
RaisePropertyChanged(nameof(root_algo_order_type));
|
||||
}
|
||||
// status
|
||||
if ((Columns.Status & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(status));
|
||||
|
||||
@@ -18,6 +18,7 @@ using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -31,7 +32,7 @@ using System.Windows.Input;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting, IRequestRowSelection {
|
||||
const double
|
||||
_min_right_panel_width = 200,
|
||||
_min_order_params_height = 300,
|
||||
@@ -56,13 +57,15 @@ namespace mbts.spark {
|
||||
bool
|
||||
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
|
||||
_is_attaching_orders,
|
||||
_is_row_collection_populated;
|
||||
_is_row_collection_populated,
|
||||
_all_rows_selected;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
|
||||
HashSet<uint>
|
||||
_attachable_selected_orders = new HashSet<uint>(),
|
||||
_detachable_selected_orders = new HashSet<uint>();
|
||||
FilterData _cached_filter_data;
|
||||
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
|
||||
|
||||
public OrderBookVM(
|
||||
IObservable<string> owner_title,
|
||||
@@ -98,7 +101,17 @@ namespace mbts.spark {
|
||||
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
|
||||
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded, (row_is_selected) => {
|
||||
// all are selected and one is checked off
|
||||
if (all_rows_selected && !row_is_selected) {
|
||||
_all_rows_selected = false;
|
||||
RaisePropertyChanged(nameof(this.all_rows_selected));
|
||||
// not all selected and now the last one gets turned on
|
||||
} else if (!all_rows_selected && _row_collection.All(r => r.is_selected)) {
|
||||
_all_rows_selected = true;
|
||||
RaisePropertyChanged(nameof(this.all_rows_selected));
|
||||
}
|
||||
}) {
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
|
||||
},
|
||||
@@ -125,7 +138,7 @@ namespace mbts.spark {
|
||||
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
|
||||
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
|
||||
const string title = "Order Book 'Mini' Fills Grid";
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_fills_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -137,7 +150,7 @@ namespace mbts.spark {
|
||||
),
|
||||
show_save_as_defaults: false);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = title, DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
@@ -334,6 +347,7 @@ namespace mbts.spark {
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
public event Action begin_attaching, end_attaching;
|
||||
public event Action<(IEnumerable, bool)> request_selection;
|
||||
|
||||
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
|
||||
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
|
||||
@@ -419,6 +433,13 @@ namespace mbts.spark {
|
||||
public bool can_attach_selected_orders => _can_attach_selected_orders.Value;
|
||||
public bool can_detach_selected_orders => _can_detach_selected_orders.Value;
|
||||
public Plurality security_plurality { get; private set; }
|
||||
public bool all_rows_selected {
|
||||
get => _all_rows_selected;
|
||||
set {
|
||||
Set(ref _all_rows_selected, value);
|
||||
request_selection?.Invoke((_row_collection, _all_rows_selected));
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
|
||||
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
|
||||
@@ -430,19 +451,43 @@ namespace mbts.spark {
|
||||
if (e.RemovedItems != null) {
|
||||
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = false;
|
||||
_selected_params_mon.on_row_unselected(row_vm);
|
||||
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
}
|
||||
}
|
||||
if (e.AddedItems != null) {
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
var root = _get_root(row_vm.order_tuple);
|
||||
row_vm.is_selected = true;
|
||||
_selected_params_mon.on_row_selected(row_vm);
|
||||
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
|
||||
row_vm.updated += on_updated;
|
||||
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
|
||||
}
|
||||
_selected_params_mon.on_order_selected(root);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
_detachable_selected_orders.Add(row_vm.order_key);
|
||||
void on_updated((OrderSecurity oldval, OrderSecurity newval) change) {
|
||||
if (!row_vm.is_selected) {
|
||||
// self destruct
|
||||
if (_on_updated_single_callback_gatekeeper.TryGetValue(row_vm.order_key, out var unsubscribe_updated_disposable)) {
|
||||
unsubscribe_updated_disposable.Dispose();
|
||||
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
|
||||
}
|
||||
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
|
||||
return;
|
||||
}
|
||||
var new_root = _get_root(change.newval);
|
||||
if (!new_root.Equals(root)) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.on_order_updated((root, new_root));
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
@@ -520,11 +565,11 @@ namespace mbts.spark {
|
||||
if (!new_sort.Equals(old_sort)) {
|
||||
/// Both filter and sort have changed, use <see cref="OperableHierarchicalCollection{TRow, T, TKey}.defer_refresh"/> to avoid syncing twice.
|
||||
using (_row_collection.defer_refresh()) {
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
}
|
||||
} else {
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
} else if (!new_sort.Equals(old_sort))
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
@@ -547,15 +592,25 @@ namespace mbts.spark {
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
|
||||
_cached_orders.TryGetValue(row.parent_order_key, out parent);
|
||||
|
||||
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
|
||||
while (r.ParentKey != 0 && _cached_orders.TryGetValue(r.ParentKey, out var parent_osam)) {
|
||||
r = parent_osam.order;
|
||||
}
|
||||
return r;
|
||||
OrderBookRowVM _get_root_row(OrderBookRowVM row) {
|
||||
while (row.parent_order_key != 0 && _row_collection.get_parent(row.parent_order_key) is OrderBookRowVM prow)
|
||||
row = prow;
|
||||
return row;
|
||||
}
|
||||
|
||||
internal void detach_orders(OrderBookRowVM order) =>
|
||||
_detach_orders_async(new[] { order }).ignore();
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
internal void detach_orders(OrderBookRowVM row) {
|
||||
if (row.is_selected) {
|
||||
_detach_orders_async(selected_rows).ignore();
|
||||
} else {
|
||||
_detach_orders_async(new[] { row }).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
public OrderSecurity get_ordersecurityalgometa(OrderRecordImmutable order) {
|
||||
_cached_orders.TryGetValue(order.Key, out var osam);
|
||||
@@ -571,8 +626,13 @@ namespace mbts.spark {
|
||||
internal void show_order_modify(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
|
||||
|
||||
internal void cancel_order(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
|
||||
internal void cancel_order(OrderBookRowVM row) {
|
||||
if (row.is_selected) {
|
||||
_bus.post(SparkTags.CancelOrders, selected_rows.Select(r => r.order_key).ToList());
|
||||
} else {
|
||||
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
|
||||
}
|
||||
}
|
||||
|
||||
internal void modify_order_quantity(OrderBookRowVM row, uint size) =>
|
||||
_bus.post(SparkTags.UpdateOrder, (row.order_key, row.price, size));
|
||||
@@ -595,7 +655,7 @@ namespace mbts.spark {
|
||||
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
|
||||
if (!fd.Equals(_cached_filter_data)) {
|
||||
_cached_filter_data = fd;
|
||||
_row_collection.filter = fd.build_predicate();
|
||||
_row_collection.filter = fd.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,6 +671,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// Note: modifications are performed on the selected orders' ROOT orders.
|
||||
async Task _modify_selected_orders_async() {
|
||||
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
|
||||
uint? size = order_mod_vm.size;
|
||||
@@ -623,17 +684,17 @@ namespace mbts.spark {
|
||||
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
|
||||
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
|
||||
// ----
|
||||
var update_params = selected_rows.Select(update_params_factory).ToList();
|
||||
var update_params = selected_rows.Select(r => update_params_factory(_get_root(r.order_tuple))).ToList();
|
||||
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
|
||||
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurity order_tuple) {
|
||||
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
|
||||
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
|
||||
} else {
|
||||
mod_algo_vm.assign_to(mod_algo);
|
||||
}
|
||||
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
|
||||
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
return (order_tuple.order, OrderDataHelper.EqualsHelpers.equals(order_tuple.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
}
|
||||
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
|
||||
}
|
||||
@@ -883,11 +944,10 @@ namespace mbts.spark {
|
||||
return;
|
||||
Disposable.replace(ref _ladder_vm,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(
|
||||
_owner._bus,
|
||||
LadderVM.create_readonly(_owner._bus,
|
||||
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
|
||||
null);
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.Default)) : null);
|
||||
RaisePropertyChanged(nameof(ladder_vm));
|
||||
}
|
||||
void _assign_param_permissions<T>(IAlgoParamVM<T> param, ref AlgoParamPermissions permissions_backing, AlgoParamPermissions permissions) {
|
||||
@@ -908,8 +968,8 @@ namespace mbts.spark {
|
||||
this.column_filters = column_filters;
|
||||
}
|
||||
|
||||
public Func<OrderSecurity, bool> build_predicate() {
|
||||
var pred = column_filters.build_predicate();
|
||||
public Func<OrderSecurity, bool> build_predicate(IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pred = column_filters.build_predicate(dynamic_type_index);
|
||||
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
|
||||
if (pred == null) {
|
||||
pred = tree_disp_pred;
|
||||
@@ -1006,22 +1066,18 @@ namespace mbts.spark {
|
||||
public IDisposable begin_udpate() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
public void on_row_selected(OrderBookRowVM row) {
|
||||
_group_mon.add(row.order_tuple);
|
||||
row.updated += _on_row_updated;
|
||||
}
|
||||
public void on_order_selected(OrderSecurity order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void on_row_unselected(OrderBookRowVM row) {
|
||||
_group_mon.remove(row.order_tuple);
|
||||
row.updated -= _on_row_updated;
|
||||
}
|
||||
public void on_order_unselected(OrderSecurity order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
|
||||
_security_md_map = security_md_map;
|
||||
_security_md_map_changed?.Invoke();
|
||||
}
|
||||
|
||||
void _on_row_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
|
||||
public void on_order_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
|
||||
using (begin_udpate()) {
|
||||
_group_mon.remove(ch.oldval);
|
||||
_group_mon.add(ch.newval);
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace mbts.spark {
|
||||
toggle_expand_all_command.group = CommandGroups.Auxiliary;
|
||||
|
||||
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
|
||||
var gs_vm = new ShellSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -175,7 +175,7 @@ namespace mbts.spark {
|
||||
}
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { DataContext = gs_vm };
|
||||
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
|
||||
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(gs_vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
|
||||
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
|
||||
});
|
||||
|
||||
@@ -1,154 +1,155 @@
|
||||
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;
|
||||
|
||||
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(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_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)]
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
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(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_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;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
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(root_algo_types, other.root_algo_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) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
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(root_algo_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),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
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(root_algo_types, other.root_algo_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) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
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(root_algo_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),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key, dynamic_type_index));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,4 +108,9 @@
|
||||
<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}">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
</c:DIYGrid>
|
||||
@@ -50,7 +50,8 @@ namespace mbts.spark {
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
|
||||
@@ -516,7 +516,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type) {
|
||||
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type, bool fit_all_to_spreader = false) {
|
||||
var mdef = defi.CloneToMutable();
|
||||
switch (security_type) {
|
||||
case Security.ContractOneofCase.Synthetic:
|
||||
@@ -542,7 +542,8 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop:
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit:
|
||||
case CustomOrderDefinition.CustomOneofCase.Market:
|
||||
def = null;
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg:
|
||||
def = fit_all_to_spreader ? new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() } : null;
|
||||
break;
|
||||
default:
|
||||
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
|
||||
@@ -622,6 +623,13 @@ namespace mbts.spark.utilities {
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
},
|
||||
outright_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
};
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
|
||||
|
||||
|
||||
@@ -26,27 +26,26 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
class InlineOrderProperties {
|
||||
public readonly InlineUserProperty<AccountTuple> account;
|
||||
public readonly InlineUserProperty<uint> account;
|
||||
public readonly InlineUserProperty<TimeInForce> time_in_force;
|
||||
public readonly InlineUserProperty<CustomOrderDefinition> order_type_def;
|
||||
public readonly InlineUserProperty<AlgoID> algo;
|
||||
|
||||
public InlineOrderProperties(OrdersModel model, LadderSettings settings) : this(
|
||||
new InlineUserProperty<AccountTuple>(),
|
||||
new InlineUserProperty<uint>(settings.default_account, settings.default_account != 0),
|
||||
new InlineUserProperty<TimeInForce>(settings.default_time_in_force, true),
|
||||
new InlineUserProperty<CustomOrderDefinition>(model.factory.custom_order(settings.default_order_type), true)) { }
|
||||
new InlineUserProperty<AlgoID>(settings.default_algo, true)) { }
|
||||
|
||||
public InlineOrderProperties(OrdersModel model, OrderDefinitionImmutable def) : this(
|
||||
new InlineUserProperty<AccountTuple>(),
|
||||
new InlineUserProperty<TimeInForce>(def.TimeInForce, true),
|
||||
new InlineUserProperty<CustomOrderDefinition>(def.CustomDef.CloneToMutable(), true)) { }
|
||||
new InlineUserProperty<uint>(def.AccountId, def.AccountId != 0),
|
||||
new InlineUserProperty<TimeInForce>(def.TimeInForce, true)) { }
|
||||
|
||||
public InlineOrderProperties(
|
||||
InlineUserProperty<AccountTuple> account=null,
|
||||
InlineUserProperty<uint> account=null,
|
||||
InlineUserProperty<TimeInForce> time_in_force=null,
|
||||
InlineUserProperty<CustomOrderDefinition> order_type_def=null) {
|
||||
this.account = account ?? new InlineUserProperty<AccountTuple>();
|
||||
InlineUserProperty<AlgoID> algo = null) {
|
||||
this.account = account ?? new InlineUserProperty<uint>();
|
||||
this.time_in_force = time_in_force ?? new InlineUserProperty<TimeInForce>();
|
||||
this.order_type_def = order_type_def ?? new InlineUserProperty<CustomOrderDefinition>();
|
||||
this.algo = algo ?? new InlineUserProperty<AlgoID>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +101,23 @@ namespace mbts.spark {
|
||||
_favorite_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.is_favorite));
|
||||
_favorite_algo_definitions_view.IsLiveFiltering = true;
|
||||
favorite_algo_definitions_view.Filter = d => d.is_favorite;
|
||||
//_accounts
|
||||
var inline_account_src = order_properties.AggregateLatest(
|
||||
model.factory.subscribe_accounts(),
|
||||
(last, change, cache) => convert_inline_account(change.New.account, cache),
|
||||
(last, props, change) => convert_inline_account(props.account, change.New),
|
||||
() => new InlineUserProperty<AccountTuple>(),
|
||||
new InlineOrderProperties(),
|
||||
DictionaryHelper.empty<uint, string>());
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
model.factory.subscribe_account_tuples(),
|
||||
order_properties.Select(ops => ops.account),
|
||||
inline_account_src,
|
||||
a => new AccountTuple?(a),
|
||||
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
|
||||
EqualityComparer<AccountTuple?>.Default,
|
||||
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
|
||||
// time in force
|
||||
_time_in_force = backing(
|
||||
nameof(selected_time_in_force),
|
||||
// remember, both observables must fire once to start the CombineLatest sequence
|
||||
@@ -164,8 +172,12 @@ namespace mbts.spark {
|
||||
}, out IObserver<bool> revert_algo_params_observer);
|
||||
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
|
||||
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
|
||||
var inline_algo_relay = new SelectionModelInlinePropRelay<AlgoDefinitionVM, AlgoDefinitionVM, AlgoID>(_selected_algo_definition, vm => AlgoID.from_algo_key(vm.key, _model.dynamic_helper.dynamic_type_index));
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
order_properties.OnDispatcher().Subscribe(props => {
|
||||
inline_algo_relay.on_inline_prop(props.algo);
|
||||
}),
|
||||
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
|
||||
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
|
||||
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
|
||||
@@ -226,6 +238,7 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _algo_param_rows_hook);
|
||||
}
|
||||
};
|
||||
// local methods
|
||||
async Task save_template_async(AlgoDefinitionTemplateVM template) {
|
||||
using (_preserve_selection_by_index())
|
||||
await AlgoPersistenceHelper.persist_template(model, algo_definitions, template);
|
||||
@@ -250,6 +263,10 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
}
|
||||
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map) =>
|
||||
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
|
||||
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), account_id.force_override) :
|
||||
new InlineUserProperty<AccountTuple>();
|
||||
}
|
||||
|
||||
IDisposable _template_collection_sync;
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
<PackageReference Include="System.Reactive" Version="3.1.1" />
|
||||
<PackageReference Include="protobuf-net" Version="2.3.2" />
|
||||
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
|
||||
<PackageReference Include="WPFTextBoxAutoComplete" Version="1.0.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml" Generator="MSBuild:Compile" />
|
||||
@@ -52,12 +53,6 @@
|
||||
<Resource Include="**\*.png" />
|
||||
<ConfigDefaults Include="../../lib/config/defaults/*.conf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Sounds\game_sound_correct.wav" />
|
||||
<None Remove="Sounds\whistle.wav" />
|
||||
<None Remove="view\Controls\TickUpDown\TickUpDownNullableUIntCommit.xaml" />
|
||||
<None Remove="view\Shell\Workspaces\WorkspacesView.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Remove="logo.ico" />
|
||||
</ItemGroup>
|
||||
@@ -68,60 +63,5 @@
|
||||
<EmbeddedResource Include="Sounds\game_sound_correct.wav" />
|
||||
<EmbeddedResource Include="Sounds\whistle.wav" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\SecurityPicker\" />
|
||||
<Folder Include="view\Extensions\ItemsControl\ObservableItemsSource\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="view\Controls\TickUpDown\TickUpDownNullableUIntCommit.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="view\Shell\Workspaces\WorkspacesDialogView.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorGridView\AlgoEditorGridView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\LadderSettingsDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\LadderSettingsView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\LadderView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\MiniLadderView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\MiniOrderBook\MiniOrderBookView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\MiniOrderBook\OrderBookView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderBookGridView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderBookToolBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderBookView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderParamsPanel.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
|
||||
</Target>
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent" />
|
||||
</Project>
|
||||
@@ -14,6 +14,7 @@
|
||||
<ResourceDictionary Source="/view/Resources/Buttons.xaml" />
|
||||
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
|
||||
<ResourceDictionary Source="/view/Resources/MenuItems.xaml" />
|
||||
<ResourceDictionary Source="/view/Controls/ToolBar/ToolBar.xaml" />
|
||||
<ResourceDictionary Source="/view/Controls/EditableTextBlock/EditableTextBlock.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DragDropItemsControl/DragDropItemsControl.xaml" />
|
||||
<ResourceDictionary Source="/view/Controls/DragDropItemsControl/DraggableContentControl.xaml" />
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.collections {
|
||||
class ItemSelector<T> : ViewModelBase {
|
||||
@@ -128,7 +129,6 @@ namespace mbts.collections {
|
||||
readonly Property<T> _value;
|
||||
readonly Func<T, TKey> _key_selector;
|
||||
readonly string _prop_name;
|
||||
TKey? _desired_key;
|
||||
|
||||
public KeyedItemSelector(IRaiseNotifyPropertyChanged owner, string prop_name, IObservableCollection<T> items, Func<T, TKey> key) {
|
||||
_owner = owner;
|
||||
@@ -143,33 +143,42 @@ namespace mbts.collections {
|
||||
get => _value.Value;
|
||||
set {
|
||||
_value.set(value);
|
||||
if (_desired_key.HasValue) {
|
||||
if (desired_key.HasValue) {
|
||||
items.CollectionChanged -= _on_items_CollectionChanged;
|
||||
_desired_key = null;
|
||||
desired_key = null;
|
||||
}
|
||||
_owner.raise_notify_property_changed(_prop_name);
|
||||
}
|
||||
}
|
||||
public TKey? desired_key { get; private set; }
|
||||
|
||||
public bool try_select(TKey key) {
|
||||
if (items.TryGetFirst(i => _key_selector(i).Equals(key), out T result)) {
|
||||
value = result;
|
||||
return true;
|
||||
} else {
|
||||
if (!_desired_key.HasValue) {
|
||||
if (!desired_key.HasValue) {
|
||||
items.CollectionChanged += _on_items_CollectionChanged;
|
||||
}
|
||||
_desired_key = key;
|
||||
desired_key = key;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<T> subscribe_value() =>
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(value);
|
||||
_value.Changed += on_changed;
|
||||
return Disposable.Create(() => _value.Changed -= on_changed);
|
||||
void on_changed(T oldval, T newval) => o.OnNext(newval);
|
||||
});
|
||||
|
||||
void _on_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (T i in e.NewItems)
|
||||
if (_key_selector(i).Equals(_desired_key.Value)) {
|
||||
if (_key_selector(i).Equals(desired_key.Value)) {
|
||||
value = i;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,15 @@ namespace mbts.collections {
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncWithDictionary(IReadOnlyDictionary<TKey, T> dictionary) {
|
||||
foreach (var k in _map.Keys.Except(dictionary.Keys).ToList())
|
||||
Remove(k);
|
||||
foreach (var kv in dictionary) {
|
||||
if (!ContainsKey(kv.Key))
|
||||
Add(kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncWithDictionary<TModel>(IReadOnlyDictionary<TKey, TModel> dictionary, Func<TModel, T> factory) {
|
||||
foreach (var k in _map.Keys.Except(dictionary.Keys).ToList())
|
||||
Remove(k);
|
||||
@@ -206,6 +215,11 @@ namespace mbts.collections {
|
||||
}
|
||||
|
||||
static class ObservableKeyedCollectionExtensions {
|
||||
public static void AddRange<T, TKey>(this ObservableKeyedCollection<T, TKey> collection, IEnumerable<T> values) {
|
||||
foreach (var v in values)
|
||||
collection.Add(v);
|
||||
}
|
||||
|
||||
public static IReadOnlyObservableKeyedCollection<TResult, TKey> WrapSelector<T, TKey, TResult>(this IReadOnlyObservableKeyedCollection<T, TKey> source, Func<T, TResult> selector) =>
|
||||
new SelectorWrapper<T, TKey, TResult>(source, selector);
|
||||
|
||||
|
||||
@@ -45,8 +45,10 @@ namespace mbts.ui {
|
||||
var cmd = new DelegateCommand(() => execute(param), () => can_execute);
|
||||
observer = new AnonymousObserver<T>(p => {
|
||||
param = p;
|
||||
if (can_execute != get_can_execute(param))
|
||||
if (can_execute != get_can_execute(param)) {
|
||||
can_execute = !can_execute;
|
||||
cmd.RaiseCanExecuteChanged();
|
||||
}
|
||||
});
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@@ -6,19 +6,15 @@ namespace mbts.ui {
|
||||
// Value = "{mbts:BTSColor X.y}"
|
||||
// Some colos are also available as static resources in colors.xaml
|
||||
public static class BTSColors {
|
||||
// 84 = 255 * 0.33
|
||||
const byte _one_third_opacity_strength = 84;
|
||||
static Brush _header_brush, _header_text_brush, _header_alternate_brush, _header_error_text_brush;
|
||||
|
||||
static BTSColors() {
|
||||
_init_brush(header, out _header_brush);
|
||||
_init_brush(header_text, out _header_text_brush);
|
||||
_init_brush(header_alternate, out _header_alternate_brush);
|
||||
_init_brush(header_error_text, out _header_error_text_brush);
|
||||
}
|
||||
|
||||
// freezing brushes makes them thread-safe
|
||||
static void _init_brush(BTSColor color, out Brush brush) {
|
||||
brush = color;
|
||||
brush.Freeze();
|
||||
_header_brush = header;
|
||||
_header_text_brush = header_text;
|
||||
_header_alternate_brush = header_alternate;
|
||||
_header_error_text_brush = header_error_text;
|
||||
}
|
||||
|
||||
[DisplayInfo(name = "Default Header")]
|
||||
@@ -72,7 +68,7 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
public static class MarketData {
|
||||
static readonly Brush _bid_brush, _ask_brush;
|
||||
static readonly Brush _bid_brush, _ask_brush, _semi_transparent_bid_brush, _semi_transparent_ask_brush;
|
||||
|
||||
static MarketData() {
|
||||
// null BTSColors are implicitly gray media colors/brushes (this behavior is problematic and should be changed),
|
||||
@@ -81,6 +77,8 @@ namespace mbts.ui {
|
||||
ask = BTSCoreColors.red_darker;
|
||||
_bid_brush = bid;
|
||||
_ask_brush = ask;
|
||||
_semi_transparent_bid_brush = new BTSColor(_one_third_opacity_strength, BTSColors.MarketData.bid);
|
||||
_semi_transparent_ask_brush = new BTSColor(_one_third_opacity_strength, BTSColors.MarketData.ask);
|
||||
}
|
||||
// Base Colors
|
||||
[DisplayInfo(name = "Market Data")]
|
||||
@@ -97,6 +95,9 @@ namespace mbts.ui {
|
||||
|
||||
public static Brush bid_brush => _bid_brush;
|
||||
public static Brush ask_brush => _ask_brush;
|
||||
public static Brush semi_transparent_bid_brush => _semi_transparent_bid_brush;
|
||||
public static Brush semi_transparent_ask_brush => _semi_transparent_ask_brush;
|
||||
|
||||
|
||||
[DisplayInfo(name = "Settlement")]
|
||||
public static BTSColor settlement_background { get { return Grid.default_column_background; } }
|
||||
@@ -165,14 +166,14 @@ namespace mbts.ui {
|
||||
|
||||
|
||||
static Orders() {
|
||||
_init_brush(_sent, out sent_brush);
|
||||
_init_brush(filled_dark, out filled_dark_brush);
|
||||
_init_brush(working_cancel, out working_cancel_brush);
|
||||
_init_brush(working_order_background, out working_order_background_brush);
|
||||
_init_brush(completed_order_background, out completed_order_background_brush);
|
||||
_init_brush(error_order_background, out error_order_background_brush);
|
||||
_init_brush(canceled_order_background, out canceled_order_background_brush);
|
||||
_init_brush(modified, out modified_brush);
|
||||
sent_brush = _sent;
|
||||
filled_dark_brush = filled_dark;
|
||||
working_cancel_brush = working_cancel;
|
||||
working_order_background_brush = working_order_background;
|
||||
completed_order_background_brush = completed_order_background;
|
||||
error_order_background_brush = error_order_background;
|
||||
canceled_order_background_brush = canceled_order_background;
|
||||
modified_brush = modified;
|
||||
}
|
||||
|
||||
// Base Colors
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using bts.api.core;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.persist {
|
||||
/// <summary>
|
||||
/// This service offers methods to save named settings, and observe the resultant named-settings <see cref="IObservable{T}"/> <see cref="TableUpdate{T, TKey}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of settings.</typeparam>
|
||||
interface ISettingsProfileService<T> where T : class, IEquatable<T> {
|
||||
uint add_or_update(SettingsProfile<T> value);
|
||||
IReadOnlyList<uint> add_or_update(IEnumerable<SettingsProfile<T>> value);
|
||||
void remove(uint id);
|
||||
IObservable<TableUpdate<SettingsProfile<T>, uint>> subscribe_all();
|
||||
}
|
||||
|
||||
class SettingsProfileService<T> : ISettingsProfileService<T> where T : class, IEquatable<T> {
|
||||
readonly UserSettingsHub _hub;
|
||||
readonly Func<UserSettings, ImmutableDictionary<uint, SettingsProfile<T>>> _get_profiles;
|
||||
readonly Func<UserSettings, IReadOnlyDictionary<uint, SettingsProfile<T>>, UserSettings> _set_profiles;
|
||||
|
||||
public SettingsProfileService(UserSettingsHub hub, Func<UserSettings, ImmutableDictionary<uint, SettingsProfile<T>>> get_profiles, Func<UserSettings, IReadOnlyDictionary<uint, SettingsProfile<T>>, UserSettings> set_profiles) {
|
||||
_hub = hub;
|
||||
_get_profiles = get_profiles;
|
||||
_set_profiles = set_profiles;
|
||||
}
|
||||
|
||||
// Note: this operation is atomic, but does not protect against overwrites.
|
||||
// This is not cause for concern, since settings are saved via user intervention (not programatically),
|
||||
// it is pracically impossible for the user to click 'Save' fast enough to cause an incidental overwrite.
|
||||
public uint add_or_update(SettingsProfile<T> value) {
|
||||
var curr = _hub.current;
|
||||
var mdata = _get_profiles(curr);
|
||||
if (value.id == 0)
|
||||
value = value.with(curr.next_ladder_profile_id);
|
||||
_hub.save_default(_set_profiles(curr, mdata.SetItem(value.id, value))).ignore();
|
||||
return value.id;
|
||||
}
|
||||
|
||||
public IReadOnlyList<uint> add_or_update(IEnumerable<SettingsProfile<T>> values) {
|
||||
var curr = _hub.current;
|
||||
var builder = _get_profiles(curr).ToBuilder();
|
||||
var ids = new List<uint>();
|
||||
uint new_id = curr.next_ladder_profile_id;
|
||||
foreach (var value in values) {
|
||||
if (value.id == 0) {
|
||||
builder.Add(new_id, value.with(new_id));
|
||||
new_id++;
|
||||
} else {
|
||||
builder[value.id] = value;
|
||||
}
|
||||
}
|
||||
_hub.save_default(_set_profiles(curr, builder.ToDictionary())).ignore();
|
||||
return ids;
|
||||
}
|
||||
|
||||
public void remove(uint id) {
|
||||
var curr = _hub.current;
|
||||
_hub.save_default(_set_profiles(curr, _get_profiles(curr).Remove(id))).ignore();
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<SettingsProfile<T>, uint>> subscribe_all() =>
|
||||
_hub.subscribe_default_settings().Select(_get_profiles).Table(SettingsProfile<T>.key_fn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface ISettingsProfile<T> {
|
||||
uint id { get; }
|
||||
string name { get; }
|
||||
T value { get; }
|
||||
SecurityCriteria security_criteria { get; }
|
||||
}
|
||||
|
||||
struct SettingsProfile<T> : IEquatable<SettingsProfile<T>> where T : class, IEquatable<T> {
|
||||
public static readonly Func<SettingsProfile<T>, uint> key_fn = ns => ns.id;
|
||||
|
||||
public readonly uint id;
|
||||
public readonly string name;
|
||||
public readonly T value;
|
||||
public readonly SecurityCriteria security_criteria;
|
||||
|
||||
public SettingsProfile(uint id, string name, T value, SecurityCriteria security_criteria = null) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.security_criteria = security_criteria;
|
||||
}
|
||||
|
||||
public SettingsProfile<T> with(uint? id = null, string name = null, T value = null, SecurityCriteria security_criteria = null) =>
|
||||
new SettingsProfile<T>(id ?? this.id, name ?? this.name, value ?? this.value, security_criteria ?? this.security_criteria);
|
||||
|
||||
public bool Equals(SettingsProfile<T> other) =>
|
||||
id == other.id &&
|
||||
name == other.name &&
|
||||
value.safe_equals(other.value) &&
|
||||
security_criteria.safe_equals(other.security_criteria);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is SettingsProfile<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
id.GetHashCode();
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class LadderSettingsProfile : ISettingsProfile<LadderSettings>, IEquatable<LadderSettingsProfile> {
|
||||
public LadderSettingsProfile() { }
|
||||
|
||||
public LadderSettingsProfile(uint id, string name, LadderSettings value, SecurityCriteria security_criteria = null) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.security_criteria = security_criteria;
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint id { get; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public string name { get; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
public LadderSettings value { get; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
public SecurityCriteria security_criteria { get; }
|
||||
|
||||
public LadderSettingsProfile with(uint? id = null, LadderSettings value = null) =>
|
||||
new LadderSettingsProfile(id ?? this.id, name, value ?? this.value, security_criteria);
|
||||
|
||||
public bool Equals(LadderSettingsProfile other) =>
|
||||
other != null &&
|
||||
id == other.id &&
|
||||
name == other.name &&
|
||||
value.safe_equals(other.value) &&
|
||||
security_criteria.safe_equals(other.security_criteria);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderSettingsProfile);
|
||||
public override int GetHashCode() => id.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,14 @@
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IDefaultableSettings {
|
||||
bool use_defaults { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The settings Mothership. All window - and any other future user based - settings should have a corresponding default member here.
|
||||
/// </summary>
|
||||
@@ -25,6 +28,18 @@ namespace mbts.spark {
|
||||
|
||||
[ProtoMember(1)]
|
||||
LadderSettings _ladder;
|
||||
[ProtoMember(11)]
|
||||
readonly LadderSettingsProfile[] _ladder_profiles;
|
||||
/// When Profiles Are Deleted, ladders that were using that profile shall copy the profile's settings to a local profile.
|
||||
/// Therefor:
|
||||
/// Deleted profile ids must never be reused.
|
||||
/// If they were reused, consider an unloaded workspace with ladders that were using the deleted profile.
|
||||
/// If such a workspace was reloaded after a new profile was saved with the deleted one's id, the ladders using that deleted profile would suddenly be using the new profile.
|
||||
/// To Prevent IDs From Being Recycled:
|
||||
/// The last profile id is stored in the protobuf (<see cref="_last_ladder_profile_id"/>).
|
||||
/// Anytime a profile is added, it shall use <see cref="next_ladder_profile_id"/>.
|
||||
[ProtoMember(12)]
|
||||
uint _last_ladder_profile_id;
|
||||
// removing NotificationSettings these from proto-serialization, since they are disabled by default for now.
|
||||
// This way, when they are enabled, there won't be any backwards compatability issues to worry about, as no clients will have persisted them yet.
|
||||
NotificationSettings _notification;
|
||||
@@ -44,7 +59,12 @@ namespace mbts.spark {
|
||||
|
||||
public UserSettings() { }
|
||||
|
||||
public UserSettings(LadderSettingsProfile[] ladder_profiles) =>
|
||||
_ladder_profiles = ladder_profiles;
|
||||
|
||||
public LadderSettings ladder => _ladder ?? (_ladder = LadderSettings.Default);
|
||||
public IReadOnlyList<LadderSettingsProfile> ladder_profiles => _ladder_profiles ?? Array.Empty<LadderSettingsProfile>();
|
||||
public uint next_ladder_profile_id => _last_ladder_profile_id + 1;
|
||||
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
|
||||
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
|
||||
@@ -53,15 +73,16 @@ namespace mbts.spark {
|
||||
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
|
||||
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
|
||||
|
||||
public UserSettings with(LadderSettings ladder = null,
|
||||
NotificationSettings notification = null,
|
||||
GridSettings provided_market_view_gridsettings = null,
|
||||
public UserSettings with(LadderSettings ladder = null,
|
||||
LadderSettingsProfile[] ladder_profiles = null,
|
||||
NotificationSettings notification = null,
|
||||
GridSettings provided_market_view_gridsettings = null,
|
||||
GridSettings time_and_sales = null,
|
||||
GridSettings provided_fills_gridsettings = null,
|
||||
GridSettings provided_synthetic_management_gridsettings = null,
|
||||
GridSettings provided_pnl_gridsettings = null,
|
||||
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null) =>
|
||||
new UserSettings {
|
||||
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null) {
|
||||
var us = new UserSettings(ladder_profiles ?? _ladder_profiles) {
|
||||
_ladder = ladder ?? _ladder,
|
||||
_notification = notification ?? _notification,
|
||||
_market_view_grid = provided_market_view_gridsettings ?? _market_view_grid,
|
||||
@@ -69,8 +90,11 @@ namespace mbts.spark {
|
||||
_fills_grid = provided_fills_gridsettings ?? _fills_grid,
|
||||
_synthetic_management_grid = provided_synthetic_management_gridsettings ?? _synthetic_management_grid,
|
||||
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
|
||||
_order_book_grid = order_book_grid ?? _order_book_grid
|
||||
_order_book_grid = order_book_grid ?? _order_book_grid,
|
||||
};
|
||||
us._last_ladder_profile_id = Math.Max(_last_ladder_profile_id, us.ladder_profiles.Select(p => p.id).MaxOrDefault());
|
||||
return us;
|
||||
}
|
||||
|
||||
public static class FileExtensions {
|
||||
public const string
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using bts;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
@@ -28,15 +31,40 @@ namespace mbts.persist {
|
||||
UserSettings s;
|
||||
try {
|
||||
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
|
||||
if (s != null) {
|
||||
// ensure valid named ladder set (w/ distinct, non-zero ids)
|
||||
var ladder_profile_map = new Dictionary<uint, LadderSettingsProfile>();
|
||||
var unidentified_lps = new List<LadderSettingsProfile>();
|
||||
foreach (var lp in s.ladder_profiles) {
|
||||
if (lp.id == 0) {
|
||||
unidentified_lps.Add(lp);
|
||||
} else ladder_profile_map[lp.id] = lp;
|
||||
}
|
||||
uint id = s.next_ladder_profile_id;
|
||||
foreach (var lp in unidentified_lps) {
|
||||
ladder_profile_map[id] = lp.with(id);
|
||||
id++;
|
||||
}
|
||||
var sorted_lps = ladder_profile_map.Values.ToArray();
|
||||
Array.Sort(sorted_lps, Comparer<uint>.Default.wrap<uint, LadderSettingsProfile>(lp => lp.id));
|
||||
if (!sorted_lps.SequenceEqual(s.ladder_profiles))
|
||||
await _persist.set(NAME_SPACE, DEFAULT_KEY, s = s.with(ladder_profiles: sorted_lps));
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
Log.Persist.warn($"Error loading {typeof(UserSettings)} from namespace '{NAME_SPACE}', will restore factory defaults and continue as usual: {ex}");
|
||||
await save_default(s = UserSettings.factory_defaults);
|
||||
}
|
||||
if (s != null)
|
||||
_set_and_notify(s);
|
||||
lock (_default_settings_lock) {
|
||||
initialized = true;
|
||||
if (s != null)
|
||||
_default_settings_subject.OnNext(_default_settings = s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// indicates whether or not the settings have been loaded from the server (this happens once, on startup - see the constructor for details)
|
||||
public bool initialized { get; private set; }
|
||||
|
||||
/// Subscribing via _persist.subscribe may be cleaner in theory, however using a local event allows observers to be notified immediately.
|
||||
/// Assuming there aren't any rogue agents saving UserSettings by themselves, there should never be any discrepancies.
|
||||
public IObservable<UserSettings> subscribe_default_settings() => _default_settings_subject.AsObservable();
|
||||
@@ -49,20 +77,19 @@ namespace mbts.persist {
|
||||
}
|
||||
|
||||
public async Task save_default(UserSettings value) {
|
||||
_set_and_notify(value);
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
lock (_default_settings_lock) {
|
||||
_default_settings_subject.OnNext(_default_settings = value);
|
||||
}
|
||||
await _persist_async();
|
||||
}
|
||||
|
||||
async Task _persist_async() {
|
||||
try {
|
||||
await _persist.set(NAME_SPACE, DEFAULT_KEY, value);
|
||||
await _persist.set(NAME_SPACE, DEFAULT_KEY, _default_settings);
|
||||
} catch(Exception ex) {
|
||||
Log.Persist.warn($"Error persisting {typeof(UserSettings)} in namespace '{NAME_SPACE}': {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
void _set_and_notify(UserSettings value) {
|
||||
lock (_default_settings_lock) {
|
||||
_default_settings = value;
|
||||
// notify observers before persisting so the changes are reflected immediately in the ui
|
||||
_default_settings_subject.OnNext(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,8 @@ namespace bts.utils {
|
||||
|
||||
static class MutableFactoryStore {
|
||||
[ThreadStatic]
|
||||
static readonly Dictionary<Type, Func<IMessage>> _factories = new Dictionary<Type, Func<IMessage>>();
|
||||
static Dictionary<Type, Func<IMessage>> _factories_backing;
|
||||
static Dictionary<Type, Func<IMessage>> _factories => _factories_backing ?? (_factories_backing = new Dictionary<Type, Func<IMessage>>());
|
||||
|
||||
public static Func<IMessage> get(Type type) =>
|
||||
_factories.GetOrAdd(type,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.ui.utility {
|
||||
@@ -22,6 +23,35 @@ namespace mbts.ui.utility {
|
||||
: base(items, item => item, select_default, comparer) { }
|
||||
}
|
||||
|
||||
class SelectionModelInlinePropRelay<TItem, TValue, TKey> where TKey : IEquatable<TKey> {
|
||||
readonly SelectionModel<TItem, TValue> _model;
|
||||
readonly Func<TItem, TKey> _item_key;
|
||||
IDisposable _select_inline_prop;
|
||||
|
||||
public SelectionModelInlinePropRelay(SelectionModel<TItem, TValue> model, Func<TItem, TKey> item_key) {
|
||||
_model = model;
|
||||
_item_key = item_key;
|
||||
}
|
||||
|
||||
public void on_inline_prop(InlineUserProperty<TKey> inline_prop) {
|
||||
Disposable.dispose(ref _select_inline_prop);
|
||||
if (inline_prop?.has_value ?? false) {
|
||||
if (!_try_convert_inline_prop() && _model.items is INotifyCollectionChanged ncc) {
|
||||
_select_inline_prop = ncc.Subscribe((s, e) => {
|
||||
if (_try_convert_inline_prop())
|
||||
Disposable.dispose(ref _select_inline_prop);
|
||||
});
|
||||
}
|
||||
bool _try_convert_inline_prop() {
|
||||
if (_model.items.TryGetFirst(i => _item_key(i).Equals(inline_prop.value), out var item)) {
|
||||
_model.on_inline_property(new InlineUserProperty<TItem>(item, inline_prop.force_override));
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionModel<TItem, TValue> {
|
||||
readonly Func<TValue> _select_default;
|
||||
readonly IEqualityComparer<TValue> _value_comparer;
|
||||
|
||||
@@ -592,13 +592,14 @@ namespace mbts.ui.shell {
|
||||
public abstract class DisposableViewModelBase : ViewModelBaseWithValidation, IPropertyChangedDemuxer, IDisposable, IWillLetYouKnowWhenIDispose
|
||||
{
|
||||
protected IDisposable _dispose = null;
|
||||
private List<IDisposable> _observable_properties;
|
||||
List<IDisposable> _observable_properties_backing;
|
||||
List<IDisposable> _observable_properties => _observable_properties_backing ?? (_observable_properties_backing = new List<IDisposable>());
|
||||
PropertyChangedDemuxer _property_changed_demuxer_backing;
|
||||
|
||||
IPropertyChangedDemuxer _property_changed_demuxer =>
|
||||
_property_changed_demuxer_backing ?? (_property_changed_demuxer_backing = new PropertyChangedDemuxer(this));
|
||||
|
||||
IObservable<T> IPropertyChangedDemuxer.subscribe<T>(string property) => (_property_changed_demuxer_backing ?? (_property_changed_demuxer_backing = new PropertyChangedDemuxer(this))).subscribe<T>(property);
|
||||
IObservable<T> IPropertyChangedDemuxer.subscribe<T>(string property) => _property_changed_demuxer.subscribe<T>(property);
|
||||
IObservable<object> IObservableDemuxer<string, PropertyChangedEventArgs, object>.subscribe(string property) => _property_changed_demuxer.subscribe(property);
|
||||
void IObserver<PropertyChangedEventArgs>.OnNext(PropertyChangedEventArgs value) => _property_changed_demuxer.OnNext(value);
|
||||
void IObserver<PropertyChangedEventArgs>.OnError(Exception error) => _property_changed_demuxer.OnError(error);
|
||||
@@ -607,7 +608,6 @@ namespace mbts.ui.shell {
|
||||
internal IPropertyChangedDemuxer as_demuxer() => this;
|
||||
|
||||
protected Property<T> backing<T>(string name, IObservable<T> source, T default_value = default(T)) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new ObservablePropertySink<T>(this, name, default_value);
|
||||
_observable_properties.Add(source.Subscribe(prop));
|
||||
return prop;
|
||||
@@ -618,7 +618,6 @@ namespace mbts.ui.shell {
|
||||
/// <para/>Note: UI settings in particular may find this useful, as brushes and other Freezable types are not inherently thread safe.
|
||||
/// </summary>
|
||||
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new ObservablePropertySink<T>(this, name, default_value);
|
||||
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
return prop;
|
||||
@@ -685,7 +684,6 @@ namespace mbts.ui.shell {
|
||||
}
|
||||
|
||||
protected void attach<T, V>(SelectorProperty<T, V> selector_property, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
_observable_properties.Add(items.OnDispatcher().Subscribe(selector_property));
|
||||
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(selector_property));
|
||||
}
|
||||
@@ -722,7 +720,7 @@ namespace mbts.ui.shell {
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
Disposable.dispose(_observable_properties);
|
||||
Disposable.dispose(_observable_properties_backing);
|
||||
Disposable.dispose(ref _property_changed_demuxer_backing);
|
||||
Dispose(true);
|
||||
OnDisposed?.Invoke(true);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace bts {
|
||||
struct AccountTuple : IEquatable<AccountTuple>, IComparable<AccountTuple>, IComparable {
|
||||
public static readonly Func<AccountTuple, uint> key_fn = a => a.id;
|
||||
public static readonly Func<AccountTuple?, uint> nullable_key_fn = a => a.HasValue ? a.Value.id : 0u;
|
||||
|
||||
public readonly uint id;
|
||||
public readonly string name;
|
||||
|
||||
@@ -36,6 +36,13 @@
|
||||
<SolidColorBrush x:Key="TextBox.Static.Background.Brush" Color="{StaticResource TextBox.Static.Background.Color}"/>
|
||||
<SolidColorBrush x:Key="TextBox.MouseOver.Border.Brush" Color="{StaticResource TextBox.MouseOver.Border.Color}"/>
|
||||
|
||||
<!--Menu-->
|
||||
<Color x:Key="MenuItem.MouseOver.Background.Color">#3D26A0DA</Color>
|
||||
<Color x:Key="MenuItem.MouseOver.Border.Color">#FF26A0DA</Color>
|
||||
<Color x:Key="Menu.Background.Color">#FFF5F5F5</Color>
|
||||
<Color x:Key="Menu.DarkBackground.Color">#FFF1F1F1</Color>
|
||||
<Color x:Key="Menu.Border.Color">#FFE2E3E3</Color>
|
||||
|
||||
<!--CheckBox-->
|
||||
<Color x:Key="CheckBox.Static.ForegroundColor">#FF212121</Color>
|
||||
<Color x:Key="CheckBox.Static.BackgroundColor">#FFFFFFFF</Color>
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace mbts.ui.controls {
|
||||
public double width { get { return column.ActualWidth; } }
|
||||
public double right { get { return unclipped_left + width; } }
|
||||
public bool is_partially_obscured { get { return unclipped_left < left; } }
|
||||
public double visible_width => width - left + unclipped_left;
|
||||
public override string ToString() {
|
||||
return "{3} {0:0.0},{1:0.0} w{2:0.0}".fmt(left, right, width, column.Header);
|
||||
}
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
using bts.shell;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row>, IKeyedColumn<TimeAndSalesView.Columns> {
|
||||
readonly Func<TimeAndSalesVM.Row, Brush> _get_brush;
|
||||
|
||||
public TimeAndSalesColumn(TimeAndSalesView.Columns key, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null)
|
||||
: base(r => r[key], filter, filter_template) {
|
||||
this.key = key;
|
||||
// id is used for settings (another case of ProtoBufs vs Generics - strings seemed like the easiest way to provide an identifier for the common GridColumnSettings proto)
|
||||
id = TimeAndSalesWindowFactory.col_id(key);
|
||||
_get_brush = get_brush;
|
||||
}
|
||||
|
||||
public TimeAndSalesView.Columns key { get; }
|
||||
|
||||
public override void draw(TimeAndSalesVM.Row row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc
|
||||
, glyph_context
|
||||
, font_size
|
||||
, _get_text(row)
|
||||
, position
|
||||
, row_height
|
||||
, font_brush
|
||||
, _get_brush(row)
|
||||
, horizontal_alignment
|
||||
, vertical_alignment
|
||||
, padding);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,17 @@ namespace mbts.ui.controls {
|
||||
|
||||
public class RenderTextColumn<TRowVM> : RenderColumnBase<TRowVM>, IRenderColumn<TRowVM>, IFilterableColumn {
|
||||
protected readonly Func<TRowVM, string> _get_text;
|
||||
protected readonly Func<TRowVM, Brush> _get_background_brush;
|
||||
|
||||
public RenderTextColumn(
|
||||
Func<TRowVM, string> get_text,
|
||||
IDataGridColumnFilter filter = null,
|
||||
string filter_template = null)
|
||||
string filter_template = null,
|
||||
Func<TRowVM, Brush> get_brush = null)
|
||||
: base() {
|
||||
_get_text = get_text;
|
||||
Func<TRowVM, Brush> get_default_background_brush = (TRowVM _) => this.background;
|
||||
_get_background_brush = get_brush ?? get_default_background_brush;
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
font_params_changed += refresh_glyph_context;
|
||||
@@ -46,13 +50,16 @@ namespace mbts.ui.controls {
|
||||
public override void draw(TRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
dc, glyph_context, font_size, _get_text(row), position, row_height,
|
||||
font_brush, background, horizontal_alignment, vertical_alignment, padding);
|
||||
font_brush, _get_background_brush(row), horizontal_alignment, vertical_alignment, padding);
|
||||
}
|
||||
|
||||
public class RenderTextColumn<TRowVM, TColKey> : RenderTextColumn<TRowVM>, IKeyedColumn<TColKey> {
|
||||
public RenderTextColumn(TColKey key, Func<TRowVM, string> get_text, IDataGridColumnFilter filter, string filter_template)
|
||||
: base(get_text, filter, filter_template) => this.key = key;
|
||||
|
||||
public RenderTextColumn(TColKey key, Func<TRowVM, string> get_text, IDataGridColumnFilter filter, string filter_template, Func<TRowVM, Brush> get_brush)
|
||||
: base(get_text, filter, filter_template, get_brush) => this.key = key;
|
||||
|
||||
public TColKey key { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,13 @@ namespace mbts.ui.controls {
|
||||
void on_row_height_changed(double v) => _row_height = v;
|
||||
}
|
||||
|
||||
public new void SetSelectedItems(IEnumerable items) {
|
||||
base.SetSelectedItems(items);
|
||||
}
|
||||
|
||||
public new void UnselectAll() {
|
||||
base.UnselectAll();
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
|
||||
var pt = e.GetPosition(this);
|
||||
@@ -877,13 +884,13 @@ namespace mbts.ui.controls {
|
||||
#endregion
|
||||
|
||||
class DataContextHooks : IDisposable {
|
||||
List<IDisposable> _hooks = new List<IDisposable>(3);
|
||||
List<IDisposable> _hooks = new List<IDisposable>(4);
|
||||
|
||||
// this constructor provides a way to avoid repetative dp lookups ala owner.DataContext
|
||||
public DataContextHooks(DIYGrid owner, object data_context)
|
||||
: this(owner, data_context as IHandleRowSelection, data_context as IHandleGridSorting, data_context as IWillLetYouKnowWhenIDispose) { }
|
||||
: this(owner, data_context as IHandleRowSelection, data_context as IHandleGridSorting, data_context as IWillLetYouKnowWhenIDispose, data_context as IRequestRowSelection) { }
|
||||
|
||||
public DataContextHooks(DIYGrid owner, IHandleRowSelection selection_handler, IHandleGridSorting sorting_handler, IWillLetYouKnowWhenIDispose dispose_broadcaster) {
|
||||
public DataContextHooks(DIYGrid owner, IHandleRowSelection selection_handler, IHandleGridSorting sorting_handler, IWillLetYouKnowWhenIDispose dispose_broadcaster, IRequestRowSelection selection_request_handler) {
|
||||
if (selection_handler != null && owner._row_presenter is ListBox rp) {
|
||||
rp.SelectionChanged += on_selection_changed;
|
||||
_hooks.Add(Disposable.Create(() => rp.SelectionChanged -= on_selection_changed));
|
||||
@@ -898,6 +905,17 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
if (dispose_broadcaster != null)
|
||||
_hooks.Add(dispose_broadcaster.SubscribeDisposed(this));
|
||||
if (selection_request_handler != null && owner._row_presenter is DIYGridRowsPresenter diy_rp) {
|
||||
selection_request_handler.request_selection += selection_requested;
|
||||
_hooks.Add(Disposable.Create(() => selection_request_handler.request_selection -= selection_requested));
|
||||
void selection_requested((IEnumerable items, bool is_selected) items_and_selectionstate) {
|
||||
if (items_and_selectionstate.is_selected) {
|
||||
diy_rp.SetSelectedItems(items_and_selectionstate.items);
|
||||
} else {
|
||||
diy_rp.UnselectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace mbts.ui.controls {
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
if (cached_background != null) {
|
||||
dc.DrawRectangle(cached_background, null, new Rect(0, 0, ActualWidth, ActualHeight));
|
||||
dc.DrawRectangle(cached_background, null, new Rect(0, 0, _column_layout.Sum(col => col.visible_width), ActualHeight));
|
||||
}
|
||||
if (_row != null) {
|
||||
foreach (var cell in _column_layout) {
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
SelectedCriteria="{Binding value}"
|
||||
SecurityMap="{Binding Path='[security_map]', UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
|
||||
Header="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}}"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.SimpleGridSettingsDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:orderbook="clr-namespace:bts.shell.orderbook"
|
||||
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"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Style="{StaticResource BTSDialog}"
|
||||
>
|
||||
<Window.Resources>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="ControlLongWidth">240</sys:Double>
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="DefaultTabVisibilityConverter" VisibilityForTrue="Visible" VisibilityForFalse="Hidden"/>
|
||||
<cv:BooleanToVisibilityConverter x:Key="CustomTabVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="TabHeaderFontWeightConverter" Default="{u:FontWeight Normal}">
|
||||
<cv:KeyValue Key="{u:True}" Value="{u:FontWeight Bold}"/>
|
||||
</cv:ValueMap>
|
||||
<!--DataTemplates-->
|
||||
<DataTemplate x:Key="GridSectionCoreContentTemplate">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Font</TextBlock>
|
||||
<Grid Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}">
|
||||
<c:PopupControl HorizontalAlignment="Stretch" Header="{Binding}" Background="{StaticResource ComboBox.Static.Background}">
|
||||
<c:PopupControl.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}" Margin="4,0" Text="{Binding font_display_name}" />
|
||||
</DataTemplate>
|
||||
</c:PopupControl.HeaderTemplate>
|
||||
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
|
||||
</c:PopupControl>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="2">Font Color (default)</TextBlock>
|
||||
<c:ColorPickerPopupWithOpacitySlider Grid.Row="2" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding font_color}" CanUserEditOpacity="False"/>
|
||||
<TextBlock Grid.Row="4">Selection Highlight</TextBlock>
|
||||
<c:ColorPickerPopupWithOpacitySlider Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding selection_highlight}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<!--StyleSelectors-->
|
||||
<u:KeyedStyleSelector x:Key="LeftButtonStyleSelector">
|
||||
<u:StyleSelectorKeyValue Key="{x:Static usp:SimpleGridSettingsDialog.ResetToFactoryKey}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
|
||||
<Setter Property="Width" Value="94"/>
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
<u:StyleSelectorKeyValue Key="{x:Static usp:SimpleGridSettingsDialog.CopyFromOtherKey}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
|
||||
<Setter Property="Width" Value="108"/>
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
</u:KeyedStyleSelector>
|
||||
<u:KeyedTemplateSelector x:Key="GridSettingsTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.SimpleGrid}">
|
||||
<DataTemplate>
|
||||
<usp:GridSettingsView Margin="8,-8,8,8" GridSectionContent="{Binding}" GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.Ladder}">
|
||||
<DataTemplate>
|
||||
<usp:LadderSettingsView Margin="0,4,0,0"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.OrderBook}">
|
||||
<DataTemplate>
|
||||
<usp:GridSettingsView Margin="8,-8,8,8" DataContext="{Binding core_settings_vm}" GridSectionContent="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:SimpleGridSettingsDialog}}">
|
||||
<usp:GridSettingsView.GridSectionContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<ContentControl Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
|
||||
<CheckBox Content="Show Full Algo Tree">
|
||||
<CheckBox.Margin>
|
||||
<Thickness Top="{StaticResource VerticalPush1}"/>
|
||||
</CheckBox.Margin>
|
||||
<CheckBox.IsChecked>
|
||||
<Binding Path="(orderbook:OrderBookSettingsVM.algo_tree_display_mode)">
|
||||
<Binding.Converter>
|
||||
<cv:ReverseConverter>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{x:Static orderbook:AlgoTreeDisplayMode.RootAndLeafNodes}" ValueForTrue="{x:Static orderbook:AlgoTreeDisplayMode.FullTree}"/>
|
||||
</cv:ReverseConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</CheckBox.IsChecked>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</usp:GridSettingsView.GridSectionContentTemplate>
|
||||
</usp:GridSettingsView>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:ActionMenu HorizontalAlignment="Right" ItemsSource="{Binding action_menu_items}"/>
|
||||
<ContentControl
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Top"
|
||||
Content="{Binding instance_vm}"
|
||||
ContentTemplateSelector="{StaticResource GridSettingsTemplateSelector}"
|
||||
/>
|
||||
<c:DialogSkirt
|
||||
Grid.Row="2"
|
||||
BorderThickness="1"
|
||||
BorderBrush="DarkGray"
|
||||
LeftItems="{Binding left_skirt_items}"
|
||||
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
|
||||
RightItems="{Binding right_skirt_items}"
|
||||
>
|
||||
<c:DialogSkirt.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Color="Transparent"/>
|
||||
<GradientStop Color="{x:Static u:BTSColors.dialog_skirt}" Offset="1"/>
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</c:DialogSkirt.Background>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for SimpleGridSettingsDialog.xaml
|
||||
/// </summary>
|
||||
public partial class SimpleGridSettingsDialog : Window {
|
||||
public SimpleGridSettingsDialog() =>
|
||||
InitializeComponent();
|
||||
|
||||
public const string ResetToFactoryKey = "Reset", CopyFromOtherKey = "Copy";
|
||||
}
|
||||
|
||||
static class GridSettingsTemplateKeys {
|
||||
public const string
|
||||
SimpleGrid = nameof(SimpleGrid),
|
||||
Ladder = nameof(Ladder),
|
||||
OrderBook = nameof(OrderBook);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
interface IRequestRowSelection {
|
||||
event Action<(IEnumerable, bool)> request_selection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.controls.OKCancelDialog"
|
||||
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"
|
||||
Padding="0"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Style="{StaticResource BTSDialog}"
|
||||
>
|
||||
<Window.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Loaded="_content_root_Loaded">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button u:Behavior.CloseWindow="True" IsDefault="True" Command="{Binding OKCommand, RelativeSource={RelativeSource AncestorType=c:OKCancelDialog}}" Style="{StaticResource CommonButton}">
|
||||
OK
|
||||
</Button>
|
||||
<Button u:Behavior.CloseWindow="True" IsCancel="True" Command="{Binding CancelCommand, RelativeSource={RelativeSource AncestorType=c:OKCancelDialog}}" Style="{StaticResource CommonButton}">
|
||||
Cancel
|
||||
</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Window.ContentTemplate>
|
||||
</Window>
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for TextBoxDialog.xaml
|
||||
/// </summary>
|
||||
partial class OKCancelDialog : Window {
|
||||
UIElement _core_content_view;
|
||||
Panel _content_root;
|
||||
|
||||
public OKCancelDialog() =>
|
||||
InitializeComponent();
|
||||
|
||||
#region CoreContent
|
||||
public static readonly DependencyProperty CoreContentProperty =
|
||||
WPFHelper.create_dp<OKCancelDialog>(
|
||||
nameof(CoreContent),
|
||||
(o, v) => o._try_load_content());
|
||||
|
||||
public object CoreContent {
|
||||
get => GetValue(CoreContentProperty);
|
||||
set => SetValue(CoreContentProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OKCommand
|
||||
public static readonly DependencyProperty OKCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, OKCancelDialog>(nameof(OKCommand));
|
||||
|
||||
public ICommand OKCommand {
|
||||
get => GetValue(OKCommandProperty) as ICommand;
|
||||
set => SetValue(OKCommandProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CancelCommand
|
||||
public static readonly DependencyProperty CancelCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, OKCancelDialog>(nameof(CancelCommand));
|
||||
|
||||
public ICommand CancelCommand {
|
||||
get => GetValue(CancelCommandProperty) as ICommand;
|
||||
set => SetValue(CancelCommandProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void _content_root_Loaded(object sender, RoutedEventArgs e) {
|
||||
if (_content_root == null) {
|
||||
_content_root = sender as Panel;
|
||||
_try_load_content();
|
||||
}
|
||||
}
|
||||
|
||||
void _try_load_content() {
|
||||
if (_content_root == null)
|
||||
return;
|
||||
try {
|
||||
if (_core_content_view != null)
|
||||
_content_root.Children.Remove(_core_content_view);
|
||||
var c = CoreContent;
|
||||
_content_root.Children.Add(_core_content_view = c as UIElement ?? new ContentControl { Content = c });
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ie="http://schemas.microsoft.com/expression/2010/interactions"
|
||||
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
|
||||
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"
|
||||
>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="ControlLongWidth">240</sys:Double>
|
||||
<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="MouseOverBorderLightColor">#80DADADA</Color>
|
||||
<Color x:Key="MouseOverBorderMediumColor">#90006CD9</Color>
|
||||
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
|
||||
<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
|
||||
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="DefaultTabVisibilityConverter" VisibilityForTrue="Visible" VisibilityForFalse="Hidden"/>
|
||||
<cv:BooleanToVisibilityConverter x:Key="CustomTabVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:CastConverter x:Key="CastConverter"/>
|
||||
|
||||
<Style x:Key="SparkToolBarItem" TargetType="Control">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Width" Value="22"/>
|
||||
<Setter Property="Height" Value="22"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="{StaticResource MouseOverBorderLightColor}"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<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:MouseStateDefinition MouseSource="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Border.TemplatedParent)', RelativeSource={RelativeSource Self}, Converter={StaticResource CastConverter}}"/>
|
||||
</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="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<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="Pressed">
|
||||
<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>
|
||||
</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>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SparkToolBar" TargetType="ToolBar">
|
||||
<Setter Property="ToolBarTray.IsLocked" Value="True"/>
|
||||
<Setter Property="Background" Value="LightGray"/>
|
||||
<Setter Property="BorderBrush" Value="DarkGray"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource SparkToolBarItem}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToolBar}">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
>
|
||||
<DockPanel>
|
||||
<ToggleButton
|
||||
x:Name="OverflowToggle"
|
||||
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="{x:Static u:BTSColors.common_background_brush}"
|
||||
>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="MouseLeave">
|
||||
<ie:ChangePropertyAction TargetName="OverflowPopup" PropertyName="IsOpen" Value="False"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<ToolBarOverflowPanel x:Name="PART_ToolBarOverflowPanel"/>
|
||||
</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="HasOverflowItems" Value="False">
|
||||
<Setter TargetName="OverflowToggle" Property="Visibility" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<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>
|
||||
</ResourceDictionary>
|
||||
@@ -20,6 +20,18 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="ComboBox.DelineatedGroupContainerStyle.DynamicFill" TargetType="GroupItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<StackPanel>
|
||||
<ItemsPresenter/>
|
||||
<Rectangle Height="1" Fill="{DynamicResource SeparatorFill}" HorizontalAlignment="Stretch"/>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="DisabledPlaceholderComboBox" TargetType="ComboBox">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
<cv:NumericSignBrushConverter x:Key="PositionSignBrushConverter" Zero="{StaticResource IntZero}"/>
|
||||
<cv:NumericSignBrushConverter x:Key="DayChangeSignBrushConverter" Zero="{StaticResource DoubleZero}"/>
|
||||
<cv:QuantityMeasureCaseDisplayTextConverter x:Key="QuantityMeasureCaseDisplayConverter"/>
|
||||
<cv:ValueMap x:Key="BoolToAffirmativeNameConverter" Default="No">
|
||||
<cv:KeyValue Key="{u:True}" Value="Yes"/>
|
||||
<cv:ValueMap x:Key="BoolToAffirmativeNameConverter" Default="No">
|
||||
<cv:KeyValue Key="{u:True}" Value="Yes"/>
|
||||
</cv:ValueMap>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,448 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.ShellSettingsDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ice="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
|
||||
xmlns:orderbook="clr-namespace:bts.shell.orderbook"
|
||||
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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Style="{StaticResource BTSWindow}"
|
||||
UseLayoutRounding="True"
|
||||
>
|
||||
<Window.Resources>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="ControlLongWidth">240</sys:Double>
|
||||
<Color x:Key="HeaderBackgroundColor">LightGray</Color>
|
||||
<Color x:Key="HeaderBackgroundBorderColor">DarkGray</Color>
|
||||
<SolidColorBrush x:Key="HeaderBackgroundBrush" ice:Freeze="True" Color="{StaticResource HeaderBackgroundColor}"/>
|
||||
<SolidColorBrush x:Key="HeaderBackgroundBorderBrush" ice:Freeze="True" Color="{StaticResource HeaderBackgroundBorderColor}"/>
|
||||
<!--Converters-->
|
||||
<cv:BooleanToVisibilityConverter x:Key="DefaultTabVisibilityConverter" VisibilityForTrue="Visible" VisibilityForFalse="Hidden"/>
|
||||
<cv:BooleanToVisibilityConverter x:Key="CustomTabVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<cv:ValueMap x:Key="TabHeaderFontWeightConverter" Default="{u:FontWeight Normal}">
|
||||
<cv:KeyValue Key="{u:True}" Value="{u:FontWeight Bold}"/>
|
||||
</cv:ValueMap>
|
||||
<!--DataTemplates-->
|
||||
<DataTemplate x:Key="GridSectionCoreContentTemplate">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="name_col"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Font</TextBlock>
|
||||
<Grid Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}">
|
||||
<c:PopupControl HorizontalAlignment="Stretch" Header="{Binding}" Background="{StaticResource ComboBox.Static.Background}">
|
||||
<c:PopupControl.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock FontFamily="{Binding font_family}" FontSize="{Binding font_size}" FontWeight="{Binding font_weight}" Margin="4,0" Text="{Binding font_display_name}" />
|
||||
</DataTemplate>
|
||||
</c:PopupControl.HeaderTemplate>
|
||||
<c:FontPicker SelectedFontFamily="{Binding font_family}" SelectedFontSize="{Binding font_size}" SelectedFontWeight="{Binding font_weight}"/>
|
||||
</c:PopupControl>
|
||||
</Grid>
|
||||
<TextBlock Grid.Row="2">Font Color (default)</TextBlock>
|
||||
<c:ColorPickerPopupWithOpacitySlider Grid.Row="2" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding font_color}" CanUserEditOpacity="False"/>
|
||||
<TextBlock Grid.Row="4">Selection Highlight</TextBlock>
|
||||
<c:ColorPickerPopupWithOpacitySlider Grid.Row="4" Grid.Column="2" HorizontalAlignment="Left" Width="{StaticResource ControlLongWidth}" DataContext="{Binding selection_highlight}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="CopyProfileTargetButton" TargetType="Button">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Height" Value="22"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Root"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="1"
|
||||
Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target)', RelativeSource={RelativeSource Self}, Converter={StaticResource CastConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)" Duration="0" To="{StaticResource MenuItem.MouseOver.Background.Color}"/>
|
||||
<ColorAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Color)" Duration="0" To="{StaticResource MenuItem.MouseOver.Border.Color}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)" Duration="0" To="{StaticResource MenuItem.MouseOver.Background.Color}"/>
|
||||
<ColorAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="(Control.BorderBrush).(SolidColorBrush.Color)" Duration="0" To="{StaticResource MenuItem.MouseOver.Border.Color}"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="ProfileItemTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock x:Name="name_view" FontWeight="Normal" Padding="4,1" Text="{Binding name}"/>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="name_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Value="True">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{x:Static cv:ValueConverters.AreEqualConverter}">
|
||||
<Binding Path=""/>
|
||||
<Binding Path="DataContext.selected_profile" RelativeSource="{RelativeSource AncestorType=ComboBox}"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<Setter TargetName="name_view" Property="FontWeight" Value="{x:Static FontWeights.Bold}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:ShellSettingsDialog.LocalProfileItemTemplateKey}">
|
||||
<DataTemplate>
|
||||
<TextBlock x:Name="name_view" FontWeight="Normal" FontStyle="Italic" Text="(Custom)"/>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="name_view" Property="Text" Value="(Custom)*"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Value="True">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{x:Static cv:ValueConverters.AreEqualConverter}">
|
||||
<Binding Path=""/>
|
||||
<Binding Path="DataContext.selected_profile" RelativeSource="{RelativeSource AncestorType=ComboBox}"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<Setter TargetName="name_view" Property="FontWeight" Value="{x:Static FontWeights.Bold}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
|
||||
<u:KeyedStyleSelector x:Key="LeftButtonStyleSelector" Default="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
|
||||
<u:StyleSelectorKeyValue Key="{x:Static usp:ShellSettingsDialog.ResetToFactoryKey}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
|
||||
<Setter Property="Width" Value="94"/>
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
<u:StyleSelectorKeyValue Key="{x:Static usp:ShellSettingsDialog.CopyFromOtherKey}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DialogSkirtButtonStyle}">
|
||||
<Setter Property="Width" Value="108"/>
|
||||
</Style>
|
||||
</u:StyleSelectorKeyValue>
|
||||
</u:KeyedStyleSelector>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="GridSettingsTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.SimpleGrid}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{StaticResource HeaderBackgroundBorderBrush}"/>
|
||||
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" ItemsSource="{Binding instance_vm.action_menu_items}"/>
|
||||
<usp:GridSettingsView Grid.Row="1" Margin="8,-8,8,8" GridSectionContent="{Binding}" GridSectionContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.Ladder}">
|
||||
<DataTemplate>
|
||||
<Grid DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
|
||||
<Grid.Resources>
|
||||
<CollectionViewSource x:Key="ProfilesViewSource" Source="{Binding profiles}">
|
||||
<CollectionViewSource.GroupDescriptions>
|
||||
<PropertyGroupDescription PropertyName="is_global"/>
|
||||
</CollectionViewSource.GroupDescriptions>
|
||||
<CollectionViewSource.SortDescriptions>
|
||||
<scm:SortDescription PropertyName="is_global"/>
|
||||
<scm:SortDescription PropertyName="name"/>
|
||||
<scm:SortDescription PropertyName="id"/>
|
||||
</CollectionViewSource.SortDescriptions>
|
||||
</CollectionViewSource>
|
||||
<SolidColorBrush x:Key="SeparatorFill" Color="Black"/>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<!--Padding.Left-->
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource HeaderBackgroundBrush}" Stroke="{StaticResource HeaderBackgroundBorderBrush}" StrokeThickness="1"/>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center">Profile:</TextBlock>
|
||||
<ComboBox Grid.Column="2" Margin="8,4" ItemsSource="{Binding Source={StaticResource ProfilesViewSource}}" SelectedItem="{Binding selected_profile}" ItemTemplateSelector="{StaticResource ProfileItemTemplateSelector}">
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle.DynamicFill}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
</ComboBox>
|
||||
<ToolBar Grid.Column="3" Background="Transparent" BorderThickness="0" Style="{StaticResource SparkToolBar}">
|
||||
<u:StateManager.VisualState>
|
||||
<Binding Path="selected_profile.is_global">
|
||||
<Binding.Converter>
|
||||
<cv:BooleanToObjectConverter ValueForTrue="Global" ValueForFalse="Local"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</u:StateManager.VisualState>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="SelectedProfileTypeStates">
|
||||
<VisualState Name="Local">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="save_profile" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Save Profile (cannot save 'Custom')"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="rename_profile" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Rename Profile (cannot rename 'Custom')"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="delete_profile" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Delete Profile (cannot delete 'Custom')"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="security_picker" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Select securities to use profile's settings by default (not applicable to 'Custom')"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Global"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<MenuItem Command="{Binding add_profile_cmd}" ToolTip="New Profile">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{x:Static u:Icons.AddBoxes}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
|
||||
<MenuItem x:Name="save_profile" Command="{Binding save_profile_cmd}" CommandParameter="{Binding selected_profile}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{x:Static u:Icons.Disk}" HorizontalAlignment="Left" Margin="-1,0,0,0"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding selected_profile.name, StringFormat='Save \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem Command="{Binding save_all_cmd}" ToolTip="Save All Profiles">
|
||||
<MenuItem.Icon>
|
||||
<Grid>
|
||||
<Image Source="{x:Static u:Icons.Disk}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Height="10"/>
|
||||
<Image Source="{x:Static u:Icons.Disk}" HorizontalAlignment="Right" VerticalAlignment="Top" Height="10"/>
|
||||
</Grid>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="rename_profile" Width="10" Command="{Binding save_profile_as_cmd}" CommandParameter="{Binding selected_profile}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{x:Static u:Icons.Pencil}" HorizontalAlignment="Left" Margin="-1,0,0,0"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding selected_profile.name, StringFormat='Rename \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="revert_profile" Command="{Binding revert_profile_cmd}" CommandParameter="{Binding selected_profile}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{x:Static u:Icons.UndoCircular}"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding selected_profile.name, StringFormat='Undo Changes To \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
|
||||
<c:PopupControl ShowExpanderArrow="False" IsOpen="{Binding is_copy_menu_open, Mode=TwoWay}">
|
||||
<c:PopupControl.Header>
|
||||
<Image Source="{x:Static u:Icons.CopyWF}"/>
|
||||
</c:PopupControl.Header>
|
||||
<c:PopupControl.ToolTip>
|
||||
<TextBlock Text="{Binding selected_profile.name, StringFormat='Copy \'{0}\' To...'}"/>
|
||||
</c:PopupControl.ToolTip>
|
||||
<Grid>
|
||||
<Grid.Background>
|
||||
<SolidColorBrush Color="{StaticResource Menu.Background.Color}"/>
|
||||
</Grid.Background>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.RowSpan="2" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="28" IsHitTestVisible="False">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{StaticResource Menu.DarkBackground.Color}"/>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<Rectangle Grid.RowSpan="2" HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="1" Margin="28,0,0,0">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{StaticResource Menu.Border.Color}"/>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<Rectangle Grid.Row="1" Height="1" VerticalAlignment="Top" HorizontalAlignment="Stretch" Margin="28,0,0,0">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{StaticResource Menu.Border.Color}"/>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<ItemsControl ItemsSource="{Binding profiles}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding name}"
|
||||
Command="{Binding DataContext.copy_profile_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Focusable="False"
|
||||
Padding="34,2"
|
||||
Style="{StaticResource CopyProfileTargetButton}"
|
||||
>
|
||||
<Button.Visibility>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe>
|
||||
<cv:AreEqualConverter/>
|
||||
<cv:BooleanToVisibilityConverter VisibilityForFalse="Visible" VisibilityForTrue="Collapsed"/>
|
||||
</cv:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="DataContext.selected_profile" RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
|
||||
<Binding Path=""/>
|
||||
</MultiBinding>
|
||||
</Button.Visibility>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button Grid.Row="1" Command="{Binding copy_profile_cmd}" Padding="6,2" Style="{StaticResource CopyProfileTargetButton}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="{x:Static u:Icons.AddBoxes}"/>
|
||||
<TextBlock VerticalAlignment="Center" Margin="14,0,0,0">New Profile</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</c:PopupControl>
|
||||
<c:ContractPickerPopup
|
||||
x:Name="security_picker"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
IsEnabled="{Binding selected_profile.is_global}"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource {x:Static usp:ShellSettingsDialog.SecurityMapKey}}"
|
||||
SelectedCriteria="{Binding selected_profile.security_criteria}"
|
||||
Style="{StaticResource SparkToolBarItem}"
|
||||
>
|
||||
<c:ContractPickerPopup.Header>
|
||||
<Image Source="{x:Static u:Icons.HandPointingOverDocument}"/>
|
||||
</c:ContractPickerPopup.Header>
|
||||
<c:ContractPickerPopup.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding selected_profile.name, StringFormat='Select securities to use \'{0}\' settings by default'}"/>
|
||||
<LineBreak/>
|
||||
<Italic>Hint: this will not apply to existing ladders.</Italic>
|
||||
</TextBlock>
|
||||
</c:ContractPickerPopup.ToolTip>
|
||||
</c:ContractPickerPopup>
|
||||
<MenuItem x:Name="delete_profile" Command="{Binding delete_profile_cmd}" CommandParameter="{Binding selected_profile}">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="{x:Static u:Icons.Trash}"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding selected_profile.name, StringFormat='Delete \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<Separator VerticalAlignment="Stretch" Width="1" Margin="2,2,1,2" Background="Gray"/>
|
||||
<c:ActionMenu Grid.Column="4" HorizontalAlignment="Right" Margin="0,0,4,0" VerticalAlignment="Center" ItemsSource="{Binding action_menu_items}"/>
|
||||
</ToolBar>
|
||||
<usp:LadderSettingsView Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Margin="0,10,8,0" DataContext="{Binding selected_profile.value}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.OrderBook}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="1" Fill="{StaticResource HeaderBackgroundBorderBrush}"/>
|
||||
<c:ActionMenu HorizontalAlignment="Right" Margin="0,2,0,0" ItemsSource="{Binding instance_vm.action_menu_items}"/>
|
||||
<usp:GridSettingsView Grid.Row="1" Margin="8,-8,8,8" DataContext="{Binding core_settings_vm}" GridSectionContent="{Binding DataContext.instance_vm, RelativeSource={RelativeSource AncestorType=usp:ShellSettingsDialog}}">
|
||||
<usp:GridSettingsView.GridSectionContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Grid.IsSharedSizeScope="True">
|
||||
<ContentControl Content="{Binding core_settings_vm}" ContentTemplate="{StaticResource GridSectionCoreContentTemplate}"/>
|
||||
<CheckBox Content="Show Full Algo Tree">
|
||||
<CheckBox.Margin>
|
||||
<Thickness Top="{StaticResource VerticalPush1}"/>
|
||||
</CheckBox.Margin>
|
||||
<CheckBox.IsChecked>
|
||||
<Binding Path="(orderbook:OrderBookSettingsVM.algo_tree_display_mode)">
|
||||
<Binding.Converter>
|
||||
<cv:ReverseConverter>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{x:Static orderbook:AlgoTreeDisplayMode.RootAndLeafNodes}" ValueForTrue="{x:Static orderbook:AlgoTreeDisplayMode.FullTree}"/>
|
||||
</cv:ReverseConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</CheckBox.IsChecked>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</usp:GridSettingsView.GridSectionContentTemplate>
|
||||
</usp:GridSettingsView>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl
|
||||
VerticalAlignment="Top"
|
||||
Content="{Binding instance_vm}"
|
||||
ContentTemplateSelector="{StaticResource GridSettingsTemplateSelector}"
|
||||
/>
|
||||
<c:DialogSkirt
|
||||
Grid.Row="1"
|
||||
BorderThickness="1"
|
||||
BorderBrush="DarkGray"
|
||||
LeftItems="{Binding left_skirt_items}"
|
||||
LeftItemContainerStyleSelector="{StaticResource LeftButtonStyleSelector}"
|
||||
RightItems="{Binding right_skirt_items}"
|
||||
>
|
||||
<c:DialogSkirt.Background>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<LinearGradientBrush.GradientStops>
|
||||
<GradientStop Color="Transparent"/>
|
||||
<GradientStop Color="{x:Static u:BTSColors.dialog_skirt}" Offset="1"/>
|
||||
</LinearGradientBrush.GradientStops>
|
||||
</LinearGradientBrush>
|
||||
</c:DialogSkirt.Background>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ShellSettingsDialog.xaml
|
||||
/// </summary>
|
||||
public partial class ShellSettingsDialog : Window {
|
||||
public ShellSettingsDialog() =>
|
||||
InitializeComponent();
|
||||
|
||||
public const string
|
||||
ResetToFactoryKey = "Reset",
|
||||
CopyFromOtherKey = "Copy",
|
||||
LocalProfileItemTemplateKey = "LocalProfileItemTemplate",
|
||||
SaveAsStyleKey = "SaveAsStyle",
|
||||
SecurityMapKey = "SecurityMap";
|
||||
}
|
||||
|
||||
static class GridSettingsTemplateKeys {
|
||||
public const string
|
||||
SimpleGrid = nameof(SimpleGrid),
|
||||
Ladder = nameof(Ladder),
|
||||
OrderBook = nameof(OrderBook);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IWindowSettingsVM<T> where T : class {
|
||||
void load(T value);
|
||||
T get_result();
|
||||
}
|
||||
|
||||
class ShellSettingsDialogVM<T, TVM> : ViewModelBase, IRequestViewClose, IDisposable where T : class, IEquatable<T> where TVM : IWindowSettingsVM<T> {
|
||||
readonly Property<bool> _use_default;
|
||||
readonly DialogSkirtItem _ok_item, _apply_item, _cancel_item, _apply_to_defaults_item;
|
||||
readonly DelegateProperty<IEnumerable<DialogSkirtItem>> _left_skirt_items;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>(2); // initialize with 2 capacity bc the only potential disposables are the instance/default vms
|
||||
|
||||
public ShellSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null, bool show_save_as_defaults = true) {
|
||||
if (merge_with_current == null)
|
||||
merge_with_current = s => s;
|
||||
// by default, each new window will use their own instance settings
|
||||
_use_default = backing(nameof(use_default), settings_manager.instance_settings == null);
|
||||
default_vm = vm_factory(settings_manager.default_settings);
|
||||
// instance vm is what will always be visible on the screen
|
||||
instance_vm = vm_factory(settings_manager.current_settings());
|
||||
// items
|
||||
const string import_group = "i", export_group = "e";
|
||||
(string name, string[] extensions) file_filter = ($"{settings_file_ext} settings files", new[] { settings_file_ext });
|
||||
var import_to_custom_cmd = FileDialogCommand.import_proto<T>(s => instance_vm.load(s), type_name: settings_file_ext, filter_items: file_filter);
|
||||
var export_to_custom_cmd = FileDialogCommand.export_proto(() => instance_vm.get_result(), filter_items: file_filter);
|
||||
action_menu_items = new IMenuItemVM[] {
|
||||
new MenuItemVM("Load Settings File ...", import_to_custom_cmd, Icons.Import, import_group),
|
||||
new MenuItemVM("Save Settings File ...", export_to_custom_cmd, Icons.Export, export_group),
|
||||
new MenuItemVM("Load Factory Defaults",
|
||||
new DelegateCommand(() => instance_vm.load(get_factory_settings(UserSettings.factory_defaults))),
|
||||
Icons.Factory
|
||||
)
|
||||
};
|
||||
_left_skirt_items = backing(nameof(left_skirt_items), left_skirt_factory);
|
||||
_apply_item = new DialogSkirtItem("Apply", new DelegateCommand(() => {
|
||||
use_default = false;
|
||||
apply();
|
||||
}));
|
||||
_ok_item = new DialogSkirtItem("OK", new DelegateCommand(() => {
|
||||
use_default = false;
|
||||
apply();
|
||||
close();
|
||||
})) { is_default = true };
|
||||
_cancel_item = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
|
||||
_apply_to_defaults_item = new DialogSkirtItem("Save As Defaults", new DelegateCommand(() => {
|
||||
use_default = true;
|
||||
apply();
|
||||
close();
|
||||
}), ui.spark.LadderSettingsDialog.ResetToFactoryKey);
|
||||
// maybe dispose vms
|
||||
if (instance_vm is IDisposable dispose_ivm)
|
||||
_resources.Add(dispose_ivm);
|
||||
if (default_vm is IDisposable dispose_dvm)
|
||||
_resources.Add(dispose_dvm);
|
||||
// local methods
|
||||
void apply() {
|
||||
var result = merge_with_current(instance_vm.get_result());
|
||||
if (!use_default) {
|
||||
settings_manager.instance_settings = result;
|
||||
} else {
|
||||
settings_manager.instance_settings = null;
|
||||
|
||||
// copy the current instance settings and persist
|
||||
if (!result.safe_equals(settings_manager.default_settings))
|
||||
settings_manager.save_default_settings(result).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void close() => RequestClose?.Invoke(this, new EventArgs());
|
||||
|
||||
IEnumerable<DialogSkirtItem> left_skirt_factory() {
|
||||
if (show_save_as_defaults)
|
||||
yield return _apply_to_defaults_item;
|
||||
};
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public TVM instance_vm { get; }
|
||||
public TVM default_vm { get; }
|
||||
public IReadOnlyCollection<IMenuItemVM> action_menu_items { get; }
|
||||
public IEnumerable<DialogSkirtItem> left_skirt_items => _left_skirt_items.Value;
|
||||
public IEnumerable<DialogSkirtItem> right_skirt_items {
|
||||
get {
|
||||
yield return _apply_item;
|
||||
yield return _ok_item;
|
||||
yield return _cancel_item;
|
||||
}
|
||||
}
|
||||
public bool use_default {
|
||||
get => _use_default.Value;
|
||||
set => _use_default.Value = value;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(_resources);
|
||||
}
|
||||
|
||||
class ProfiledShellSettingsDialogVM<T, TVM, TProfile> : DisposableViewModelBase, IRequestViewClose where T : class, IEquatable<T> where TVM : class, IValueFactory<T>, IObservable<T> where TProfile : ISettingsProfile<T> {
|
||||
readonly IComparer<SettingsProfile<T>> _key_comp = Comparer<uint>.Default.wrap(SettingsProfile<T>.key_fn);
|
||||
readonly Property<ProfileVM> _selected_profile;
|
||||
readonly Func<T, TVM> _vm_factory;
|
||||
readonly ProfileVM _local_profile;
|
||||
bool _is_copy_menu_open;
|
||||
DialogSkirtItem _apply_item, _ok_item, _cancel_item;
|
||||
MenuItemVM _import_item, _export_item, _load_factory_item;
|
||||
|
||||
public ProfiledShellSettingsDialogVM(ISettingsProfileService<T> profile_service, Func<UserSettings, T> get_fallback, Func<T, TVM> vm_factory, Func<T, T> normalize, T init, string file_ext, uint settings_id = 0) {
|
||||
_vm_factory = vm_factory;
|
||||
// profile collections
|
||||
var skip_local = profiles.WrapSkip(1);
|
||||
var skip_local_wrapper = skip_local.WrapSelector(
|
||||
p => new SettingsProfile<T>(p.id, p.name, p.value.create(), p.security_criteria),
|
||||
ns => new ProfileVM(ns.id, vm_factory(ns.value), ns.name, ns.security_criteria));
|
||||
profiles.Add(_local_profile = new ProfileVM(0, vm_factory(init), "Custom", template_key: ShellSettingsDialog.LocalProfileItemTemplateKey));
|
||||
_selected_profile = backing(nameof(selected_profile), _local_profile);
|
||||
// dialog skirt items
|
||||
_apply_item = new DialogSkirtItem("Apply", new DelegateCommand(apply));
|
||||
_ok_item = new DialogSkirtItem("OK", new DelegateCommand(new Action(apply) + close)) { is_default = true };
|
||||
_cancel_item = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
|
||||
// cmds
|
||||
add_profile_cmd = new DelegateCommand(begin_add_profile);
|
||||
copy_profile_cmd = new DelegateCommand<ProfileVM>(copy_profile);
|
||||
revert_profile_cmd = ObservantCommand.create(revert_profile, ProfileVM.subscribe_is_dirty_fn, out var revert_profile_observer);
|
||||
save_profile_cmd = ObservantCommand.create(save_profile, ProfileVM.subscribe_is_global_dirty_fn, out var save_profile_observer);
|
||||
save_profile_as_cmd = ObservantCommand.create(begin_save_profile_as, ProfileVM.is_global_fn, out var save_as_observer);
|
||||
save_all_cmd = ObservantCommand.create(save_all, out var save_all_observer);
|
||||
delete_profile_cmd = ObservantCommand.create(delete, ProfileVM.is_global_fn, out var delete_observer);
|
||||
// toolbar
|
||||
const string import_group = "i", export_group = "e";
|
||||
(string name, string[] extensions) file_filter = ($"{file_ext} settings files", new[] { file_ext });
|
||||
var import_cmd = FileDialogCommand.import_proto<T>(s => selected_profile?.load_value_from(s), type_name: file_ext, filter_items: file_filter);
|
||||
var export_cmd = FileDialogCommand.export_proto(() => instance_vm.create(), filter_items: file_filter);
|
||||
_load_factory_item = new MenuItemVM("Load Factory Defaults", new DelegateCommand(() => selected_profile?.load_value_from(get_fallback(UserSettings.factory_defaults))), Icons.Factory);
|
||||
_import_item = new MenuItemVM("Load Settings File ...", import_cmd, Icons.Import, import_group);
|
||||
_export_item = new MenuItemVM("Save Settings File ...", export_cmd, Icons.Export, export_group);
|
||||
//
|
||||
var is_global_dirty_mon = new ReactiveThresholdMonitor<ProfileVM>(ProfileVM.subscribe_is_global_dirty_fn);
|
||||
_dispose = Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(profiles, p => Disposable.Create(is_global_dirty_mon.join(p), p)),
|
||||
is_global_dirty_mon.Subscribe(save_all_observer),
|
||||
_selected_profile.observe_current(true).Subscribe(v => {
|
||||
revert_profile_observer.OnNext(v);
|
||||
save_profile_observer.OnNext(v);
|
||||
save_as_observer.OnNext(v);
|
||||
delete_observer.OnNext(v);
|
||||
RaisePropertyChanged(nameof(instance_vm));
|
||||
}),
|
||||
profile_service.subscribe_all().OnDispatcher().Subscribe(table => {
|
||||
var prev_selected_name = selected_profile?.name;
|
||||
skip_local_wrapper.RemoveSorted(table.removed, _key_comp);
|
||||
foreach (var ns in table.changed) {
|
||||
int i = CollectionHelper.binary_search(skip_local_wrapper, ns, _key_comp);
|
||||
if (i >= 0)
|
||||
skip_local[i].commit(ns.with(value: normalize(ns.value)));
|
||||
}
|
||||
foreach (var ns in table.added) {
|
||||
int i = CollectionHelper.binary_search(skip_local_wrapper, ns, _key_comp);
|
||||
if (i >= 0)
|
||||
continue;
|
||||
i = ~i;
|
||||
var temp = ns.with(value: normalize(ns.value));
|
||||
skip_local_wrapper.Insert(i, temp);
|
||||
if (settings_id == temp.id) {
|
||||
if (temp.value.Equals(init))
|
||||
selected_profile = skip_local[i];
|
||||
settings_id = 0;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
void apply() {
|
||||
if (selected_profile != null) {
|
||||
var newval = selected_profile.create();
|
||||
if (selected_profile.id != 0)
|
||||
profile_service.add_or_update(newval);
|
||||
selected_profile.commit_current();
|
||||
applied?.Invoke(newval);
|
||||
}
|
||||
}
|
||||
void close() => RequestClose?.Invoke(this, new EventArgs());
|
||||
void delete(ProfileVM target) {
|
||||
if (target.id != 0 && BTSMessageBox.Confirm($"Are you sure you want to delete '{target.name}?'", "Settings Profile"))
|
||||
profile_service.remove(target.id);
|
||||
}
|
||||
void copy_profile(ProfileVM target) {
|
||||
if (selected_profile != null) {
|
||||
if (target == null) {
|
||||
// copy to new
|
||||
var profile = selected_profile.create().with(0, $"{selected_profile.name} - Copy");
|
||||
target = new ProfileVM(profile_service.add_or_update(profile), _vm_factory(profile.value), profile.name);
|
||||
}
|
||||
target.load_value_from(selected_profile.create().value);
|
||||
is_copy_menu_open = false;
|
||||
selected_profile = target;
|
||||
}
|
||||
}
|
||||
// cmd impl
|
||||
void begin_add_profile() => choose_name_dialog("New Profile", selected_profile, (t, n) => save_profile_as(null, n)).Show();
|
||||
void begin_save_profile_as(ProfileVM target) => choose_name_dialog($"Rename '{target.name}'", target, (t, n) => save_profile_as(t ?? target, n)).Show();
|
||||
void revert_profile(ProfileVM target) => target?.revert();
|
||||
void save_profile(ProfileVM target) => save_profile_as(target, target?.name);
|
||||
//
|
||||
void save_profile_as(ProfileVM target, string name) {
|
||||
if (name == null || selected_profile == null)
|
||||
return;
|
||||
SettingsProfile<T> val;
|
||||
if (target != null) {
|
||||
// overwrite target profile
|
||||
val = selected_profile.create().with(name: name);
|
||||
target.commit(val);
|
||||
selected_profile = target;
|
||||
profile_service.add_or_update(val);
|
||||
} else {
|
||||
// create new profile (set id=0 so 'store' knows to add a new profile, rather than update an existing one)
|
||||
val = selected_profile.create().with(0, name);
|
||||
uint id = profile_service.add_or_update(val);
|
||||
/// Add the new profile to (<see cref="profiles"/>) so it can then be assigned to <see cref="selected_profile"/> without being immediately unselected.
|
||||
/// remember: val needs the new id before it can be added.
|
||||
val = val.with(id);
|
||||
// the save process is asynchronous now, but if that were to change, the new setting vm would have been added (already) in the iobservable settings callback.
|
||||
// check that a vm with this id doesn't already exist before adding it .
|
||||
int i = CollectionHelper.binary_search(skip_local_wrapper, val, _key_comp);
|
||||
if (i < 0)
|
||||
skip_local_wrapper.Insert(i = ~i, val);
|
||||
selected_profile = skip_local[i];
|
||||
}
|
||||
}
|
||||
void save_all() =>
|
||||
profile_service.add_or_update(skip_local.Select(p => p.create()));
|
||||
|
||||
Window choose_name_dialog(string title, ProfileVM target, Action<ProfileVM, string> commit) {
|
||||
var text_box = new TextBox { Width = 160, Margin = new Thickness(8, 0, 0, 0), Text = target?.name ?? string.Empty };
|
||||
TextBoxBehavior.SetFocusOnLoaded(text_box, true);
|
||||
WPFTextBoxAutoComplete.AutoCompleteBehavior.SetAutoCompleteItemsSource(text_box, skip_local.Select(vm => vm.name).ToList());
|
||||
// panel
|
||||
var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(8) };
|
||||
panel.Children.Add(new TextBlock { Text = "Name:", VerticalAlignment = VerticalAlignment.Center });
|
||||
panel.Children.Add(text_box);
|
||||
// dialog
|
||||
var dialog = new OKCancelDialog {
|
||||
Title = title,
|
||||
OKCommand = new DelegateCommand(() => commit(skip_local.FirstOrDefault(vm => vm.name == text_box.Text), text_box.Text)),
|
||||
CoreContent = panel,
|
||||
Topmost = true
|
||||
};
|
||||
dialog.move_to_mouse(-100, -80);
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<SettingsProfile<T>> applied;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<ProfileVM> profiles { get; } = new ObservableCollection<ProfileVM>();
|
||||
public ProfileVM selected_profile {
|
||||
get => _selected_profile.Value;
|
||||
set {
|
||||
if (_selected_profile.set_new(value) && value != null)
|
||||
value.is_selected = true;
|
||||
}
|
||||
}
|
||||
public TVM instance_vm => selected_profile?.value;
|
||||
public bool is_copy_menu_open { get => _is_copy_menu_open; set => Set(ref _is_copy_menu_open, value); }
|
||||
|
||||
public ICommand add_profile_cmd { get; }
|
||||
public ICommand copy_profile_cmd { get; }
|
||||
public ICommand delete_profile_cmd { get; }
|
||||
public ICommand revert_profile_cmd { get; }
|
||||
public ICommand edit_profile_securities_cmd { get; }
|
||||
public ICommand save_profile_cmd { get; }
|
||||
public ICommand save_profile_as_cmd { get; }
|
||||
public ICommand save_all_cmd { get; }
|
||||
|
||||
public IEnumerable<IMenuItemVM> action_menu_items {
|
||||
get {
|
||||
yield return _import_item;
|
||||
yield return _export_item;
|
||||
yield return _load_factory_item;
|
||||
}
|
||||
}
|
||||
public IEnumerable<DialogSkirtItem> left_skirt_items { get { yield break; } }
|
||||
public IEnumerable<DialogSkirtItem> right_skirt_items {
|
||||
get {
|
||||
yield return _apply_item;
|
||||
yield return _ok_item;
|
||||
yield return _cancel_item;
|
||||
}
|
||||
}
|
||||
|
||||
public class ProfileVM : ViewModelBase, IValueFactory<SettingsProfile<T>>, IEquatable<ProfileVM>, ITemplateSelectorKey, IDisposable {
|
||||
// static delegates
|
||||
public static readonly Func<ProfileVM, bool> is_global_fn = p => p != null && p.is_global;
|
||||
public static readonly Func<ProfileVM, IObservable<bool>> subscribe_is_global_dirty_fn = p => p?.is_global ?? false ? p.subscribe_is_dirty() : Observable.Return(false);
|
||||
public static readonly Func<ProfileVM, IObservable<bool>> subscribe_is_dirty_fn = p => p != null ? p.subscribe_is_dirty() : Observable.Return(false);
|
||||
//
|
||||
readonly DeferrableActionWrapper _refresh_is_dirty;
|
||||
readonly DelegateProperty<bool> _is_dirty;
|
||||
//
|
||||
SecurityCriteria _security_criteria;
|
||||
SettingsProfile<T> _committed, _last_result;
|
||||
string _name, _template_key;
|
||||
bool _is_selected;
|
||||
IDisposable _dispose;
|
||||
|
||||
public ProfileVM(uint id, TVM value, string name, SecurityCriteria security_criteria = null, string template_key = null) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
_name = name;
|
||||
_security_criteria = security_criteria;
|
||||
_template_key = template_key;
|
||||
_committed = _last_result = create();
|
||||
_is_dirty = backing(nameof(is_dirty), () => !_last_result.Equals(_committed));
|
||||
_refresh_is_dirty = new DeferrableActionWrapper(_is_dirty.refresh);
|
||||
var sub = value.Subscribe(v => {
|
||||
_last_result = _last_result.with(value: v);
|
||||
_refresh_is_dirty.execute();
|
||||
});
|
||||
_dispose = value is IDisposable d ? Disposable.Create(sub, d) : sub;
|
||||
}
|
||||
|
||||
public uint id { get; }
|
||||
public TVM value { get; }
|
||||
public string name {
|
||||
get => _name;
|
||||
set {
|
||||
if (Set(ref _name, value)) {
|
||||
_last_result = _last_result.with(name: value);
|
||||
_is_dirty.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool is_global => id != 0;
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_selected { get => _is_selected; set => Set(ref _is_selected, value); }
|
||||
public SecurityCriteria security_criteria {
|
||||
get => _security_criteria;
|
||||
set {
|
||||
if (Set(ref _security_criteria, value)) {
|
||||
_last_result = _last_result.with(security_criteria: security_criteria);
|
||||
_is_dirty.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
public SettingsProfile<T> create() =>
|
||||
new SettingsProfile<T>(id, name, value.create(), security_criteria);
|
||||
|
||||
public void load_from(SettingsProfile<T> data) {
|
||||
using (_refresh_is_dirty.defer_execution()) {
|
||||
_load_from(data);
|
||||
/// assign directly to <see cref="_last_result"/>, to capture props that aren't visible in the dialog (i.e., props that are manipulated in the main window)
|
||||
_last_result = data;
|
||||
}
|
||||
}
|
||||
|
||||
public void load_value_from(T value) {
|
||||
using (_refresh_is_dirty.defer_execution())
|
||||
this.value.load_from(value);
|
||||
}
|
||||
|
||||
public void commit(SettingsProfile<T> data) {
|
||||
using (_refresh_is_dirty.defer_execution())
|
||||
_load_from(_committed = data);
|
||||
}
|
||||
|
||||
public void commit_current() {
|
||||
_committed = _last_result;
|
||||
_refresh_is_dirty.execute();
|
||||
}
|
||||
|
||||
public void revert() {
|
||||
using (_refresh_is_dirty.defer_execution()) {
|
||||
_load_from(_committed);
|
||||
_last_result = _committed;
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
public bool Equals(ProfileVM other) => id == other?.id;
|
||||
public override bool Equals(object obj) => Equals(obj as ProfileVM);
|
||||
public override int GetHashCode() => id.GetHashCode();
|
||||
public override string ToString() => _name;
|
||||
|
||||
void _load_from(SettingsProfile<T> data) {
|
||||
name = data.name;
|
||||
security_criteria = data.security_criteria;
|
||||
value.load_from(data.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
ShowExpanderArrow="False"
|
||||
Padding="2,0"
|
||||
>
|
||||
|
||||
@@ -96,5 +96,8 @@ namespace mbts.spark {
|
||||
clause.security_types.is_null_or_empty() &&
|
||||
!clause.security_keys.is_null_or_empty() &&
|
||||
!clause.security_keys.Distinct().Skip(1).Any();
|
||||
|
||||
public static bool includes(this SecurityCriteria security_criteria, SecurityImmutable security) =>
|
||||
security_criteria != null && ContractPickerModel.security_in_criteria(security_criteria, security);
|
||||
}
|
||||
}
|
||||
@@ -152,9 +152,7 @@ namespace mbts.spark {
|
||||
add_col(Columns.TotalPNL, DefaultColumnHeaders.TotalPnL, r => r.total_pnl_text, nameof(PnLSummaryRow.total_pnl), DefaultColumnWidths.PnL_Generic, total_pnl_filter, DataGridFilterKnownTemplates.DoubleMinMax, HorizontalAlignment.Right, r => r.total_pnl_background);
|
||||
add_col(Columns.PNLPrice, DefaultColumnHeaders.PnLPrice, r => r.pnl_price_text, nameof(PnLSummaryRow.pnl_price), DefaultColumnWidths.PnL_Generic, pnl_price_filter, DataGridFilterKnownTemplates.DoubleMinMax, HorizontalAlignment.Right);
|
||||
void add_col(Columns key, string header, Func<PnLSummaryRow, string> get_text, string sort_prop, double width, IDataGridColumnFilter filter, string filter_template, HorizontalAlignment h_align = HorizontalAlignment.Left, Func<PnLSummaryRow, Brush> get_background = null) {
|
||||
var col = get_background == null ?
|
||||
new RenderTextColumn<PnLSummaryRow, Columns>(key, get_text, filter, filter_template) :
|
||||
new PnLDelegateBackgroundColumn(key, get_text, get_background, filter, filter_template);
|
||||
var col = new RenderTextColumn<PnLSummaryRow, Columns>(key, get_text, filter, filter_template, get_background);
|
||||
col.Header = header;
|
||||
col.Width = width;
|
||||
col.sort_member_path = sort_prop;
|
||||
@@ -357,14 +355,5 @@ namespace mbts.spark {
|
||||
DateTime _last_pnl_update_time = DateTime.MinValue;
|
||||
ObservableCollection<PnLSummaryRow> _pnl_data = new ObservableCollection<PnLSummaryRow>();
|
||||
|
||||
class PnLDelegateBackgroundColumn : RenderTextColumn<PnLSummaryRow, Columns> {
|
||||
readonly Func<PnLSummaryRow, Brush> _get_brush;
|
||||
|
||||
public PnLDelegateBackgroundColumn(Columns key, Func<PnLSummaryRow, string> get_text, Func<PnLSummaryRow, Brush> get_brush, IDataGridColumnFilter filter, string filter_template)
|
||||
: base(key, get_text, filter, filter_template) => _get_brush = get_brush;
|
||||
|
||||
public override void draw(PnLSummaryRow row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, glyph_context, font_size, _get_text(row), position, row_height, font_brush, _get_brush.Invoke(row), horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ namespace mbts.spark {
|
||||
_view.DataContext = _vm;
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.pnl_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -69,7 +69,7 @@ namespace mbts.spark {
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { Title = "PnL Grid Settings", DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = "PnL Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -7,17 +7,18 @@ using System.Windows.Input;
|
||||
namespace mbts.spark {
|
||||
public class CalculatorLogic : DisposableViewModelBase {
|
||||
readonly UserProperty<uint?> _value;
|
||||
readonly Property<int[]> _buttons;
|
||||
int[] _buttons;
|
||||
|
||||
public CalculatorLogic(uint? initial_size, IObservable<int[]> buttons_source) {
|
||||
public CalculatorLogic(uint? initial_size) =>
|
||||
_value = new UserProperty<uint?>(backing(nameof(value), initial_size));
|
||||
_buttons = backing(nameof(buttons), buttons_source);
|
||||
}
|
||||
|
||||
public CalculatorLogic(uint? initial_size, IObservable<int[]> buttons_source) : this(initial_size) =>
|
||||
_dispose = buttons_source.Subscribe(newval => buttons = newval);
|
||||
|
||||
public uint? value { get => _value.Value; set => _value.Value = value; }
|
||||
public bool value_setter_called => _value.setter_called;
|
||||
public IObservable<uint?> value_observable => _value.Select(vals => vals.newval);
|
||||
public int[] buttons { get => _buttons.Value; set => _buttons.set(value); }
|
||||
public int[] buttons { get => _buttons; set => Set(ref _buttons, value); }
|
||||
public ICommand click => new DelegateCommand<uint>(handle_calculator_click);
|
||||
public ICommand set_value_command => new DelegateCommand<uint>(v => _value.Value = v);
|
||||
public ICommand clear => new DelegateCommand(() => _value.Value = null);
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IWindowSettingsVM<T> where T : class {
|
||||
void load(T value);
|
||||
T get_result();
|
||||
}
|
||||
|
||||
class WindowSettingsDialogVM<T, TVM> : ViewModelBase, IRequestViewClose, IDisposable where T : class, IEquatable<T> where TVM : IWindowSettingsVM<T> {
|
||||
readonly Property<bool> _use_default;
|
||||
readonly DialogSkirtItem _ok_item, _apply_item, _cancel_item, _apply_to_defaults_item;
|
||||
readonly DelegateProperty<IEnumerable<DialogSkirtItem>> _left_skirt_items;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>(2); // initialize with 2 capacity bc the only potential disposables are the instance/default vms
|
||||
|
||||
public WindowSettingsDialogVM(IWindowSettingsManager<T> settings_manager, Func<UserSettings, T> get_factory_settings, string settings_file_ext, Func<T, TVM> vm_factory, Func<T, T> merge_with_current = null, bool show_save_as_defaults = true) {
|
||||
if (merge_with_current == null)
|
||||
merge_with_current = s => s;
|
||||
// by default, each new window will use their own instance settings
|
||||
_use_default = backing(nameof(use_default), settings_manager.instance_settings == null);
|
||||
default_vm = vm_factory(settings_manager.default_settings);
|
||||
// instance vm is what will always be visible on the screen
|
||||
instance_vm = vm_factory(settings_manager.current_settings());
|
||||
// items
|
||||
const string import_group = "i", export_group = "e";
|
||||
(string name, string[] extensions) file_filter = ($"{settings_file_ext} settings files", new[] { settings_file_ext });
|
||||
var import_to_custom_cmd = FileDialogCommand.import_proto<T>(s => instance_vm.load(s), type_name: settings_file_ext, filter_items: file_filter);
|
||||
var export_to_custom_cmd = FileDialogCommand.export_proto(() => instance_vm.get_result(), filter_items: file_filter);
|
||||
action_menu_items = new IMenuItemVM[] {
|
||||
new MenuItemVM("Load Settings File ...", import_to_custom_cmd, Icons.Import, import_group),
|
||||
new MenuItemVM("Save Settings File ...", export_to_custom_cmd, Icons.Export, export_group),
|
||||
new MenuItemVM("Load Factory Defaults",
|
||||
new DelegateCommand(() => instance_vm.load(get_factory_settings(UserSettings.factory_defaults))),
|
||||
Icons.Factory
|
||||
)
|
||||
};
|
||||
_left_skirt_items = backing(nameof(left_skirt_items), left_skirt_factory);
|
||||
_apply_item = new DialogSkirtItem("Apply", new DelegateCommand(() => {
|
||||
use_default = false;
|
||||
apply();
|
||||
}));
|
||||
_ok_item = new DialogSkirtItem("OK", new DelegateCommand(() => {
|
||||
use_default = false;
|
||||
apply();
|
||||
close();
|
||||
})) { is_default = true };
|
||||
_cancel_item = new DialogSkirtItem("Cancel", new DelegateCommand(close)) { is_cancel = true };
|
||||
_apply_to_defaults_item = new DialogSkirtItem("Save As Defaults", new DelegateCommand(() => {
|
||||
use_default = true;
|
||||
apply();
|
||||
close();
|
||||
}), ui.spark.LadderSettingsDialog.ResetToFactoryKey);
|
||||
// maybe dispose vms
|
||||
if (instance_vm is IDisposable dispose_ivm)
|
||||
_resources.Add(dispose_ivm);
|
||||
if (default_vm is IDisposable dispose_dvm)
|
||||
_resources.Add(dispose_dvm);
|
||||
// local methods
|
||||
void apply() {
|
||||
var result = merge_with_current(instance_vm.get_result());
|
||||
if (!use_default) {
|
||||
settings_manager.instance_settings = result;
|
||||
} else {
|
||||
settings_manager.instance_settings = null;
|
||||
|
||||
// copy the current instance settings and persist
|
||||
if (!result.safe_equals(settings_manager.default_settings))
|
||||
settings_manager.save_default_settings(result).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void close() => RequestClose?.Invoke(this, new EventArgs());
|
||||
|
||||
IEnumerable<DialogSkirtItem> left_skirt_factory() {
|
||||
if (show_save_as_defaults)
|
||||
yield return _apply_to_defaults_item;
|
||||
};
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public TVM instance_vm { get; }
|
||||
public TVM default_vm { get; }
|
||||
public IReadOnlyCollection<IMenuItemVM> action_menu_items { get; }
|
||||
public IEnumerable<DialogSkirtItem> left_skirt_items => _left_skirt_items.Value;
|
||||
public IEnumerable<DialogSkirtItem> right_skirt_items {
|
||||
get {
|
||||
yield return _apply_item;
|
||||
yield return _ok_item;
|
||||
yield return _cancel_item;
|
||||
}
|
||||
}
|
||||
public bool use_default {
|
||||
get => _use_default.Value;
|
||||
set => _use_default.Value = value;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(_resources);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace mbts.ui {
|
||||
protected virtual bool _try_get_item_key(object item, out TKey result) {
|
||||
if (item is IStyleSelectorKey<TKey> ssk) {
|
||||
result = ssk.key;
|
||||
return true;
|
||||
return result != null;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace mbts.ui {
|
||||
static class WPFHelper {
|
||||
public delegate void PropertyChangedCallback<TValue, TOwner>(TOwner owner, TValue old_value, TValue new_value);
|
||||
|
||||
public static DependencyProperty create_dp<TOwner>(string name, Action<TOwner, object> callback, object default_value = null) =>
|
||||
DependencyProperty.Register(name, typeof(object), typeof(TOwner), new PropertyMetadata(default_value, (d, e) => callback(ConvertUtils.try_convert<TOwner>(d), e.NewValue)));
|
||||
|
||||
public static DependencyProperty create_dp<TValue, TOwner>(string name, TValue default_value = default) =>
|
||||
DependencyProperty.Register(name, typeof(TValue), typeof(TOwner), new PropertyMetadata(default_value));
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace bts.api.core {
|
||||
_token = result.value.Token;
|
||||
user = username;
|
||||
user_id = result.value.UserId;
|
||||
return new LoginResult();
|
||||
return new LoginResult(result.value.Reason, true);
|
||||
}
|
||||
else {
|
||||
return new LoginResult(result.value.Reason);
|
||||
@@ -89,6 +89,7 @@ namespace bts.api.core {
|
||||
public class LoginResult {
|
||||
public LoginResult() { success = true; }
|
||||
public LoginResult(string reason) { success = false; this.reason = reason; }
|
||||
public LoginResult(string reason, bool success) { this.success = success; this.reason = reason; }
|
||||
public bool success { get; }
|
||||
public string reason { get; }
|
||||
}
|
||||
|
||||
@@ -246,11 +246,30 @@ namespace System.Collections.Generic {
|
||||
|
||||
public static class DictionaryHelper {
|
||||
public static IReadOnlyDictionary<TKey, TValue> empty<TKey, TValue>() =>
|
||||
Empty<TKey, TValue>.value;
|
||||
Impl<TKey, TValue>.empty;
|
||||
|
||||
static class Empty<TKey, TValue> {
|
||||
public static readonly IReadOnlyDictionary<TKey, TValue> value =
|
||||
public static IComparer<KeyValuePair<TKey, TValue>> key_comparer<TKey, TValue>() =>
|
||||
Impl<TKey, TValue>.key_comparer;
|
||||
|
||||
static class Impl<TKey, TValue> {
|
||||
public static readonly IReadOnlyDictionary<TKey, TValue> empty =
|
||||
new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>(0));
|
||||
|
||||
public static readonly IComparer<KeyValuePair<TKey, TValue>> key_comparer =
|
||||
new KeyComparer(Comparer<TKey>.Default);
|
||||
|
||||
class KeyComparer : IComparer<KeyValuePair<TKey, TValue>>, IComparer {
|
||||
readonly IComparer<TKey> _comparer;
|
||||
|
||||
public KeyComparer(IComparer<TKey> comparer) =>
|
||||
_comparer = comparer;
|
||||
|
||||
public int Compare(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y) =>
|
||||
_comparer.Compare(x.Key, y.Key);
|
||||
|
||||
public int Compare(object x, object y) =>
|
||||
x is KeyValuePair<TKey, TValue> xkv ? (y is KeyValuePair<TKey, TValue> ykv ? Compare(xkv, ykv) : 1) : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,6 +382,25 @@ namespace System.Collections.Generic {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MinOrDefault
|
||||
public static T MinOrDefault<T>(this IEnumerable<T> source) where T : IComparable<T> =>
|
||||
MinOrDefault(source, default);
|
||||
|
||||
public static T MinOrDefault<T>(this IEnumerable<T> source, T default_value) where T : IComparable<T> {
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
T min = en.Current;
|
||||
while (en.MoveNext()) {
|
||||
if (min.CompareTo(en.Current) > 0)
|
||||
min = en.Current;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MinElement
|
||||
public static TElement MinElement<TElement, T>(this IEnumerable<TElement> enumerable, Func<TElement, T> select_value) where T : IComparable<T> =>
|
||||
MinElement(enumerable, select_value, default(TElement));
|
||||
@@ -448,6 +467,25 @@ namespace System.Collections.Generic {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MaxOrDefault
|
||||
public static T MaxOrDefault<T>(this IEnumerable<T> source) where T : IComparable<T> =>
|
||||
MaxOrDefault(source, default);
|
||||
|
||||
public static T MaxOrDefault<T>(this IEnumerable<T> source, T default_value) where T : IComparable<T> {
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
T max = en.Current;
|
||||
while (en.MoveNext()) {
|
||||
if (max.CompareTo(en.Current) < 0)
|
||||
max = en.Current;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MaxElement
|
||||
public static TElement MaxElement<TElement, T>(this IEnumerable<TElement> enumerable, Func<TElement, T> select_value) where T : IComparable<T> =>
|
||||
MaxElement(enumerable, select_value, default(TElement));
|
||||
|
||||
@@ -116,6 +116,9 @@ namespace bts.collections {
|
||||
public static IList<T> WrapSubRange<T>(this IList<T> list, int start, int length) =>
|
||||
new SubRangeWrapper<T>(list, start, length);
|
||||
|
||||
public static IList<T> WrapSkip<T>(this IList<T> list, int skip) =>
|
||||
new SkipFrontWrapper<T>(list, skip);
|
||||
|
||||
public static IList<T> WrapSelector<T>(this IList list, Func<object, T> convert, Func<T, object> convert_back = null) =>
|
||||
new SelectorWrapper<T>(list, convert, convert_back);
|
||||
|
||||
@@ -408,5 +411,69 @@ namespace bts.collections {
|
||||
public void RemoveAt(int index) => _source.RemoveAt(index);
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
class SkipFrontWrapper<T> : IList<T>, IReadOnlyList<T> {
|
||||
readonly IList<T> _source;
|
||||
readonly int _skip;
|
||||
|
||||
public SkipFrontWrapper(IList<T> source, int skip) {
|
||||
_source = source;
|
||||
_skip = skip;
|
||||
}
|
||||
|
||||
public T this[int index] {
|
||||
get => _source[_skip + index];
|
||||
set => _source[_skip + index] = value;
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
int c = _source.Count;
|
||||
return c > _skip ? (c - _skip) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => _source.IsReadOnly;
|
||||
|
||||
public void Add(T item) => _source.Add(item);
|
||||
public void Clear() {
|
||||
int c = Count;
|
||||
if (c == 0)
|
||||
return;
|
||||
if (_source is List<T> l) {
|
||||
l.RemoveRange(_skip, c);
|
||||
} else {
|
||||
for (int i = 0; i < c; i++)
|
||||
_source.RemoveAt(_skip);
|
||||
}
|
||||
}
|
||||
public bool Contains(T item) => IndexOf(item) >= 0;
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
arrayIndex -= _skip;
|
||||
for (int i = _skip; i < _source.Count; i++)
|
||||
array[arrayIndex + i] = _source[i];
|
||||
}
|
||||
public IEnumerator<T> GetEnumerator() {
|
||||
for (int i = _skip; i < _source.Count; i++)
|
||||
yield return _source[i];
|
||||
}
|
||||
public int IndexOf(T item) {
|
||||
for (int i = _skip; i < _source.Count; i++) {
|
||||
if (_source[i].Equals(item))
|
||||
return i - _skip;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public void Insert(int index, T item) => _source.Insert(index + _skip, item);
|
||||
public bool Remove(T item) {
|
||||
int i = IndexOf(item);
|
||||
if (i >= 0) {
|
||||
_source.RemoveAt(i + _skip);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
public void RemoveAt(int index) => _source.RemoveAt(index + _skip);
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -65,8 +65,12 @@ namespace bts.collections {
|
||||
return false;
|
||||
}
|
||||
|
||||
public TRow get_parent(TKey key) =>
|
||||
_tree.TryGetNode(key, out var n) ? n.value : default;
|
||||
public TRow get_parent(TKey key) {
|
||||
if (try_get_row(key, out TRow r)) {
|
||||
return _tree.GetParent(r).value;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public IEnumerable<TRow> get_descendent_rows(TKey key) {
|
||||
if (_tree.TryGetNode(key, out var n)) {
|
||||
|
||||
@@ -234,8 +234,8 @@ namespace bts.tests
|
||||
//create Orders Here
|
||||
|
||||
var all = await all_futures(test_symbol);
|
||||
SecurityImmutable? sec = all.FirstOrDefault();
|
||||
Assert.IsTrue(sec.HasValue);
|
||||
SecurityImmutable? sec = all.TryFirst();
|
||||
Assert.IsTrue(sec.HasValue, "No Futures?");
|
||||
|
||||
uint under_limit_order_key;
|
||||
uint over_limit_order_key;
|
||||
|
||||
@@ -131,7 +131,39 @@ namespace bts.tests.utils {
|
||||
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
|
||||
watch.Start();
|
||||
|
||||
var odef = OrderDefinitionUtilities.create_limit_order_def(1, Side.Buy, ("bts", 1), ("ACC A", 1));
|
||||
var ctx = new ServiceContext();
|
||||
var mdrelay = ctx.locate<bts.co2.MarketDataRelay>();
|
||||
|
||||
bts.co2.ProductListReply products = null;
|
||||
|
||||
for(int i = 0; i < 10; i++) {
|
||||
var tmpp = await mdrelay.request_products_async(null);
|
||||
if( tmpp.value != null) {
|
||||
Console.Write(i);
|
||||
products = tmpp.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bts.test.Assert.IsTrue(products != null, "No products found");
|
||||
var filtered_products = products.Products.Where(z => z.Exchange == Venue.Sim && z.Type == SecurityType.Future);
|
||||
bts.test.Assert.IsTrue(filtered_products.Count() > 0, "No suitable products found");
|
||||
|
||||
uint? sec_id = null;
|
||||
|
||||
for(int i = 0; i < 10; i++) {
|
||||
var test_security = await MarketDataUtilities.get_security(mdrelay, filtered_products.First().Symbol, SecurityType.Future, f => true);
|
||||
if(test_security.HasValue) {
|
||||
Console.Write("," + i);
|
||||
sec_id = test_security.Value.Id;
|
||||
break;
|
||||
}
|
||||
await Task.Delay(10000);
|
||||
}
|
||||
|
||||
bts.test.Assert.IsTrue(sec_id != null, "No security id found");
|
||||
|
||||
var odef = OrderDefinitionUtilities.create_limit_order_def(sec_id.Value, Side.Buy, ("ACC A", 1), ("bts", 1));
|
||||
|
||||
Update<OrderRecordImmutable, uint> service_value = null;
|
||||
while(service_value == null && watch.Elapsed < timeout) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
if [ -d /sparkdata/ ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
else
|
||||
sparkdata_dir=~/sparkdata
|
||||
fi
|
||||
|
||||
if [ -d "$sparkdata_dir/data" ]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
if [ -d /sparkdata ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
else
|
||||
sparkdata_dir=~/sparkdata
|
||||
fi
|
||||
|
||||
./delete_dbs.sh
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
if [ -d /sparkdata ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
else
|
||||
sparkdata_dir=~/sparkdata
|
||||
fi
|
||||
|
||||
./delete_dbs.sh
|
||||
@@ -28,7 +28,6 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
MarketDataConfig { value { use_onixs: false; } }
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
if [ -d /sparkdata/ ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
else
|
||||
sparkdata_dir=~/sparkdata
|
||||
fi
|
||||
|
||||
./delete_dbs.sh
|
||||
@@ -28,7 +28,6 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
MarketDataConfig { value { use_onixs: false; } }
|
||||
EOF
|
||||
|
||||
nohup ./sparkd run &
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
# Stop script if unbound variable found (use ${var:-} if intentional)
|
||||
set -u
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
if [ -d /sparkdata/ ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
else
|
||||
sparkdata_dir=~/sparkdata
|
||||
fi
|
||||
|
||||
# Use in the the functions: eval $invocation
|
||||
@@ -130,11 +130,6 @@ ExchangeSim {
|
||||
merge_with_md: false
|
||||
}
|
||||
}
|
||||
MarketDataConfig {
|
||||
value {
|
||||
use_onixs: false;
|
||||
}
|
||||
}
|
||||
END_HEREDOC
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -d ~/sparkdata/ ]
|
||||
if [ -d /sparkdata ]
|
||||
then
|
||||
sparkdata_dir=~/sparkdata
|
||||
else
|
||||
sparkdata_dir=/sparkdata
|
||||
else
|
||||
sparkdata_dir=~/sparkdata
|
||||
fi
|
||||
|
||||
./delete_dbs.sh
|
||||
@@ -28,7 +28,6 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
|
||||
file { default_level: "Error" }
|
||||
}
|
||||
}
|
||||
MarketDataConfig { value { use_onixs: false; } }
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthToken.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/datetime.cpp;utils/download.cpp;utils/file_utils.cpp;utils/init_spark_lib.cpp;utils/is_unittest_environment.cpp;utils/stats.cpp;utils/tests/AuthTokenTests.cpp;utils/tests/SingleWriterConcurrentVectorTests.cpp;utils/thread_manager.cpp)
|
||||
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthSessionManager.cpp;utils/AuthToken.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/crypto/CipherType.cpp;utils/crypto/CryptoInit.cpp;utils/crypto/Digest.cpp;utils/crypto/KeyCodeHelper.cpp;utils/crypto/PasswordCallback.cpp;utils/crypto/PrivateKey.cpp;utils/crypto/PublicKey.cpp;utils/crypto/iostreambio.cpp;utils/datetime.cpp;utils/download.cpp;utLine truncated
|
||||
@@ -27,7 +27,7 @@ message User {
|
||||
}
|
||||
|
||||
message TokenPayload {
|
||||
uint32 date = 1;
|
||||
uint64 date = 1;
|
||||
uint32 id = 2;
|
||||
bool wheel = 3;
|
||||
}
|
||||
|
||||
@@ -174,6 +174,8 @@ message CoverOrderDefinition {
|
||||
CustomOrderDefinition hedge = 1;
|
||||
uint32 hedge_ticks = 2;
|
||||
CustomOrderDefinition child = 3;
|
||||
bool cancel_on_external_hedge_cancel = 4;
|
||||
bool cancel_hedges_on_cancel = 5;
|
||||
}
|
||||
message OffsetOrderDefinition {
|
||||
uint32 tick_offset_on_deploy = 1;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#nusers: 1
|
||||
#allow concurrent : false
|
||||
#expires: 2019-Sep-30 00:00:00 637053984000000000
|
||||
#generation_time: 2019-Jun-18 18:25:44.333280 5248650809870720704
|
||||
EncryptedKeyCode {
|
||||
value {
|
||||
keycode: "pgtv/OmievnpmNlgxTYuN801Hbetgu7ZGkHnlGWtK+DBrKSEgKxr5lGvCW1ld/jsztkWhKHDh0iediMDjrPm4+C5qmXlt4JJlAFvKolMN8HVC59m82fECvakcqKq28WSEjf6/DuPJNP1ZwxjcmKouxx1LZ15VlNkECtNrC9pkCnifazbfQZGV3kD08zMEyw/RPmOaVLFFeav8lwI5UpcjPYBIK1rJowXdGSvm1CJViwG5Z2JbdHR39QbKjVx112d5VbVtBq9nO3i0FEKvobjN/Gzcz/5x4TgQtEShKsbmgEN+MBVL3AItQlcFzZvE/05JLmfACUFmk5Eu0a3DlLs3A=="
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
syntax='proto2';
|
||||
package bts.config;
|
||||
|
||||
message KeyCode
|
||||
{
|
||||
optional uint32 nusers = 10;
|
||||
optional uint64 expiration = 20;
|
||||
optional uint64 generation_time = 30;
|
||||
|
||||
optional string ip = 40;
|
||||
optional string port = 50;
|
||||
optional bool allow_concurrent = 60;
|
||||
}
|
||||
|
||||
message EncryptedKeyCode
|
||||
{
|
||||
optional string keycode = 10;
|
||||
}
|
||||
|
||||
message PublicKey
|
||||
{
|
||||
optional string key = 10;
|
||||
}
|
||||
Loaded 100 of 146 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user