Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c22f50963f | ||
|
|
626402965d | ||
|
|
16acc4f5b0 | ||
|
|
b8a94f2540 | ||
|
|
3af7e561b9 | ||
|
|
556132c2dd | ||
|
|
f871dfe142 | ||
|
|
d44de204ee | ||
|
|
04beea61eb | ||
|
|
2181b92970 | ||
|
|
693c730f51 | ||
|
|
0ccba5bb0b | ||
|
|
3458be59d7 | ||
|
|
7dc9aa0710 | ||
|
|
88d6dd0f00 | ||
|
|
ce4c46da04 | ||
|
|
59c1f6a511 | ||
|
|
938caf9a4f | ||
|
|
df4f061b6c | ||
|
|
8afee21df7 | ||
|
|
f34014eb25 | ||
|
|
b07eec08b4 | ||
|
|
964274f5c4 | ||
|
|
c16941977c | ||
|
|
6b19119ab2 | ||
|
|
ea42086e96 | ||
|
|
292873b189 | ||
|
|
8545a5ef38 | ||
|
|
6a52fff3d5 | ||
|
|
4ec6fafbc2 | ||
|
|
b08e2c2ee6 | ||
|
|
8bdd033501 | ||
|
|
76466e83f0 | ||
|
|
a22b7b7189 | ||
|
|
2540dc0510 | ||
|
|
f0132c84b6 | ||
|
|
57ca9596c8 | ||
|
|
bf4953f6dc | ||
|
|
c277fcd503 | ||
|
|
dff57a43dd | ||
|
|
39c71d0c16 | ||
|
|
009e72953c | ||
|
|
8e41a42ced | ||
|
|
635163c895 | ||
|
|
95e96a6afc | ||
|
|
e0f7bdfc13 | ||
|
|
ec0b96215f | ||
|
|
e27a8be526 | ||
|
|
f5354776f1 | ||
|
|
70cd9f185e | ||
|
|
c7f0e2dd58 | ||
|
|
33eaf6f86d | ||
|
|
f5e79a65a3 | ||
|
|
c958f4bf1a | ||
|
|
9413eb2206 | ||
|
|
e986f77e72 | ||
|
|
75095ce874 | ||
|
|
95939a5534 | ||
|
|
9a7164b42b | ||
|
|
f347fda47f | ||
|
|
d75ab3a63f | ||
|
|
b30f59227e | ||
|
|
ef60d4200a | ||
|
|
ed6f0b8bdb | ||
|
|
89b5610aa2 | ||
|
|
f721098ae0 | ||
|
|
bfff6454ef | ||
|
|
766116eac4 | ||
|
|
8d49de3f61 | ||
|
|
16cf4554c5 | ||
|
|
6b17efee60 | ||
|
|
5d64213455 | ||
|
|
b90e5e4274 | ||
|
|
bacd4558e1 | ||
|
|
008e698766 | ||
|
|
ed7f066f20 | ||
|
|
d7c81a08ef | ||
|
|
aadb6c86f3 | ||
|
|
0ef44e92aa | ||
|
|
5653a66a31 | ||
|
|
ee2f76553b | ||
|
|
c1887b9786 | ||
|
|
1fd5e51b9e | ||
|
|
56d3a27e7f | ||
|
|
1c65b1d87a | ||
|
|
693910f093 | ||
|
|
498b38ebcb | ||
|
|
99b9c51ad6 | ||
|
|
fe92389eab | ||
|
|
1ca5b217fc | ||
|
|
fb91206e0f | ||
|
|
91f4de00f8 | ||
|
|
28980f10ae | ||
|
|
4e4ab52032 | ||
|
|
4cc513f1d3 | ||
|
|
7904065333 | ||
|
|
961e06c31f | ||
|
|
4132bdaba4 | ||
|
|
1c75f5f530 | ||
|
|
d8e7dd7c3b | ||
|
|
d0377475bb | ||
|
|
7826ff17c0 | ||
|
|
a46982333b | ||
|
|
e2cebaa5c8 | ||
|
|
d03987591c | ||
|
|
e909c37f88 | ||
|
|
e998edc20f | ||
|
|
1c1be2b334 | ||
|
|
04e006b812 | ||
|
|
5d89257643 | ||
|
|
897e134714 | ||
|
|
e6cb97028b | ||
|
|
6c0dc80d43 | ||
|
|
ce3a4562d9 | ||
|
|
217f0cd4cc | ||
|
|
1e649ccce6 | ||
|
|
cbc69ec5fd | ||
|
|
0d1eedc34a | ||
|
|
4e85bf4520 | ||
|
|
5d0023a80b | ||
|
|
e300de2425 | ||
|
|
569877752e | ||
|
|
4846cf7693 | ||
|
|
16a46955f4 | ||
|
|
13a6d68b60 | ||
|
|
1affd992de | ||
|
|
d951258891 | ||
|
|
f5dfb0264b | ||
|
|
3b8f5c5093 | ||
|
|
253d213818 | ||
|
|
f89cb3d61e | ||
|
|
eb6573399b | ||
|
|
8ba8ce76ce | ||
|
|
e65b8e9d4d | ||
|
|
2fc379fbef | ||
|
|
fb96a75a8e | ||
|
|
240d15af47 | ||
|
|
2530e43797 | ||
|
|
b05d85d6c3 | ||
|
|
52f2e77310 | ||
|
|
bd00c6de3e | ||
|
|
5b959ca5bd | ||
|
|
87aea4812f | ||
|
|
21ae3b14c8 | ||
|
|
708ae185f1 | ||
|
|
5c6dbd7bd1 | ||
|
|
f19a280b7f | ||
|
|
47cf9e2fe6 | ||
|
|
df75518389 | ||
|
|
90c371c72f | ||
|
|
9609f7e6cf | ||
|
|
30c83b7c72 | ||
|
|
382d8b468c | ||
|
|
cbe6d07a86 | ||
|
|
6d67700ee6 | ||
|
|
e9cf1db184 | ||
|
|
e4c98d947e | ||
|
|
ab9de97d6e | ||
|
|
4e425a3380 | ||
|
|
ac67c84ce7 | ||
|
|
79120ea7a5 | ||
|
|
a85a7f624a | ||
|
|
19e4da9936 | ||
|
|
1494c37cc7 | ||
|
|
af1fbaf3ed | ||
|
|
82a93e39f8 | ||
|
|
36684f8ee5 | ||
|
|
c8cd2ae1ad | ||
|
|
93afb5b602 | ||
|
|
429b0bb8f8 | ||
|
|
80ce5447e5 | ||
|
|
609b0dceb1 | ||
|
|
ea47042816 | ||
|
|
4b23e17534 | ||
|
|
f69301611a | ||
|
|
7ac54242e0 | ||
|
|
1ebf8219c2 | ||
|
|
f0b14cb8dc | ||
|
|
7259f81ab5 | ||
|
|
3b902b61dd | ||
|
|
c04ac5cd48 | ||
|
|
aa39349fc4 | ||
|
|
d1c915fef3 | ||
|
|
69bdf07423 | ||
|
|
4115a6c790 | ||
|
|
8f8aae57de | ||
|
|
362f75c8b2 | ||
|
|
39e855e691 | ||
|
|
73303ccd33 | ||
|
|
6cfef51e3d | ||
|
|
76edb94905 | ||
|
|
e4bf9fc747 | ||
|
|
6826494898 | ||
|
|
69fcfbd6ce | ||
|
|
c1d62a2ff6 | ||
|
|
d488ff23ba | ||
|
|
6b6fd9c40a | ||
|
|
1fa419eae2 | ||
|
|
e3a3eb6d85 | ||
|
|
18a32f9a05 | ||
|
|
b7c127b2d8 | ||
|
|
f83f94fb07 | ||
|
|
23f76a1c80 | ||
|
|
cc0f6ea448 | ||
|
|
cbb6936553 | ||
|
|
633433cab9 | ||
|
|
3ab7ed0b71 | ||
|
|
fc1adf8c28 | ||
|
|
79cf313b5b | ||
|
|
00834a668c | ||
|
|
32c9c23d7e | ||
|
|
5ac8d2ada6 | ||
|
|
54eb6ceaa9 | ||
|
|
540db91311 | ||
|
|
1ee2193c88 | ||
|
|
84877af947 | ||
|
|
388cf92a70 | ||
|
|
840441a749 | ||
|
|
c39a510d09 | ||
|
|
f7265740e7 | ||
|
|
299d46aa2c | ||
|
|
345f6ed9fe | ||
|
|
31aa9d9bf6 | ||
|
|
0c063a3876 | ||
|
|
af253198d2 | ||
|
|
cc381d605c | ||
|
|
724da31316 | ||
|
|
2ae5b50445 | ||
|
|
dfcf38289f | ||
|
|
c2f954a79d | ||
|
|
1c54b0140b | ||
|
|
e79a73147a | ||
|
|
6a921ae900 | ||
|
|
0f13d27efe | ||
|
|
e034fd2623 | ||
|
|
a3b8d579a1 | ||
|
|
9d2cdb702d | ||
|
|
d52347cd71 | ||
|
|
0ab628fb6c | ||
|
|
1a8a492ef7 | ||
|
|
74066c0ffe | ||
|
|
0269d5fa86 | ||
|
|
84a5a11a9c | ||
|
|
d0784feddd | ||
|
|
a01e6df7fa | ||
|
|
d4caccdbfb | ||
|
|
4ede8a62ea | ||
|
|
ba11248ac9 | ||
|
|
22cde8aa0d | ||
|
|
3dba49e899 | ||
|
|
4a64e45d36 | ||
|
|
23d7a9e1ad | ||
|
|
7bd9d3f4ad | ||
|
|
97f09768bb | ||
|
|
7740f64bfd | ||
|
|
7ccbb69f5c | ||
|
|
b4ba527c12 | ||
|
|
a62a41a0aa | ||
|
|
2b4add85a2 | ||
|
|
904023c5fa | ||
|
|
a9fc3d43a0 | ||
|
|
4febc07aa0 | ||
|
|
198e76f722 | ||
|
|
836301e75f | ||
|
|
ad37a652c5 | ||
|
|
5bed54c953 | ||
|
|
5675c4e117 | ||
|
|
16f1e568a5 | ||
|
|
e375274cbe | ||
|
|
cf3ef9323a | ||
|
|
7e2e2bf1b0 | ||
|
|
781c9dcc86 | ||
|
|
a8a922160d | ||
|
|
367bd0e3b9 | ||
|
|
4215d96ba9 | ||
|
|
50d18eb10d | ||
|
|
b65fedc781 | ||
|
|
88c8663d29 | ||
|
|
8c7dd2460d | ||
|
|
f20ee21ce7 | ||
|
|
a61e21bd87 | ||
|
|
39014696f3 | ||
|
|
af278d2d87 | ||
|
|
636e170f44 | ||
|
|
3d4fe8f832 | ||
|
|
94285918bf | ||
|
|
2ba7f949cc | ||
|
|
fc82ccf221 | ||
|
|
e71b1518f0 | ||
|
|
7738da7d16 | ||
|
|
c91eedcca0 | ||
|
|
4f5f4c61f3 | ||
|
|
2f31fb6284 | ||
|
|
d18caa7ba1 | ||
|
|
ac88586c8f | ||
|
|
968dfbac2a | ||
|
|
0501418da6 | ||
|
|
4efd853c96 | ||
|
|
a384eb9f73 | ||
|
|
4747fe5535 | ||
|
|
94eb1e37cb | ||
|
|
fd1ae60e49 | ||
|
|
62d06180ea | ||
|
|
dba3b9371c | ||
|
|
aadfe2a677 | ||
|
|
c0c34efc47 | ||
|
|
b66fb02174 | ||
|
|
3cf026ff47 | ||
|
|
8619084fa7 | ||
|
|
f6df644ab1 | ||
|
|
1183dee418 | ||
|
|
ffaba90c96 | ||
|
|
9eb760b3c7 | ||
|
|
70fcb30c67 | ||
|
|
553ab7b858 | ||
|
|
4c798f6db4 | ||
|
|
b2e275124f | ||
|
|
36e4f62a53 | ||
|
|
8dcf41e9cf | ||
|
|
a0bf0e93f4 | ||
|
|
5529e80e17 | ||
|
|
5df7cec55a | ||
|
|
8f705d42b3 | ||
|
|
fc2f85b842 | ||
|
|
ffe5eee584 | ||
|
|
b7897fc7ac | ||
|
|
b4d18a62c9 | ||
|
|
c6b450f45c | ||
|
|
6a46049610 | ||
|
|
21712711f7 | ||
|
|
9f1bb177b8 | ||
|
|
dcd2cc22e1 | ||
|
|
af91ee1512 | ||
|
|
b4d63aa3cc | ||
|
|
d0e32911ad | ||
|
|
75fa582774 | ||
|
|
69155162ca | ||
|
|
2c865c123a | ||
|
|
0ec955cc90 | ||
|
|
0c4e22537d | ||
|
|
601eb4ece1 | ||
|
|
b84a638199 | ||
|
|
34350fd469 | ||
|
|
a469da5cc6 | ||
|
|
8be8a67f18 | ||
|
|
abc2e55610 | ||
|
|
cfd15d3371 | ||
|
|
44369bae43 |
No files matched your search
Vendored
+9
-3
@@ -75,6 +75,7 @@
|
||||
"name": "SparkVM Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["run"],
|
||||
"stopAtEntry": false,
|
||||
@@ -96,7 +97,12 @@
|
||||
"description": "Skip STL",
|
||||
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "LD LIBRARY",
|
||||
"text": "set env LD_LIBRARY_PATH=.",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
@@ -208,10 +214,10 @@
|
||||
"name": "MD Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["test", "[md]"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
|
||||
@@ -55,6 +55,8 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
|
||||
@@ -5,6 +5,7 @@ sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
{{ youtube(id="RZU9Wj0cETo") }}
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/XEZpuPAgMC0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
@@ -406,6 +406,8 @@ insert_anchor_links = "right"
|
||||
|
||||
## Algo Studio
|
||||
|
||||
{{ youtube(id="DHIa5tnifKc") }}
|
||||
|
||||
- The BTS Algo Studio was built for users to experience algo composability
|
||||
never seen before in a professional futures trading system.
|
||||
|
||||
|
||||
@@ -4,6 +4,26 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 1.5.7
|
||||
### User Visible Changes
|
||||
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
|
||||
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
|
||||
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
|
||||
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in the SIM execution gateway
|
||||
- Added initial support for ICE market data
|
||||
- In an effort to streamline the PnL report we've made the following changes:
|
||||
- The PnL report will by default filter for Futures contracts only
|
||||
- If you wish to see Spread contracts again you may change Security Type column filter
|
||||
- The PnL report will now only show rows in which there is a position (net and/or day)
|
||||
- The Exchange, Start PnL, Total Net Pos, and Total Net PnL columns are now hidden by default
|
||||
- These columns can be restored by toggling their visibility within the PnL's Settings form
|
||||
- A new Traded Today column has been added to the PnL
|
||||
- Filtering on this column will allow a user to restrict the report to only show today's position
|
||||
### Internal changes
|
||||
- Improved organization of the shell notification architecture
|
||||
- Added logging to help troubleshoot shell exit hang
|
||||
|
||||
## 1.5.6
|
||||
### User Visible Changes
|
||||
- Fixed a bug that could cause limits to be erroneously enforced
|
||||
|
||||
@@ -54,3 +54,8 @@ img {
|
||||
background: #ccc;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width:560px;
|
||||
height:315px;
|
||||
}
|
||||
+1
-1
Submodule externals updated: fc3db9b691...d0e8645b5e.
@@ -1,3 +1,6 @@
|
||||
1.5.7
|
||||
- Fixed Aggressor coloring and ladder high/low coloring in SIM
|
||||
|
||||
1.4.2
|
||||
- Be sure the system has a bts user before deployment. bts is the superuser, he has all permissions and can see all market data
|
||||
- After deployment re-permission all existing users for trading, riskadmin, and/or md admin permissions as appropriate
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace mbts.ui.shell
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
Log.Gen.info("Application.OnExit");
|
||||
if (_runtime != null) _runtime.Dispose();
|
||||
utils.BTSNativeMethods.finit_spark_lib();
|
||||
base.OnExit(e);
|
||||
|
||||
@@ -22,7 +22,9 @@ namespace mbts.ui {
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
|
||||
DocumentGeometryKey = nameof(DocumentGeometryKey),
|
||||
@@ -39,6 +41,7 @@ namespace mbts.ui {
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PencilGeometryKey = nameof(PencilGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
@@ -115,6 +118,7 @@ namespace mbts.ui {
|
||||
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
|
||||
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
|
||||
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
|
||||
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
|
||||
|
||||
@@ -139,6 +143,7 @@ namespace mbts.ui {
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
|
||||
public static ImageSource Disk => create_icon(DiskGeometryKey);
|
||||
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
@@ -158,6 +163,7 @@ namespace mbts.ui {
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
|
||||
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
|
||||
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
|
||||
public static ImageSource Star => create_icon(StarGeometryKey);
|
||||
|
||||
@@ -40,6 +40,69 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Unlink free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
|
||||
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
|
||||
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
|
||||
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
|
||||
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
|
||||
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
|
||||
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
|
||||
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
|
||||
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Disconnected chains free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
|
||||
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
|
||||
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
|
||||
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
|
||||
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
|
||||
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
|
||||
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
|
||||
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
|
||||
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
|
||||
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
|
||||
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
|
||||
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
|
||||
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
|
||||
C164.311,139.575,168.644,142.574,173.171,141.769z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
|
||||
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
|
||||
C34.717,189.582,39.058,192.589,43.561,191.776z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/chanut
|
||||
Ttile: Cancel free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
|
||||
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
|
||||
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
|
||||
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
|
||||
C59.518,47.514,47.514,59.518,32.759,59.518z
|
||||
</sys:String>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/smashicons
|
||||
Ttile: File free icon
|
||||
|
||||
@@ -137,6 +137,12 @@ namespace mbts.spark {
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
public string market_description {get;}
|
||||
public string cleared_alias { get; }
|
||||
public string product_description { get; }
|
||||
public string hub_alias { get; }
|
||||
public string strip_name{ get; }
|
||||
public string mic_code { get; }
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
|
||||
@@ -57,6 +57,13 @@ namespace mbts.spark {
|
||||
add_col(Columns.SessionLow, "Low", r => r.format_price(r.session_low), nameof(Row.session_low), DefaultColumnWidths.Price, session_low_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.md.security.HasIce ? r.md.security.Ice.MarketDescription : string.Empty, nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.md.security.HasIce ? r.md.security.Ice.ClearedAlias : string.Empty, nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.md.security.HasIce ? r.md.security.Ice.ProductName : string.Empty, nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.md.security.HasIce ? r.md.security.Ice.HubAlias : string.Empty, nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.md.security.HasIce ? r.md.security.Ice.StripName : string.Empty, nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.md.security.HasIce ? r.md.security.Ice.MicCode : string.Empty, nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
@@ -194,6 +201,14 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
@@ -216,7 +231,13 @@ namespace mbts.spark {
|
||||
working_bid_volume = working_bid_volume_filter.get_state(),
|
||||
working_ask_volume = working_ask_volume_filter.get_state(),
|
||||
volume = volume_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
security_key = security_key_filter.get_state(),
|
||||
market_description = market_description_filter.get_state(),
|
||||
cleared_alias = cleared_alias_filter.get_state(),
|
||||
product_description = product_description_filter.get_state(),
|
||||
hub_alias = hub_alias_filter.get_state(),
|
||||
strip_name = strip_name_filter.get_state(),
|
||||
mic_code = mic_code_filter.get_state(),
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -8,7 +8,9 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for MarketSheetsView.xaml
|
||||
/// </summary>
|
||||
partial class MarketViewView : UserControl {
|
||||
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey }
|
||||
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey,
|
||||
MarketDescription, ClearedAlias, ProductDescription, HubAlias, StripName, MicCode
|
||||
}
|
||||
|
||||
public MarketViewView() {
|
||||
InitializeComponent();
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
@@ -47,7 +48,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
|
||||
@@ -97,6 +97,12 @@ namespace bts.shell {
|
||||
public static GridColumnSettings session_low => new GridColumnSettings(col_id(Columns.SessionLow), DefaultColumnHeaders.SessionLow, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings session_high => new GridColumnSettings(col_id(Columns.SessionHigh), DefaultColumnHeaders.SessionHigh, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings market_description => new GridColumnSettings(col_id(Columns.MarketDescription), DefaultColumnHeaders.MarketDescription, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings cleared_alias => new GridColumnSettings(col_id(Columns.ClearedAlias), DefaultColumnHeaders.ClearedAlias, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings product_description => new GridColumnSettings(col_id(Columns.ProductDescription), DefaultColumnHeaders.ProductDescription, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings hub_alias => new GridColumnSettings(col_id(Columns.HubAlias), DefaultColumnHeaders.HubAlias, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings strip_name => new GridColumnSettings(col_id(Columns.StripName), DefaultColumnHeaders.StripName, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings mic_code => new GridColumnSettings(col_id(Columns.MicCode), DefaultColumnHeaders.MicCode, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
|
||||
|
||||
|
||||
@@ -128,6 +134,12 @@ namespace bts.shell {
|
||||
yield return session_low;
|
||||
yield return session_high;
|
||||
yield return security_key;
|
||||
yield return market_description;
|
||||
yield return cleared_alias;
|
||||
yield return product_description;
|
||||
yield return hub_alias;
|
||||
yield return strip_name;
|
||||
yield return mic_code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +189,18 @@ namespace bts.shell {
|
||||
public UIntMinMaxState ask_direct_size;
|
||||
[ProtoMember(20)]
|
||||
public UIntMinMaxState ask_implied_size;
|
||||
[ProtoMember(21)]
|
||||
public string market_description;
|
||||
[ProtoMember(22)]
|
||||
public string cleared_alias;
|
||||
[ProtoMember(23)]
|
||||
public string product_description;
|
||||
[ProtoMember(24)]
|
||||
public string hub_alias;
|
||||
[ProtoMember(25)]
|
||||
public string strip_name;
|
||||
[ProtoMember(26)]
|
||||
public string mic_code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id { get; }
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
|
||||
public override void Dispose() =>
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
@@ -33,8 +33,20 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
Log.notnull(_state);
|
||||
|
||||
@@ -101,16 +113,6 @@ namespace mbts.spark {
|
||||
else _view.DataContext = _vm.value;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
@@ -559,71 +557,14 @@
|
||||
</Grid>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding content_source}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
Grid.Column="3"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
SelectedItem="{Binding selected_definition.algo_root}"
|
||||
ItemsSource="{Binding selected_definition.algos_view}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Width="140"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding selected_definition.algo_root}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:ArrayExtension Type="{x:Type Button}">
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
@@ -18,26 +16,11 @@ namespace mbts.ui.spark {
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
set_controller();
|
||||
// local methods
|
||||
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Windows;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string
|
||||
NAME = "Algo Studio",
|
||||
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
|
||||
@@ -37,7 +37,7 @@ namespace mbts.spark {
|
||||
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
|
||||
_model = model;
|
||||
_state = state;
|
||||
thread_init();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
|
||||
}
|
||||
|
||||
public override StartupData SaveState => _state = _vm.create();
|
||||
@@ -45,12 +45,12 @@ namespace mbts.spark {
|
||||
public override string window_type => NAME;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public string thread_id => NAME;
|
||||
public override double MinWidth =>
|
||||
SPARK_MENU_COLLAPSED_WIDTH +
|
||||
LAUNCHER_BUTTON_BORDER_WIDTH +
|
||||
DEFAULT_CONTENT_WIDTH;
|
||||
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
|
||||
public string thread_id { get; }
|
||||
|
||||
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -10,12 +11,6 @@ namespace mbts.spark {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
|
||||
: this(definition.CustomCase.type_id(), definition) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
|
||||
: this(name, new AlgoStructureKey(definition)) { }
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
@@ -31,31 +26,22 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
[ProtoMember(1)]
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
|
||||
int[] s = new[] { (int)definition.CustomCase };
|
||||
_build_structure(definition, ref s, 1);
|
||||
_structure = s;
|
||||
}
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public CustomOrderDefinitionImmutable build_def() {
|
||||
int pos = 0;
|
||||
var mdef = new CustomOrderDefinition();
|
||||
_build_def(_structure, ref pos, ref mdef);
|
||||
return new CustomOrderDefinitionImmutable(mdef);
|
||||
}
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) =>
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
_structure.Length == other._structure.Length &&
|
||||
_structure.SequenceEqual(other._structure);
|
||||
|
||||
@@ -67,35 +53,105 @@ namespace mbts.spark {
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
|
||||
_build_structure(definition.CloneToMutable(), ref structure, pos);
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
int del_mask = 1 << (31 - di);
|
||||
structure[pos++] = (int)child_algo.CustomCase | del_mask;
|
||||
_build_structure(child_algo, ref structure, pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ArrayHelper.trim(ref structure, pos);
|
||||
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
|
||||
for (int i = pos; i < structure.Length; i++) {
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
int mask = 1 << (31 - di);
|
||||
if ((structure[i] & mask) == mask) {
|
||||
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
|
||||
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
|
||||
pos++;
|
||||
_build_def(structure, ref pos, ref child_algo);
|
||||
cad[di].assign_child(algo, child_algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,18 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void update_persisted_definition_wrappers(ObservableKeyedCollection<IAlgoVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
|
||||
foreach (var d in update.removed)
|
||||
target.Remove(d.key);
|
||||
foreach (var d in update.changed)
|
||||
target[d.key].load_from(d.value);
|
||||
foreach (var d in update.added) {
|
||||
if (target.TryGetValue(d.key, out IAlgoVM def)) {
|
||||
def.load_from(d.value);
|
||||
} else target.Add(new AlgoVMWrapper(factory(d)));
|
||||
}
|
||||
}
|
||||
|
||||
static class _CompareUtils {
|
||||
public static int compare_modes(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
|
||||
_compare_modes(a.key.mode, b.key.mode);
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace mbts.spark {
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
@@ -54,29 +55,43 @@ namespace mbts.spark {
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format);
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
if (connect)
|
||||
rv.connect();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
_model = model;
|
||||
_permissions_mask.mask = init_permissions;
|
||||
_permissions_mask.raw = init_permissions;
|
||||
var resources = new CompositeDisposable();
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_values)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
else if (model.algo_model.templates_enabled()) {
|
||||
resources.Add(_templates.Subscribe((s, e) => {
|
||||
if (_templates.Count == 0) {
|
||||
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
} else {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
}
|
||||
}));
|
||||
}
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
|
||||
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
|
||||
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
_editing_permissions = _get_permissions();
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
var resources = new CompositeDisposable();
|
||||
// init backing props
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
@@ -87,7 +102,7 @@ namespace mbts.spark {
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
@@ -113,7 +128,6 @@ namespace mbts.spark {
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
|
||||
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
|
||||
if (vm == null)
|
||||
return Disposable.empty;
|
||||
@@ -152,16 +166,22 @@ namespace mbts.spark {
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(f);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}));
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeHookLatest(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
_dispose = resources;
|
||||
on_editing_permissions(_get_permissions());
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
@@ -169,11 +189,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void on_editing_permissions(AlgoEditingPermissions eps) {
|
||||
_editing_permissions = eps;
|
||||
foreach (var vm in algo_roots)
|
||||
vm.set_permissions(eps);
|
||||
}
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||
void on_local_changed() {
|
||||
@@ -230,6 +245,9 @@ namespace mbts.spark {
|
||||
// visual states
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
|
||||
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
|
||||
|
||||
#region IAlgoSelector
|
||||
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
|
||||
@@ -248,7 +266,7 @@ namespace mbts.spark {
|
||||
|
||||
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
var orig_algo = algo_root?.id;
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
|
||||
if (!try_select(orig_algo) && !try_select(default_algo))
|
||||
algo_root = algo_roots.FirstOrDefault();
|
||||
//
|
||||
@@ -298,6 +316,19 @@ namespace mbts.spark {
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_from(def);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_from(def);
|
||||
algo_root = vm;
|
||||
} else {
|
||||
algo_roots.Add(vm = _mk_algo_root(def));
|
||||
algo_root = vm;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(AlgoData data) {
|
||||
_assert_can_load(data.key);
|
||||
if (algo_root != null)
|
||||
@@ -309,7 +340,7 @@ namespace mbts.spark {
|
||||
|
||||
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
|
||||
public AlgoDefinitionVM to_persisted(AlgoData def) =>
|
||||
new AlgoDefinitionVM(_model, def);
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
@@ -322,16 +353,6 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
|
||||
|
||||
AlgoEditingPermissions _get_permissions() {
|
||||
var permissions = AlgoEditingPermissions.None;
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
|
||||
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
|
||||
}
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
|
||||
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
return permissions;
|
||||
}
|
||||
|
||||
void _assert_can_load(AlgoKey data_key) {
|
||||
if (key.mode != data_key.mode)
|
||||
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
|
||||
@@ -362,7 +383,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
|
||||
IObserver<SecurityMD?>,
|
||||
IObserver<SecurityImmutable?>,
|
||||
IObserver<OrdersModel>,
|
||||
IObserver<AlgoEditingPermissions> {
|
||||
static readonly HashSet<string>
|
||||
_algo_root_echo_props = new HashSet<string> {
|
||||
nameof(is_expanded),
|
||||
nameof(editing_permissions),
|
||||
nameof(definition),
|
||||
nameof(display_name)
|
||||
},
|
||||
_value_echo_props = new HashSet<string> {
|
||||
nameof(AlgoDefinitionVM.name),
|
||||
nameof(AlgoDefinitionVM.abbreviation),
|
||||
nameof(AlgoDefinitionVM.badge_fill),
|
||||
nameof(AlgoDefinitionVM.can_undo),
|
||||
nameof(AlgoDefinitionVM.can_redo)
|
||||
};
|
||||
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
|
||||
readonly DisposableContainer _value_hooks = new DisposableContainer();
|
||||
Action _definition_changed = () => { };
|
||||
_ObservedData? _data_cache;
|
||||
AlgoDefinitionVM _value;
|
||||
|
||||
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
|
||||
value = algo_definition_vm;
|
||||
_dispose = new CompositeDisposable(_value_hooks) {
|
||||
_child_algo_selectors.clear_collections,
|
||||
() => _value?.Dispose()
|
||||
};
|
||||
}
|
||||
|
||||
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
|
||||
|
||||
public AlgoDefinitionVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (_value != value) {
|
||||
_value?.Dispose();
|
||||
_value = value;
|
||||
_value_hooks.replace(_set_hooks(value));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
public AlgoKey id => _value.key;
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
|
||||
public bool is_bts_type => _algo_root?.is_bts_type ?? false;
|
||||
public OrderMetadata meta {
|
||||
get {
|
||||
if (_value != null) {
|
||||
if (_value.custom_badge_color.enabled)
|
||||
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
|
||||
else if (_value.algo_root != null)
|
||||
return _value.algo_root.meta;
|
||||
}
|
||||
return OrderDataHelper.get_metadata(order_type);
|
||||
}
|
||||
}
|
||||
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
|
||||
public bool can_expand => _algo_root?.can_expand ?? false;
|
||||
public bool is_expanded {
|
||||
get => _value.algo_root?.is_expanded ?? false;
|
||||
set {
|
||||
if (_value != null)
|
||||
_value.is_expanded = value;
|
||||
}
|
||||
}
|
||||
public string display_name => _value?.name ?? string.Empty;
|
||||
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
|
||||
public dynamic children_vm => _algo_root?.children_vm;
|
||||
|
||||
IAlgoVM _algo_root => _value?.algo_root;
|
||||
|
||||
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
|
||||
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
|
||||
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
|
||||
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
|
||||
|
||||
IDisposable _set_hooks(AlgoDefinitionVM vm) {
|
||||
var algo_root_subs = new DisposableContainer();
|
||||
vm.PropertyChanged += on_value_prop_changed;
|
||||
return new CompositeDisposable(
|
||||
vm.observe_algo_root().Subscribe(r => {
|
||||
algo_root_subs.replace(null);
|
||||
_child_algo_selectors.clear_collections();
|
||||
if (r != null) {
|
||||
if (_data_cache.HasValue) {
|
||||
_data_cache.Value.forward_to(r);
|
||||
}
|
||||
_child_algo_selectors.add_collection(r.child_algo_selectors);
|
||||
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
|
||||
algo_root_subs.replace(new CompositeDisposable(
|
||||
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
|
||||
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
|
||||
});
|
||||
}
|
||||
}),
|
||||
algo_root_subs) {
|
||||
() => vm.PropertyChanged -= on_value_prop_changed
|
||||
};
|
||||
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_algo_root_echo_props.Contains(e.PropertyName))
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_value_echo_props.Contains(e.PropertyName))
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
}
|
||||
|
||||
// permissions
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
|
||||
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
// model
|
||||
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
|
||||
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<OrdersModel>.OnError(Exception error) { }
|
||||
void IObserver<OrdersModel>.OnCompleted() { }
|
||||
// security
|
||||
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
|
||||
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityImmutable?>.OnCompleted() { }
|
||||
// security md
|
||||
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
|
||||
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<SecurityMD?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityMD?>.OnCompleted() { }
|
||||
|
||||
struct _ObservedData {
|
||||
public readonly AlgoEditingPermissions permissions;
|
||||
public readonly OrdersModel model;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly SecurityMD? security_md;
|
||||
|
||||
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
|
||||
this.permissions = permissions;
|
||||
this.model = model;
|
||||
this.security = security;
|
||||
this.security_md = security_md;
|
||||
}
|
||||
|
||||
public void forward_to(IAlgoVM recipient) {
|
||||
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
|
||||
if (model != null)
|
||||
(recipient as IObserver<OrdersModel>)?.OnNext(model);
|
||||
if (security_md.HasValue)
|
||||
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
|
||||
if (security.HasValue)
|
||||
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
|
||||
}
|
||||
|
||||
public _ObservedData with(AlgoEditingPermissions? permissions) =>
|
||||
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(OrdersModel model) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(SecurityImmutable? security) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(SecurityMD? security_md) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.AlgoEditorView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
|
||||
<!--Default Styles-->
|
||||
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
|
||||
<Setter Property="Padding" Value="8"/>
|
||||
</Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
|
||||
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
Grid.Row="2"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Width="140"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
ItemsSource="{Binding algos_view}"
|
||||
SelectedItem="{Binding algo_root}"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.IsEnabled>
|
||||
<Binding Path="editing_permissions">
|
||||
<Binding.Converter>
|
||||
<cv:EnumHasFlagsConverter>
|
||||
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</ComboBox.IsEnabled>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoEditorView : UserControl {
|
||||
public AlgoEditorView() {
|
||||
InitializeComponent();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
// local methods
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,13 @@ using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
@@ -42,7 +45,7 @@ namespace mbts.spark {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
@@ -98,16 +101,17 @@ namespace mbts.spark {
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
_rows.update(t);
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null) {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
@@ -120,8 +124,8 @@ namespace mbts.spark {
|
||||
};
|
||||
rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
fills_source.Connect()
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
);
|
||||
// local methods
|
||||
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
|
||||
@@ -153,12 +157,12 @@ namespace mbts.spark {
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
|
||||
_sort_manager.create();
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
|
||||
/// </summary>
|
||||
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
|
||||
bool _disposed;
|
||||
FillsVM _value;
|
||||
|
||||
public event Action<bool> OnDisposed;
|
||||
|
||||
public FillsVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (value != _value) {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
Disposable.replace(ref _value, value);
|
||||
if (_value != null) {
|
||||
rows.add_collection(_value.rows);
|
||||
columns.add_collection(_value.columns);
|
||||
} else {
|
||||
rows.clear_collections();
|
||||
columns.clear_collections();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
|
||||
public ObservableAggregateCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableAggregateCollection<DIYGrid.ColumnBase>();
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
value = null;
|
||||
OnDisposed?.Invoke(true);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
if (!_disposed)
|
||||
_value?.on_sorting(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
namespace mbts.spark {
|
||||
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string window_type_name = "Fills";
|
||||
const string _thread_id = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -33,6 +32,7 @@ namespace mbts.spark {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace bts.shell {
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_md_auxiliary_model = md_auxiliary_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public MiniOrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,13 @@ using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
|
||||
string sort_member_path { get; }
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
@@ -78,7 +77,6 @@ namespace mbts.spark {
|
||||
Add(symbol_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Symbol,
|
||||
DefaultColumnHeaders.Symbol,
|
||||
nameof(OrderBookRowVM.symbol),
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
@@ -92,7 +90,6 @@ namespace mbts.spark {
|
||||
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.Time,
|
||||
"Time",
|
||||
nameof(OrderBookRowVM.time_created),
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -104,7 +101,6 @@ namespace mbts.spark {
|
||||
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.LastChange,
|
||||
"Last Change",
|
||||
nameof(OrderBookRowVM.last_change),
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -116,7 +112,6 @@ namespace mbts.spark {
|
||||
Add(user_column = new SimpleTextColumn<string[]>(
|
||||
Columns.User,
|
||||
DefaultColumnHeaders.User,
|
||||
nameof(OrderBookRowVM.user),
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
@@ -129,7 +124,6 @@ namespace mbts.spark {
|
||||
Add(account_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Account,
|
||||
DefaultColumnHeaders.Account,
|
||||
nameof(OrderBookRowVM.account),
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
|
||||
@@ -144,7 +138,6 @@ namespace mbts.spark {
|
||||
Add(exchange_column = new SimpleTextColumn<Venue[]>(
|
||||
Columns.Exchange,
|
||||
DefaultColumnHeaders.Exchange,
|
||||
nameof(OrderBookRowVM.exchange),
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
|
||||
@@ -157,7 +150,6 @@ namespace mbts.spark {
|
||||
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
|
||||
Columns.TimeInForce,
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
nameof(OrderBookRowVM.time_in_force),
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
|
||||
@@ -176,14 +168,13 @@ namespace mbts.spark {
|
||||
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
|
||||
Columns.SubType,
|
||||
"Sub Type",
|
||||
nameof(OrderBookRowVM.order_sub_type),
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
SubTypeFilterTemplateKey,
|
||||
OrderBookGridView.SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
@@ -205,7 +196,6 @@ namespace mbts.spark {
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
nameof(OrderBookRowVM.price),
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -221,7 +211,6 @@ namespace mbts.spark {
|
||||
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.AvgFilled,
|
||||
"Avg Price",
|
||||
nameof(OrderBookRowVM.avg_price),
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -233,7 +222,6 @@ namespace mbts.spark {
|
||||
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.LastPrice,
|
||||
"Last Filled",
|
||||
nameof(OrderBookRowVM.last_price),
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -245,7 +233,6 @@ namespace mbts.spark {
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
nameof(OrderBookRowVM.status),
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
|
||||
@@ -263,7 +250,6 @@ namespace mbts.spark {
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
Columns.SecurityKey,
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
nameof(OrderBookRowVM.security_key),
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -275,7 +261,6 @@ namespace mbts.spark {
|
||||
Add(order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.OrderKey,
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
nameof(OrderBookRowVM.order_key),
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -287,7 +272,6 @@ namespace mbts.spark {
|
||||
Add(parent_key_column = new SimpleTextColumn<string>(
|
||||
Columns.ParentKey,
|
||||
"Parent Key",
|
||||
nameof(OrderBookRowVM.parent_order_key),
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -295,6 +279,17 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
// attached target order key
|
||||
Add(attached_target_order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.AttachedTargetOrderKey,
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order.attached_target_order_key())));
|
||||
}
|
||||
|
||||
public event Action filter_changed;
|
||||
@@ -323,10 +318,13 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<string> security_key_column { get; }
|
||||
public SimpleTextColumn<string> order_key_column { get; }
|
||||
public SimpleTextColumn<string> parent_key_column { get; }
|
||||
public SimpleTextColumn<string> attached_target_order_key_column { get; }
|
||||
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
|
||||
new FilterStates {
|
||||
order_key_regex = order_key_column.filter.get_state(),
|
||||
parent_key_regex = parent_key_column.filter.get_state(),
|
||||
attached_target_order_key_regex = attached_target_order_key_column.filter.get_state(),
|
||||
time_created = time_created_column.filter.get_state(),
|
||||
users = user_column.filter.get_state(),
|
||||
accounts = account_column.filter.get_state(),
|
||||
@@ -364,6 +362,8 @@ namespace mbts.spark {
|
||||
time_in_force_column.filter.load_state(fs.tifs);
|
||||
exchange_column.filter.load_state(fs.exchanges);
|
||||
order_key_column.filter.load_state(fs.order_key_regex);
|
||||
parent_key_column.filter.load_state(fs.parent_key_regex);
|
||||
attached_target_order_key_column.filter.load_state(fs.attached_target_order_key_regex);
|
||||
avg_price_column.filter.load_state(fs.avg_price);
|
||||
last_price_column.filter.load_state(fs.last_price);
|
||||
status_column.filter.load_state(fs.statuses);
|
||||
@@ -480,13 +480,12 @@ namespace mbts.spark {
|
||||
protected readonly Func<OrderBookRowVM, string> _get_text;
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
_get_text = get_text;
|
||||
sort_member_path = sort_prop;
|
||||
this.filter = filter;
|
||||
_filter_template = filter_template;
|
||||
_cached_sort_direction = SortDirection;
|
||||
@@ -515,9 +514,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
|
||||
@@ -527,7 +526,7 @@ namespace mbts.spark {
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
|
||||
@@ -536,9 +535,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.quantity);
|
||||
}
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
|
||||
@@ -547,9 +544,7 @@ namespace mbts.spark {
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
|
||||
}
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
|
||||
@@ -562,7 +557,6 @@ namespace mbts.spark {
|
||||
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
sort_member_path = nameof(OrderBookRowVM.order_type);
|
||||
font_weight_binding = RenderUtils.get_font_weight_binding();
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
@@ -587,8 +581,7 @@ namespace mbts.spark {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = DIYGrid.DefaultColumnWidths.LastMessage;
|
||||
sort_member_path = nameof(OrderBookRowVM.last_msg);
|
||||
Width = DefaultColumnWidths.LastMessage;
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
@@ -616,7 +609,6 @@ namespace mbts.spark {
|
||||
this.filter = filter;
|
||||
Header = DefaultColumnHeaders.Security;
|
||||
Width = DefaultColumnWidths.Security;
|
||||
sort_member_path = nameof(OrderBookRowVM.security);
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
@@ -634,7 +626,7 @@ namespace mbts.spark {
|
||||
toggle_button.SetBinding(ToggleButton.IsCheckedProperty, new Binding(nameof(OrderBookRowVM.is_expanded)));
|
||||
toggle_button.SetBinding(ToggleButton.MarginProperty, new Binding(nameof(OrderBookRowVM.depth_level)) { Converter = _margin_converter });
|
||||
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, OrderBookGridView.ExpandCollapseToggleStyleKey);
|
||||
//
|
||||
var security_view = new TextBlock { Foreground = font_brush };
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
@@ -651,6 +643,7 @@ namespace mbts.spark {
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.Children.Add(toggle_button);
|
||||
grid.Children.Add(security_view);
|
||||
|
||||
grid.VerticalAlignment = VerticalAlignment.Center;
|
||||
//
|
||||
var border = new Border { Child = grid };
|
||||
|
||||
@@ -6,14 +6,16 @@ using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -44,12 +46,26 @@ namespace mbts.spark {
|
||||
modify_order_price?.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
//// This takes care of the case where the OB has all rows visible
|
||||
//// and an order is cancelled outside of the OB (say via the ladder).
|
||||
//// Since the OB window Commands only revaluate their CanExecuteChanged
|
||||
//// when the OrderBookVM.selected_order_changes, this emulates the selected
|
||||
//// order changing since the current_row setter invokes the selected_order_changed
|
||||
//// handler
|
||||
public IObservable<OrderSecurityAlgoMeta> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
this.invalidated -= OrderBookRowVM_invalidated;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
|
||||
|
||||
// Core
|
||||
public OrderSecurityAlgoMeta order_tuple { get; private set; }
|
||||
@@ -127,8 +143,8 @@ namespace mbts.spark {
|
||||
public ICommand toggle_command => _toggle_expanded_cmd ?? (
|
||||
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
|
||||
|
||||
ICommand DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd ??
|
||||
(_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
public ICommand show_order_modify_cmd =>
|
||||
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
|
||||
public ICommand cancel_command => _cancel_cmd ??
|
||||
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working));
|
||||
@@ -170,10 +186,10 @@ namespace mbts.spark {
|
||||
public void update(OrderSecurityAlgoMeta order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
bool had_security = _security.HasValue;
|
||||
var old_meta = this.order_tuple.meta;
|
||||
var old_order_tuple = this.order_tuple;
|
||||
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
|
||||
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
|
||||
OrderRecordImmutable old_order = order;
|
||||
OrderRecordImmutable old_order = old_order_tuple.order;
|
||||
this.order_tuple = order_tuple;
|
||||
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
|
||||
bool invalidate = !(
|
||||
@@ -200,10 +216,12 @@ namespace mbts.spark {
|
||||
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
|
||||
invalidate = true;
|
||||
}
|
||||
if (old_meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
_badge_fill = null;
|
||||
RaisePropertyChanged(nameof(badge_fill));
|
||||
}
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
if (old_order.status() != order.status()) {
|
||||
invalidate = true;
|
||||
RaisePropertyChanged(nameof(status));
|
||||
@@ -213,6 +231,7 @@ namespace mbts.spark {
|
||||
}
|
||||
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
|
||||
_show_order_modify_cmd.RaiseCanExecuteChanged();
|
||||
updated?.Invoke((old_order_tuple, order_tuple));
|
||||
if (invalidate)
|
||||
raise_invalidated();
|
||||
}
|
||||
|
||||
@@ -19,12 +19,11 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using _GridSettingsManager =
|
||||
using _GridSettingsManager =
|
||||
mbts.ui.GridSettingsManager<
|
||||
mbts.spark.OrderBookRowVM,
|
||||
bts.api.orderman.OrderSecurity,
|
||||
bts.shell.INotificationServiceDataCache,
|
||||
mbts.OrderRecordTuplePredicate,
|
||||
mbts.spark.OrderBookRowVM,
|
||||
bts.api.orderman.OrderSecurity,
|
||||
mbts.OrderRecordTuplePredicate,
|
||||
bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -54,6 +53,7 @@ namespace mbts.spark {
|
||||
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
|
||||
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
|
||||
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, rows, core_columns);
|
||||
|
||||
_resources = new List<IDisposable> {
|
||||
model.on_order_tuples.OnDispatcher().Subscribe(_row_collection.update),
|
||||
grid_settings_manager,
|
||||
@@ -62,7 +62,12 @@ namespace mbts.spark {
|
||||
_row_collection.CollectionChanged -= _row_collection_CollectionChanged;
|
||||
Disposable.dispose(_row_collection);
|
||||
}),
|
||||
_columns
|
||||
_columns,
|
||||
_current_row.SubscribeHookLatest(
|
||||
r => r.newval.subscribe_data().Subscribe(
|
||||
data => selected_order_changed?.Invoke(data.order)
|
||||
)
|
||||
)
|
||||
};
|
||||
if (settings_manager != null) {
|
||||
_resources.Add(settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager));
|
||||
@@ -231,21 +236,25 @@ namespace mbts.spark {
|
||||
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
}
|
||||
return new _GridSettingsManager(
|
||||
owner_title, bus, notification_service, model, rows, columns,
|
||||
owner_title, bus, model, rows, columns,
|
||||
() => new OrderRecordTuplePredicate(),
|
||||
() => {
|
||||
var pvm = PredicateBuilderVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = pvm.predicate_types.FirstOrDefault();
|
||||
return pvm;
|
||||
},
|
||||
col_id => {
|
||||
var pvm = PredicateBuilderVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
pvm.selected_predicate_type = _corresponding_predicate_type(EnumHelper.parse_or_default<Columns>(col_id));
|
||||
return pvm;
|
||||
},
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_builder, a.cell_animations, a.row_animations, a.sound_notifications))),
|
||||
(notification_service == null) ? null : notification_service.data_cache,
|
||||
(gen, anims) => new OrderBookGridSettings(gen, anims.Select(a => new OrderRecordTuplePredicateAnimations(a.predicate_data, a.cell_animations, a.row_animations, a.sound_notifications))),
|
||||
p => p.create_predicate(notification_service.data_cache),
|
||||
p => p.create_notification_predicate(notification_service.data_cache),
|
||||
notification_service == null ? new Func<IPredicate<OrderSecurity>, SoundNotification, IDisposable>((p, sn) => Disposable.empty) : notification_service.link_to_order,
|
||||
notification_service == null ? new Func<INotificationPredicate<OrderSecurity>, IDisposable>(t => Disposable.empty) : notification_service.link_to_order,
|
||||
col_settings_flags);
|
||||
}
|
||||
|
||||
@@ -275,4 +284,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
<UserControl x:Class="mbts.spark.OrderBookView" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:uc="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="1080"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.RowTemplateKey}">
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows}"
|
||||
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
|
||||
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
|
||||
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="RenderTransform" TargetName="ExpandPath">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
|
||||
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -2,27 +2,31 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
//using IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
|
||||
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
|
||||
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
|
||||
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook", _default_title = "Order Book";
|
||||
const string _window_type = "OrderBook", _default_title = "Order Book";
|
||||
readonly INotificationService _notification_service;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
IDisposable _attachment_exit_sub;
|
||||
OrderBookWindowFactory.OrderBookStartupData _state;
|
||||
OrdersModel _model;
|
||||
OrderBookVM _vm;
|
||||
@@ -30,17 +34,6 @@ namespace mbts.spark {
|
||||
OrderBookWindowFactory _factory;
|
||||
SparkCommand[] _commands;
|
||||
|
||||
SparkCommand _show_fills_command
|
||||
, _modify_order_command
|
||||
, _cancel_buy_orders_command
|
||||
, _cancel_sell_orders_command
|
||||
, _cancel_all_orders_command
|
||||
, _cancel_selected_orders_command
|
||||
, _recreate_selected_orders_command
|
||||
, _toggle_collapse_all_command
|
||||
, _toggle_expand_all_command
|
||||
, _show_grid_settings_command;
|
||||
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
||||
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
|
||||
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
@@ -49,6 +42,7 @@ namespace mbts.spark {
|
||||
_notification_service = notification_service;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
|
||||
}
|
||||
|
||||
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm?.create();
|
||||
@@ -61,10 +55,11 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id => ORDERBOOK_THREAD_ID;
|
||||
public string thread_id { get; }
|
||||
|
||||
public override void Dispose() {
|
||||
_commands.OfType<IDisposable>().DisposeAll();
|
||||
Disposable.dispose(ref _attachment_exit_sub);
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
|
||||
@@ -73,54 +68,86 @@ namespace mbts.spark {
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
_show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
var show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
_modify_order_command = SparkCommands.ShowModifyOrder.complete(
|
||||
() => _vm.current_order.Value.Key,
|
||||
can_execute: () => {
|
||||
if (_vm.current_order.HasValue) {
|
||||
return _vm.current_order.Value.State.Status == bts.api.orderman.OrderStatus.Working;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
var toggle_right_panel_is_visible_command = new SparkCommandCommand(
|
||||
get_toggle_algo_editor_cmd_title(),
|
||||
Icons.ModifyOrder,
|
||||
_vm.toggle_algo_editor_is_visible_cmd,
|
||||
new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
_cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
|
||||
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
|
||||
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Buy).Select(o => o.order.Key)),
|
||||
wbids => wbids.Any()
|
||||
wbids => wbids.Any(),
|
||||
() => {
|
||||
var resp = BTSMessageBox.Show(
|
||||
"Are you sure you want to cancel all BUY orders?",
|
||||
"Cancel Buy Orders",
|
||||
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
|
||||
MessageBoxImage.Question);
|
||||
return resp == BTSMessageBoxButtons.Yes;
|
||||
}
|
||||
);
|
||||
|
||||
_cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
|
||||
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
|
||||
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Sell).Select(o => o.order.Key)),
|
||||
wasks => wasks.Any()
|
||||
wasks => wasks.Any(),
|
||||
() => {
|
||||
var resp = BTSMessageBox.Show(
|
||||
"Are you sure you want to cancel all SELL orders?",
|
||||
"Cancel Sell Orders",
|
||||
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
|
||||
MessageBoxImage.Question);
|
||||
return resp == BTSMessageBoxButtons.Yes;
|
||||
}
|
||||
);
|
||||
|
||||
_cancel_all_orders_command = SparkCommands.CancelOrders.complete(
|
||||
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
|
||||
_model.working_orders.Select(os => os.Select(o => o.Key)),
|
||||
worders => worders.Any()
|
||||
worders => worders.Any(),
|
||||
() => {
|
||||
var resp = BTSMessageBox.Show(
|
||||
"Are you sure you want to cancel ALL orders?",
|
||||
"Cancel All Orders",
|
||||
BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No,
|
||||
MessageBoxImage.Question);
|
||||
return resp == BTSMessageBoxButtons.Yes;
|
||||
}
|
||||
);
|
||||
|
||||
_cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
|
||||
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
|
||||
() => _vm.selected_orders.Where(x => x.status() == OrderStatus.Working).Select(x => x.Key),
|
||||
can_execute: () => _vm.selected_orders.Any(x => x.status() == OrderStatus.Working)
|
||||
);
|
||||
|
||||
_recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
|
||||
var recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
|
||||
() =>
|
||||
_vm.selected_orders.Where(x => _passes_recreate_search_criteria(x))
|
||||
_vm.selected_orders.Where(x => passes_recreate_search_criteria(x))
|
||||
.Select(x => (x.Def, x.State.Price, x.State.Quantity - x.State.FilledQuantity)),
|
||||
can_execute: () => _vm.selected_orders.Any(x => _passes_recreate_search_criteria(x))
|
||||
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
|
||||
);
|
||||
|
||||
_toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
var attach_selected_orders_command = new SparkCommandCommand(
|
||||
"Attach To Selected Orders",
|
||||
Icons.AttachAlgos,
|
||||
new DelegateCommand(_vm.begin_attach_algos, () => _vm.selected_rows.Count > 0),
|
||||
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
var detach_selected_orders_command = new SparkCommandCommand(
|
||||
"Detach And Cancel Selected Orders",
|
||||
Icons.DetachAlgos,
|
||||
new DelegateCommand(_vm.detach_selected_algos, () => _vm.selected_rows.Count > 0),
|
||||
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
Icons.RightArrow,
|
||||
_vm.collapse_all_rows_command);
|
||||
|
||||
_toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
var toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
Icons.DownArrow,
|
||||
_vm.expand_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
@@ -145,31 +172,44 @@ namespace mbts.spark {
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
_show_fills_command,
|
||||
_modify_order_command,
|
||||
_cancel_selected_orders_command,
|
||||
_cancel_buy_orders_command,
|
||||
_cancel_sell_orders_command,
|
||||
_cancel_all_orders_command,
|
||||
_recreate_selected_orders_command,
|
||||
_toggle_collapse_all_command,
|
||||
_toggle_expand_all_command,
|
||||
_show_grid_settings_command
|
||||
_commands = new SparkCommand[] {
|
||||
show_fills_command,
|
||||
toggle_right_panel_is_visible_command,
|
||||
cancel_selected_orders_command,
|
||||
cancel_buy_orders_command,
|
||||
cancel_sell_orders_command,
|
||||
cancel_all_orders_command,
|
||||
recreate_selected_orders_command,
|
||||
attach_selected_orders_command,
|
||||
detach_selected_orders_command,
|
||||
toggle_collapse_all_command,
|
||||
toggle_expand_all_command,
|
||||
show_grid_settings_command
|
||||
};
|
||||
_vm.selected_order_changed += _vm_selected_order_changed;
|
||||
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.algo_editor_is_visible))
|
||||
.Subscribe(_ => toggle_right_panel_is_visible_command.title = get_toggle_algo_editor_cmd_title());
|
||||
_vm.selected_order_changed += vm_selected_order_changed;
|
||||
// local methods
|
||||
string get_toggle_algo_editor_cmd_title() =>
|
||||
$"{(_vm.algo_editor_is_visible ? "Hide" : "Show")} Order Params";
|
||||
|
||||
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
|
||||
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
|
||||
!record.has_parent() &&
|
||||
record.State.Quantity > record.State.FilledQuantity;
|
||||
|
||||
void vm_selected_order_changed(OrderRecordImmutable? e) {
|
||||
cancel_selected_orders_command.raiseCanExecuteChanged();
|
||||
recreate_selected_orders_command.raiseCanExecuteChanged();
|
||||
attach_selected_orders_command.raiseCanExecuteChanged();
|
||||
detach_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _passes_recreate_search_criteria(OrderRecordImmutable record) {
|
||||
return (record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error)
|
||||
&& !record.has_parent()
|
||||
&& record.State.Quantity > record.State.FilledQuantity;
|
||||
}
|
||||
|
||||
private void _vm_selected_order_changed(OrderRecordImmutable? e) {
|
||||
_modify_order_command.raiseCanExecuteChanged();
|
||||
_cancel_selected_orders_command.raiseCanExecuteChanged();
|
||||
_recreate_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
|
||||
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
|
||||
_vm.is_attaching_algos = false;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace bts.shell {
|
||||
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookWindowFactory.OrderBookStartupData> {
|
||||
@@ -49,24 +50,57 @@ namespace bts.shell {
|
||||
|
||||
[ProtoContract]
|
||||
public class OrderBookStartupData {
|
||||
//[ProtoMember(5)]
|
||||
//public readonly List<SortingCriteria> sort_criteria;
|
||||
const bool _default_algo_editor_is_visible = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_fills_slave_grid_height = 138;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid;
|
||||
// algo editor is visible
|
||||
[ProtoMember(16)]
|
||||
[DefaultValue(_default_algo_editor_is_visible)]
|
||||
public bool algo_editor_is_visible { get; set; }
|
||||
// right panel width
|
||||
[ProtoMember(17)]
|
||||
[DefaultValue(_default_right_panel_width)]
|
||||
public double right_panel_width { get; set; }
|
||||
// fills slave grid height
|
||||
[ProtoMember(18)]
|
||||
[DefaultValue(_default_fills_slave_grid_height)]
|
||||
public double fills_slave_grid_height { get; set; }
|
||||
// fills slave grid is visible
|
||||
[ProtoMember(19)]
|
||||
public bool fills_slave_grid_is_visible { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
public OrderBookStartupData() { }
|
||||
public OrderBookStartupData() {
|
||||
algo_editor_is_visible = _default_algo_editor_is_visible;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
fills_slave_grid_height = _default_fills_slave_grid_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, bool algo_editor_is_visible = false, double right_panel_width = 0, double fills_slave_grid_height = 0, bool fills_slave_grid_is_visible = false, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.algo_editor_is_visible = algo_editor_is_visible;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_slave_grid_height = fills_slave_grid_height;
|
||||
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
this.grid = grid;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
@@ -83,9 +117,12 @@ namespace bts.shell {
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates {
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
}, _default_general_grid, use_defaults: true);
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
_default_general_grid,
|
||||
_default_algo_editor_is_visible,
|
||||
_default_right_panel_width,
|
||||
_default_fills_slave_grid_height,
|
||||
use_defaults: true);
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
|
||||
|
||||
@@ -115,6 +152,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -145,11 +183,12 @@ namespace bts.shell {
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key
|
||||
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
@@ -208,11 +247,17 @@ namespace bts.shell {
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
@@ -230,6 +275,32 @@ namespace bts.shell {
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols));
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -240,7 +311,7 @@ namespace bts.shell {
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
|
||||
public OrderBookGridSettings() { }
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, double right_panel_width = double.NaN) {
|
||||
this.general = general;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookView {
|
||||
public class OrderBookAttachmentTargetAdorner : DIYGridRowHighlighterAdorner, IDisposable {
|
||||
IDisposable _attach_op;
|
||||
bool _is_active, _is_disposed;
|
||||
|
||||
#region IsActive
|
||||
public static readonly DependencyProperty IsActiveProperty =
|
||||
WPFHelper.create_dp<bool, OrderBookAttachmentTargetAdorner>(
|
||||
nameof(IsActive),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => {
|
||||
o._is_active = v;
|
||||
Disposable.dispose(ref o._attach_op);
|
||||
if (v && !o._is_disposed) {
|
||||
o.IsHitTestVisible = true;
|
||||
o._attach_op = Disposable.Create(
|
||||
o.SubscribePreviewMouseDown((s, e) => {
|
||||
if (e.ChangedButton == MouseButton.Left) {
|
||||
if (e.ClickCount > 1 && o.TargetRow is OrderBookRowVM r) {
|
||||
e.Handled = true;
|
||||
o._raise_target_row_selected(r);
|
||||
}
|
||||
}
|
||||
}),
|
||||
o.AdornedElement.SubscribeMouseHoverRow<OrderBookRowVM>().Subscribe(row => o.TargetRow = row));
|
||||
} else {
|
||||
o.IsHitTestVisible = false;
|
||||
o.TargetRow = null;
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
public bool IsActive {
|
||||
get => (bool)GetValue(IsActiveProperty);
|
||||
set => SetValue(IsActiveProperty, value);
|
||||
}
|
||||
|
||||
public event Action<OrderBookRowVM> TargetRowSelected;
|
||||
#endregion
|
||||
|
||||
public OrderBookAttachmentTargetAdorner(DIYGrid grid) : base(grid) { }
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
_is_disposed = true;
|
||||
Disposable.dispose(ref _attach_op);
|
||||
}
|
||||
}
|
||||
|
||||
void _raise_target_row_selected(OrderBookRowVM row) => TargetRowSelected?.Invoke(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<c:DIYGrid
|
||||
x:Class="mbts.spark.OrderBookGridView"
|
||||
x:ClassModifier="internal"
|
||||
x:Name="_grid"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Resources (Brushes, Colors, etc.)-->
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<!--DataTemplates-->
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookGridView.SubTypeFilterTemplateKey}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<!--Styles-->
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
|
||||
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="RenderTransform" TargetName="ExpandPath">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.RowStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
+33
-9
@@ -1,11 +1,7 @@
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookView : UserControl {
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
@@ -14,13 +10,41 @@ namespace mbts.spark {
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys {
|
||||
Security,
|
||||
Symbol,
|
||||
Time,
|
||||
LastChange,
|
||||
User,
|
||||
Account,
|
||||
Exchange,
|
||||
TimeInForce,
|
||||
OrderType,
|
||||
SubType,
|
||||
Side,
|
||||
Size,
|
||||
Price,
|
||||
Filled,
|
||||
AvgFilled,
|
||||
LastPrice,
|
||||
Status,
|
||||
LastMessage,
|
||||
SecurityKey,
|
||||
OrderKey,
|
||||
ParentKey,
|
||||
AttachedTargetOrderKey,
|
||||
Cancel,
|
||||
EditableQty,
|
||||
EditablePrice
|
||||
}
|
||||
|
||||
public OrderBookGridView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
<UserControl
|
||||
x:Class="mbts.spark.OrderBookView"
|
||||
x:ClassModifier="internal"
|
||||
x:Name="Root"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
Grid.Row="0"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="GridSplitterWidth">5</sys:Double>
|
||||
<sys:Double x:Key="FillsSlaveGridMinHeight">64</sys:Double>
|
||||
<SolidColorBrush x:Key="AlgoAttachmentBackground" Color="#55009500"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
||||
<Style TargetType="GridSplitter">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource Control.MediumGrayBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Control.MediumGrayBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Bid Price ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Bid Size ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Ask Price ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Ask Size ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelVisualStateConverter}">
|
||||
<Binding Path="DataContext.algo_editor_is_visible" Source="{u:DirectReference ContentRoot}"/>
|
||||
<Binding Path="DataContext.is_attaching_algos" Source="{u:DirectReference ContentRoot}"/>
|
||||
</MultiBinding>
|
||||
</u:StateDefinition>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
|
||||
<!--Normal State-->
|
||||
<VisualState Name="{x:Static Normal, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}"/>
|
||||
<!--Attach Orders State-->
|
||||
<VisualState Name="{x:Static AttachOrders, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
|
||||
<Storyboard>
|
||||
<!--Orders Grid-->
|
||||
<Int32Animation Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="1"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Pen}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="attachment_grid_overlay_border" Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<!--Right Panel-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_host" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_width_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<!--Modify Orders Controls-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_orders_button" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<!--Fills Slave Grid-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_toggle_hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<!--Attach Orders Controls-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_content_root" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<!--Modify Orders State-->
|
||||
<VisualState Name="{x:Static ModifyOrders, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
|
||||
<Storyboard>
|
||||
<!--Orders Grid-->
|
||||
<Int32Animation Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="1"/>
|
||||
<!--Right Panel-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_host" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_width_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding right_panel_width, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<sp:OrderBookGridView
|
||||
x:Name="orders_grid"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="Transparent"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
Cursor="Arrow"
|
||||
/>
|
||||
<Border
|
||||
x:Name="attachment_grid_overlay_border"
|
||||
Panel.ZIndex="1"
|
||||
Width="{Binding ActualWidth, ElementName=orders_grid}"
|
||||
Height="{Binding ActualHeight, ElementName=orders_grid}"
|
||||
Background="{StaticResource AlgoAttachmentBackground}"
|
||||
Opacity="0"
|
||||
IsHitTestVisible="False"
|
||||
/>
|
||||
<GridSplitter
|
||||
x:Name="right_panel_width_splitter"
|
||||
Grid.Column="1"
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Width="{StaticResource GridSplitterWidth}"
|
||||
BorderThickness="1,0,0,0"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<Grid x:Name="right_panel_host" Grid.Column="1" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="right_panel_content_root">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference right_panel_content_root}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.FillsSlaveGridStateConverter}">
|
||||
<Binding Path="DataContext.fills_slave_grid_is_visible" Source="{u:DirectReference right_panel_content_root}"/>
|
||||
<Binding Path="DataContext.is_attaching_algos" Source="{u:DirectReference right_panel_content_root}"/>
|
||||
</MultiBinding>
|
||||
</u:StateDefinition>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_grid" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterWidth}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition MinHeight="{StaticResource FillsSlaveGridMinHeight}" Height="{Binding fills_slave_grid_height, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}" MaxHeight="{Binding ActualHeight, ElementName=right_panel_content_root, Converter={x:Static sp:OrderBookView.FillsSlaveGridMaxHeightConverter}}"/>
|
||||
<RowDefinition MinHeight="{x:Static sp:OrderBookView.GridSplitterHeight}" Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="fills_toggle_hyperlink" Grid.Column="1">
|
||||
<TextBlock.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
|
||||
</TextBlock.Margin>
|
||||
<Hyperlink Command="{Binding toggle_fills_slave_grid_is_visible_cmd}">
|
||||
<Hyperlink.Inlines>
|
||||
<Run Text="{Binding current_row, Converter={x:Static sp:OrderBookView.OrderDescriptionConverter}, Mode=OneWay}"/>
|
||||
</Hyperlink.Inlines>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<c:DIYGrid x:Name="fills_grid" Grid.Column="1" Grid.Row="1" Columns="{Binding fills_vm.columns}" Rows="{Binding fills_vm.rows}" Visibility="Collapsed">
|
||||
<c:DIYGrid.Margin>
|
||||
<Thickness Bottom="{x:Static sp:OrderBookView.GridSplitterHeight}"/>
|
||||
</c:DIYGrid.Margin>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<GridSplitter
|
||||
x:Name="fills_order_params_splitter"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<GroupBox
|
||||
x:Name="order_params_group_box"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Padding="12"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Order Parameters"
|
||||
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
|
||||
>
|
||||
<usp:OrderParamsPanel
|
||||
x:Name="order_params_panel"
|
||||
DataContext="{Binding order_mod_vm}"
|
||||
Price="{Binding price}"
|
||||
Size="{Binding size_calculator.value}"
|
||||
Accounts="{Binding accounts}"
|
||||
SelectedAccount="{Binding selected_account}"
|
||||
BestBidPrice="{Binding bbo.bid}"
|
||||
BestBidSize="{Binding bbo.bid_size}"
|
||||
BestAskPrice="{Binding bbo.ask}"
|
||||
BestAskSize="{Binding bbo.ask_size}"
|
||||
SizePresets="{Binding size_calculator.buttons}"
|
||||
Security="{Binding security}"
|
||||
AlgoVM="{Binding algo_vm}"
|
||||
IsEnabled="{Binding IsEnabled, ElementName=modify_orders_button}"
|
||||
>
|
||||
<usp:OrderParamsPanel.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
|
||||
</usp:OrderParamsPanel.CommandBindings>
|
||||
</usp:OrderParamsPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
x:Name="attachment_content_root"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Padding="12"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Attached Order Parameters"
|
||||
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
|
||||
Margin="0,4,0,0"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<usp:OrderParamsPanel
|
||||
x:Name="attached_order_params_panel"
|
||||
DataContext="{Binding attach_vm}"
|
||||
Price="{Binding price}"
|
||||
Size="{Binding size_calculator.value}"
|
||||
Accounts="{Binding accounts}"
|
||||
SelectedAccount="{Binding selected_account}"
|
||||
BestBidPrice="{Binding bbo.bid}"
|
||||
BestBidSize="{Binding bbo.bid_size}"
|
||||
BestAskPrice="{Binding bbo.ask}"
|
||||
BestAskSize="{Binding bbo.ask_size}"
|
||||
SizePresets="{Binding size_calculator.buttons}"
|
||||
Security="{Binding security}"
|
||||
AlgoVM="{Binding algo_vm}"
|
||||
IsEnabled="{Binding IsEnabled, ElementName=send_attached_orders_button}"
|
||||
>
|
||||
<usp:OrderParamsPanel.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
|
||||
</usp:OrderParamsPanel.CommandBindings>
|
||||
</usp:OrderParamsPanel>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
<Canvas Grid.Row="1" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=order_params_group_box}" Height="30">
|
||||
<Button
|
||||
x:Name="modify_orders_button"
|
||||
Canvas.Top="{StaticResource DialogSkirtPadding.Top}"
|
||||
Command="{Binding modify_selected_algos_command}"
|
||||
>
|
||||
<Canvas.Left>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelDialogSkirtLeftAnchorConverter}">
|
||||
<Binding Path="Width" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Canvas.Left>
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Orders ({0})'}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
|
||||
<Setter Property="Text" Value="Modify Orders"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Canvas>
|
||||
<Canvas Grid.Row="1" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=attachment_content_root}" Height="30">
|
||||
<StackPanel
|
||||
x:Name="attach_orders_dialog_skirt"
|
||||
Orientation="Horizontal"
|
||||
Canvas.Top="{StaticResource DialogSkirtPadding.Top}"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<Canvas.Left>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelDialogSkirtLeftAnchorConverter}">
|
||||
<Binding Path="Width" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Canvas.Left>
|
||||
<Button x:Name="send_attached_orders_button" Command="{Binding send_attached_orders_cmd}" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Attach To Orders ({0})'}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
|
||||
<Setter Property="Text" Value="Attach To Orders"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button u:Behavior.CloseWindow="False" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_attach_orders_cmd}" Style="{StaticResource CancelButton}"/>
|
||||
</StackPanel>
|
||||
</Canvas>
|
||||
|
||||
</Grid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,151 @@
|
||||
//#define EXPAND_ATTACHMENT_PANEL
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookView : UserControl {
|
||||
public const double
|
||||
GridSplitterWidth = 5,
|
||||
GridSplitterHeight = 5,
|
||||
MinAlgoEditorHeight = 64,
|
||||
MinAttachOrderParamsPanelWidth = 340,
|
||||
AttachOrderParamsPanelWidthRatio = 0.33;
|
||||
|
||||
public static readonly IValueConverter
|
||||
OrderDescriptionConverter = ValueConverter.create<OrderBookRowVM, string>(row => row == null ? string.Empty : row.order.short_description(row.security)),
|
||||
FillsSlaveGridMaxHeightConverter = ValueConverter.create<double, double>(right_panel_height => right_panel_height - (GridSplitterHeight + MinAlgoEditorHeight)),
|
||||
FillsPanelVisualStateConverter = ValueConverter.create<bool, string>(show => show ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive),
|
||||
AttachmentTargetCursorConverter = ValueConverter.create<bool, Cursor>(is_active => is_active ? Cursors.Pen : Cursors.Arrow);
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
RightPanelVisualStateConverter = ValueConverter.create<bool, bool, string>((is_modifying, is_attaching) =>
|
||||
is_attaching ? VisualStateNames.RightPanel.AttachOrders :
|
||||
(is_modifying ? VisualStateNames.RightPanel.ModifyOrders : VisualStateNames.RightPanel.Normal)),
|
||||
RightPanelDialogSkirtLeftAnchorConverter = ValueConverter.create<double, double, double>((canvas_width, skirt_width) => canvas_width - skirt_width + GridSplitterWidth),
|
||||
FillsSlaveGridStateConverter = ValueConverter.create<bool, bool, string>((is_fills_grid_visible, is_attaching) => is_fills_grid_visible && !is_attaching ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive);
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
#if EXPAND_ATTACHMENT_PANEL
|
||||
(RightPanelState right_panel, IDisposable hooks)? attach_op = null;
|
||||
vm.begin_attaching += on_begin_attaching;
|
||||
vm.end_attaching += on_end_attaching;
|
||||
vm.SubscribeDisposed(() => {
|
||||
if (attach_op.HasValue) {
|
||||
vm.right_panel_width = attach_op.Value.right_panel.raw_width;
|
||||
vm.fills_slave_grid_is_visible = attach_op.Value.right_panel.fills_slave_grid_is_visible;
|
||||
}
|
||||
vm.begin_attaching -= on_begin_attaching;
|
||||
vm.end_attaching -= on_end_attaching;
|
||||
});
|
||||
const int delay_ms = 25;
|
||||
async void on_begin_attaching() {
|
||||
if (attach_op.HasValue) {
|
||||
// begin-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
|
||||
return;
|
||||
}
|
||||
attach_op = (new RightPanelState(vm.right_panel_is_visible, vm.fills_slave_grid_is_visible, vm.raw_right_panel_width), orders_grid.SubscribePreviewMouseDown(cancel_on_grid_click));
|
||||
vm.fills_slave_grid_is_visible = false;
|
||||
vm.algo_editor_is_visible = true;
|
||||
double target_width = Math.Max(ActualWidth * AttachOrderParamsPanelWidthRatio, MinAttachOrderParamsPanelWidth);
|
||||
if (vm.right_panel_width < target_width) {
|
||||
foreach (double incr in _get_incremental_widths(attach_op.Value.right_panel.is_visible ? attach_op.Value.right_panel.raw_width : 0, target_width)) {
|
||||
vm.right_panel_width = incr;
|
||||
await Task.Delay(delay_ms);
|
||||
if (!attach_op.HasValue)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async void on_end_attaching() {
|
||||
if (!attach_op.HasValue) {
|
||||
// end-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
|
||||
return;
|
||||
}
|
||||
attach_op.Value.hooks.Dispose();
|
||||
var incr_widths = _get_incremental_widths(vm.right_panel_width, attach_op.Value.right_panel.raw_width);
|
||||
vm.algo_editor_is_visible = attach_op.Value.right_panel.is_visible;
|
||||
vm.fills_slave_grid_is_visible = attach_op.Value.right_panel.fills_slave_grid_is_visible;
|
||||
vm.right_panel_width = attach_op.Value.right_panel.raw_width;
|
||||
attach_op = null;
|
||||
foreach (double incr in incr_widths) {
|
||||
vm.right_panel_width = incr;
|
||||
await Task.Delay(delay_ms);
|
||||
if (attach_op.HasValue)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
IDisposable attach_op = null;
|
||||
vm.begin_attaching += on_begin_attaching;
|
||||
vm.end_attaching += on_end_attaching;
|
||||
void on_begin_attaching() => Disposable.replace(ref attach_op, orders_grid.SubscribePreviewMouseDown(cancel_on_grid_click));
|
||||
void on_end_attaching() => Disposable.dispose(ref attach_op);
|
||||
#endif
|
||||
void cancel_on_grid_click(object sender, MouseButtonEventArgs e) {
|
||||
if (!KeyboardExtensions.IsCtrlDown() && !KeyboardExtensions.IsShiftDown())
|
||||
vm.is_attaching_algos = false;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<double> _get_incremental_widths(double from, double to) {
|
||||
var list = new List<double> { to };
|
||||
int incr = 1;
|
||||
if (from < to) {
|
||||
do {
|
||||
list.Add(to - (incr * 0.6666));
|
||||
list.Add(to - incr);
|
||||
from += incr;
|
||||
incr += incr;
|
||||
} while (from < to);
|
||||
} else if (from > to) {
|
||||
do {
|
||||
list.Add(to + incr);
|
||||
incr += incr;
|
||||
to += incr;
|
||||
} while (from > to);
|
||||
}
|
||||
for (int i = list.Count - 1; i >= 0; i--) {
|
||||
yield return list[i];
|
||||
}
|
||||
}
|
||||
|
||||
struct RightPanelState {
|
||||
public readonly bool is_visible, fills_slave_grid_is_visible;
|
||||
public readonly double raw_width;
|
||||
|
||||
public RightPanelState(bool right_panel_is_visible, bool fills_slave_grid_is_visible, double raw_right_panel_width) {
|
||||
this.is_visible = right_panel_is_visible;
|
||||
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
|
||||
this.raw_width = raw_right_panel_width;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class RightPanel {
|
||||
public const string GroupName = nameof(RightPanel);
|
||||
|
||||
public static readonly string
|
||||
Normal = $"{GroupName}{nameof(Normal)}",
|
||||
ModifyOrders = $"{GroupName}{nameof(ModifyOrders)}",
|
||||
AttachOrders = $"{GroupName}{nameof(AttachOrders)}";
|
||||
}
|
||||
|
||||
public static class FillsPanel {
|
||||
public const string GroupName = nameof(FillsPanel);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ControlTemplate x:Key="OrderParamsPanelTemplate" TargetType="usp:OrderParamsPanel">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:PriceEntryControl
|
||||
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
ToolTip="Price"
|
||||
/>
|
||||
<Button
|
||||
x:Name="best_bid_button"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
|
||||
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
FontWeight="Bold"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
|
||||
>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="best_ask_button"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
|
||||
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
FontWeight="Bold"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
|
||||
>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:TickUpDownNullableUInt Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
|
||||
<Button
|
||||
x:Name="best_bid_size_button"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
|
||||
/>
|
||||
<Button
|
||||
x:Name="best_ask_size_button"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
|
||||
/>
|
||||
<ItemsControl Grid.Row="2" Grid.RowSpan="2" Margin="-2,-2,-2,0" ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="3" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="2,4,2,0"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox Margin="0,2,0,4" ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}" SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Choose Account"/>
|
||||
<ComboBox Grid.Row="1">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
|
||||
<Setter Property="ItemsSource">
|
||||
<Setter.Value>
|
||||
<x:ArrayExtension Type="{x:Type sys:String}">
|
||||
<sys:String>Time In Force (N/A)</sys:String>
|
||||
</x:ArrayExtension>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<usp:AlgoEditorView x:Name="algo_editor" Grid.Row="4" DataContext="{Binding AlgoVM, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="usp:OrderParamsPanel">
|
||||
<Setter Property="MinWidth" Value="300"/>
|
||||
<Setter Property="Template" Value="{StaticResource OrderParamsPanelTemplate}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,188 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
class OrderParamsPanel : Control {
|
||||
public const string
|
||||
BestBidPriceButtonStyleKey = nameof(BestBidPriceButtonStyleKey),
|
||||
BestBidSizeButtonStyleKey = nameof(BestBidSizeButtonStyleKey),
|
||||
BestAskPriceButtonStyleKey = nameof(BestAskPriceButtonStyleKey),
|
||||
BestAskSizeButtonStyleKey = nameof(BestAskSizeButtonStyleKey),
|
||||
SizeCalculatorButtonStyleKey = nameof(SizeCalculatorButtonStyleKey);
|
||||
|
||||
public static readonly RoutedCommand SetPriceCommand = new RoutedCommand();
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler SetPriceExecuted =
|
||||
(s, e) => {
|
||||
if (s is OrderParamsPanel opp)
|
||||
opp.SetCurrentValue(PriceProperty, ConvertUtils.try_convert(e.Parameter, out double d) ? d : double.NaN);
|
||||
};
|
||||
|
||||
public static readonly CanExecuteRoutedEventHandler
|
||||
SetPriceCanExecute = (s, e) => e.CanExecute = e.Parameter is double,
|
||||
SetSizeCanExecute = (s, e) => e.CanExecute = e.Parameter is uint;
|
||||
|
||||
static OrderParamsPanel() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(OrderParamsPanel), new FrameworkPropertyMetadata(typeof(OrderParamsPanel)));
|
||||
}
|
||||
|
||||
#region Price
|
||||
public static readonly DependencyProperty PriceProperty =
|
||||
WPFHelper.create_dp<double, OrderParamsPanel>(nameof(Price), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, double.NaN);
|
||||
|
||||
public double Price {
|
||||
get => (double)GetValue(PriceProperty);
|
||||
set => SetValue(PriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Size
|
||||
public static readonly DependencyProperty SizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(Size), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public uint? Size {
|
||||
get => GetValue(SizeProperty) as uint?;
|
||||
set => SetValue(SizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SizePresets
|
||||
public static readonly DependencyProperty SizePresetsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(SizePresets), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable SizePresets {
|
||||
get => GetValue(SizePresetsProperty) as IEnumerable;
|
||||
set => SetValue(SizePresetsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestBidPrice
|
||||
public static readonly DependencyProperty BestBidPriceProperty =
|
||||
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestBidPrice));
|
||||
|
||||
public double? BestBidPrice {
|
||||
get => GetValue(BestBidPriceProperty) as double?;
|
||||
set => SetValue(BestBidPriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestBidSize
|
||||
public static readonly DependencyProperty BestBidSizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestBidSize));
|
||||
|
||||
public uint? BestBidSize {
|
||||
get => GetValue(BestBidSizeProperty) as uint?;
|
||||
set => SetValue(BestBidSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestAskPrice
|
||||
public static readonly DependencyProperty BestAskPriceProperty =
|
||||
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestAskPrice));
|
||||
|
||||
public double? BestAskPrice {
|
||||
get => GetValue(BestAskPriceProperty) as double?;
|
||||
set => SetValue(BestAskPriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestAskSize
|
||||
public static readonly DependencyProperty BestAskSizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestAskSize));
|
||||
|
||||
public uint? BestAskSize {
|
||||
get => GetValue(BestAskSizeProperty) as uint?;
|
||||
set => SetValue(BestAskSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Accounts
|
||||
public static readonly DependencyProperty AccountsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(Accounts), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable Accounts {
|
||||
get => GetValue(AccountsProperty) as IEnumerable;
|
||||
set => SetValue(AccountsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedAccount
|
||||
public static readonly DependencyProperty SelectedAccountProperty =
|
||||
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedAccount));
|
||||
|
||||
public object SelectedAccount {
|
||||
get => GetValue(SelectedAccountProperty);
|
||||
set => SetValue(SelectedAccountProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TimeInForces
|
||||
public static readonly DependencyProperty TimeInForcesProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(TimeInForces), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable TimeInForces {
|
||||
get => GetValue(TimeInForcesProperty) as IEnumerable;
|
||||
set => SetValue(TimeInForcesProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedTimeInForce
|
||||
public static readonly DependencyProperty SelectedTimeInForceProperty =
|
||||
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedTimeInForce));
|
||||
|
||||
public object SelectedTimeInForce {
|
||||
get => GetValue(SelectedTimeInForceProperty);
|
||||
set => SetValue(SelectedTimeInForceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Security
|
||||
public static readonly DependencyProperty SecurityProperty =
|
||||
WPFHelper.create_dp<SecurityImmutable?, OrderParamsPanel>(
|
||||
nameof(Security),
|
||||
(o, v) => o.CoerceValue(ContractSpecProperty));
|
||||
|
||||
public SecurityImmutable? Security {
|
||||
get => GetValue(SecurityProperty) as SecurityImmutable?;
|
||||
set => SetValue(SecurityProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ContractSpec
|
||||
public static readonly DependencyProperty ContractSpecProperty =
|
||||
WPFHelper.create_dp<ContractSpecificationImmutable?, OrderParamsPanel>(
|
||||
nameof(ContractSpec),
|
||||
null,
|
||||
(d, base_value) => d.Security?.ContractSpec ?? base_value ?? SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
|
||||
public ContractSpecificationImmutable? ContractSpec {
|
||||
get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?;
|
||||
set => SetValue(ContractSpecProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AlgoVM
|
||||
public static readonly DependencyProperty AlgoVMProperty =
|
||||
WPFHelper.create_dp<AlgoDefinitionVM, OrderParamsPanel>(
|
||||
nameof(AlgoVM),
|
||||
(o, v) => o._algo_editor?.SetCurrentValue(VisibilityProperty, v != null ? Visibility.Visible : Visibility.Collapsed));
|
||||
|
||||
public AlgoDefinitionVM AlgoVM {
|
||||
get => GetValue(AlgoVMProperty) as AlgoDefinitionVM;
|
||||
set => SetValue(AlgoVMProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
AlgoEditorView _algo_editor;
|
||||
public override void OnApplyTemplate() {
|
||||
_algo_editor = GetTemplateChild("algo_editor") as AlgoEditorView;
|
||||
_algo_editor?.SetCurrentValue(VisibilityProperty, AlgoVM != null ? Visibility.Visible : Visibility.Collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,17 +84,17 @@ namespace bts.shell {
|
||||
static class DefaultColumns {
|
||||
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, false, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings security_type => new GridColumnSettings(col_id(Columns.SecurityType), DefaultColumnHeaders.SecurityType, width: DefaultColumnWidths.SecurityType);
|
||||
public static GridColumnSettings start_net_position => new GridColumnSettings(col_id(Columns.StartNetPos), DefaultColumnHeaders.StartNetPos, width: DefaultColumnWidths.Start_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings start_pnl => new GridColumnSettings(col_id(Columns.StartPNL), DefaultColumnHeaders.StartPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_pnl => new GridColumnSettings(col_id(Columns.DayLong), DefaultColumnHeaders.DayLong, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_pnl => new GridColumnSettings(col_id(Columns.DayShort), DefaultColumnHeaders.DayShort, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_pnl => new GridColumnSettings(col_id(Columns.DayPNL), DefaultColumnHeaders.DayPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(col_id(Columns.TotalNetPos), DefaultColumnHeaders.TotalNetPos, false, width: DefaultColumnWidths.Total_Net_Pos, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(col_id(Columns.TotalPNL), DefaultColumnHeaders.TotalPnL, false, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings pnl_price => new GridColumnSettings(col_id(Columns.PNLPrice), DefaultColumnHeaders.PnLPrice, width: DefaultColumnWidths.PnL_Generic, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
@@ -158,6 +158,8 @@ namespace bts.shell {
|
||||
// ProtoMember 13 cannot be used further
|
||||
[ProtoMember(14)]
|
||||
public SecurityType[] security_types;
|
||||
[ProtoMember(15)]
|
||||
public bool[] has_day_trades;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using OrderTuple = System.ValueTuple<bts.api.SecurityImmutable?, bts.api.orderman.OrderRecordImmutable>;
|
||||
|
||||
namespace bts.shell {
|
||||
interface INotificationService {
|
||||
@@ -20,40 +19,14 @@ namespace bts.shell {
|
||||
INotificationServiceDataCache data_cache { get; }
|
||||
IDisposable link_to_activity(IPredicate<Activity> predicate, SoundNotification sound);
|
||||
IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate);
|
||||
IDisposable link_to_order(IPredicate<OrderTuple> predicate, SoundNotification sound);
|
||||
IDisposable link_to_order(INotificationPredicate<OrderTuple> toast_notification_predicate);
|
||||
}
|
||||
|
||||
static class NotificationServiceExtensions {
|
||||
/// TODO: remove this extension and force callers to use the appropriate 'link' method in <see cref="INotificationService"/> (it is smelly, and is NOT ACTUALLY STRONGLY TYPED)
|
||||
public static IDisposable link<T>(this INotificationService notification_service, IPredicate<T> predicate, SoundNotification sound) {
|
||||
if (predicate is IPredicate<OrderTuple> orp)
|
||||
return notification_service.link_to_order(orp, sound);
|
||||
if (predicate is IPredicate<OrderSecurity> osp) {
|
||||
// Note: watch out for implicit types as generic type arguments (although OrderSecurity can be implicitly cast to OrderTuple, IPredicate<OrderSecurity> CANNOT be implicitly cast to IPredicate<OrderTuple>)
|
||||
return notification_service.link_to_order(PredicateBuilder.create<OrderTuple>(ot => osp.trigger(new OrderSecurity(ot.Item1, ot.Item2))), sound);
|
||||
}
|
||||
if (predicate is IPredicate<Activity> ap)
|
||||
return notification_service.link_to_activity(ap, sound);
|
||||
Log.Ui.error($"Unable to activate sound-trigger (unrecognized predicate type '{typeof(T)}').");
|
||||
return Disposable.empty;
|
||||
}
|
||||
|
||||
/// TODO: remove this extension and force callers to use the appropriate 'link' method in <see cref="INotificationService"/> (it is smelly, and is NOT ACTUALLY STRONGLY TYPED)
|
||||
public static IDisposable link<T>(this INotificationService notification_service, INotificationPredicate<T> toast_notification_predicate) {
|
||||
if (toast_notification_predicate is INotificationPredicate<OrderTuple> orp)
|
||||
return notification_service.link_to_order(orp);
|
||||
if (toast_notification_predicate is INotificationPredicate<Activity> ap)
|
||||
return notification_service.link_to_activity(ap);
|
||||
Log.Ui.error($"Unable to activate toast-notification-trigger (unrecognized predicate type '{typeof(T)}').");
|
||||
return Disposable.empty;
|
||||
}
|
||||
IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound);
|
||||
IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate);
|
||||
}
|
||||
|
||||
[Export(typeof(INotificationService))]
|
||||
partial class NotificationService : INotificationService, IDisposable {
|
||||
readonly NotificationProcessor<Activity> _activity_notifications;
|
||||
readonly NotificationProcessor<OrderTuple> _order_notifications;
|
||||
readonly NotificationProcessor<OrderSecurity> _order_notifications;
|
||||
readonly DataCache _data_cache = new DataCache();
|
||||
|
||||
IDisposable _dispose;
|
||||
@@ -62,7 +35,7 @@ namespace bts.shell {
|
||||
public NotificationService(IBus bus, INotifier notifier, OrdersModel orders_model) {
|
||||
this.notifier = notifier;
|
||||
_activity_notifications = new NotificationProcessor<Activity>(notifier);
|
||||
_order_notifications = new NotificationProcessor<OrderTuple>(notifier);
|
||||
_order_notifications = new NotificationProcessor<OrderSecurity>(notifier);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.LogActivity).Subscribe(_on_activity),
|
||||
orders_model.on_orders.Subscribe(_on_orders)
|
||||
@@ -75,8 +48,8 @@ namespace bts.shell {
|
||||
|
||||
public IDisposable link_to_activity(IPredicate<Activity> predicate, SoundNotification sound) => _activity_notifications.link(predicate, sound);
|
||||
public IDisposable link_to_activity(INotificationPredicate<Activity> toast_notification_predicate) => _activity_notifications.link(toast_notification_predicate);
|
||||
public IDisposable link_to_order(IPredicate<OrderTuple> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
|
||||
public IDisposable link_to_order(INotificationPredicate<OrderTuple> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
|
||||
public IDisposable link_to_order(IPredicate<OrderSecurity> predicate, SoundNotification sound) => _order_notifications.link(predicate, sound);
|
||||
public IDisposable link_to_order(INotificationPredicate<OrderSecurity> toast_notification_predicate) => _order_notifications.link(toast_notification_predicate);
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
void _on_activity(Activity activity) =>
|
||||
|
||||
@@ -79,10 +79,12 @@ namespace mbts.spark {
|
||||
RequestClose?.Invoke(this, new EventArgs());
|
||||
}
|
||||
});
|
||||
bbo = new BBOVM();
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
bbo = new BBOVM(security_md_source),
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
bbo,
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark {
|
||||
) {
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
|
||||
bbo = new BBOVM(security_md_source);
|
||||
bbo = new BBOVM();
|
||||
send_order = new DelegateCommand<Side>(side => {
|
||||
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
||||
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
|
||||
@@ -59,6 +59,8 @@ namespace mbts.spark {
|
||||
});
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
bbo,
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
@@ -83,25 +84,18 @@ namespace bts.spark {
|
||||
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
|
||||
double old_price = old_order.State.Price, new_price = new_order.State.Price;
|
||||
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
|
||||
string modified_size_partial_description(int working_size) {
|
||||
return $"size from {old_size} to {new_size}, working {working_size}";
|
||||
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
|
||||
return StringBuilderEx.join(prefix, change_descs());
|
||||
//
|
||||
string prefix() => $"({old_order.Key}) Changed";
|
||||
IEnumerable<string> change_descs() {
|
||||
if (old_price != new_price)
|
||||
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
if (old_size != new_size)
|
||||
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
|
||||
if (old_attached_target_key != new_attached_target_key)
|
||||
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
|
||||
}
|
||||
string modified_price_partial_description() {
|
||||
return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
}
|
||||
if (old_price != new_price || old_size != new_size) {
|
||||
StringBuilder sb = new StringBuilder($"({old_order.Key}) Changed ");
|
||||
if (old_price != new_price) {
|
||||
sb.Append(modified_price_partial_description());
|
||||
if (old_size != new_size)
|
||||
sb.Append($", {modified_size_partial_description(new_order.working_quantity())}");
|
||||
}
|
||||
else
|
||||
sb.Append(modified_size_partial_description(new_order.working_quantity()));
|
||||
return sb.ToString();
|
||||
}
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
static string _header(uint key) => $"({key})";
|
||||
@@ -184,21 +178,31 @@ namespace bts.spark {
|
||||
format_cancel_error(order, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void update_order(OrderRecordImmutable order, double price, uint size) {
|
||||
internal void update_order(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "change from");
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "failed to change from");
|
||||
|
||||
void _log_order_update(OrderRecordImmutable order, double price, uint size, bool algo_changed, string change_desc_prefix) {
|
||||
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
|
||||
// nothing actually changed
|
||||
return;
|
||||
}
|
||||
const string algo_change_text = "change algo parameters";
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var spec = security?.ContractSpec;
|
||||
log_status(ActivityLogCategory.modify,
|
||||
security,
|
||||
$"({order.Key}) change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
|
||||
var desc_builder = new StringBuilderEx();
|
||||
if (price != order.price() || size != order.working_quantity()) {
|
||||
desc_builder.Add(order.State.Quantity.ToString(), "@", spec.format(order.State.Price), "to ", size.ToString(), "@", spec.format(price));
|
||||
if (algo_changed)
|
||||
desc_builder.Add("; ", algo_change_text);
|
||||
} else if (algo_changed) {
|
||||
desc_builder.Add(algo_change_text);
|
||||
}
|
||||
desc_builder.Insert(0, $"{change_desc_prefix} ");
|
||||
desc_builder.Insert(0, $"({order.Key}) ");
|
||||
log_status(ActivityLogCategory.modify, security, desc_builder.ToString());
|
||||
}
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, uint size) {
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var spec = security?.ContractSpec;
|
||||
log_status(ActivityLogCategory.modify,
|
||||
security,
|
||||
$"({order.Key}) failed to change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -346,9 +346,9 @@ namespace mbts.spark {
|
||||
_time_in_force.observe_current(start_immediately);
|
||||
|
||||
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
|
||||
public OrderDefinition create(Side side) {
|
||||
if (HasErrors)
|
||||
|
||||
@@ -11,7 +11,7 @@ using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel> {
|
||||
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
|
||||
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
|
||||
readonly VersionContainer<AlgoDefi> _definition;
|
||||
AlgoFormats _format;
|
||||
@@ -37,21 +37,15 @@ namespace mbts.spark {
|
||||
impl.is_expanded = value;
|
||||
}
|
||||
}
|
||||
public bool can_edit_values => impl?.can_edit_values ?? false;
|
||||
public bool can_edit_structure => impl?.can_edit_structure ?? false;
|
||||
public bool can_save_values => impl?.can_save_values ?? false;
|
||||
public bool can_save_structure => impl?.can_save_structure ?? false;
|
||||
public AlgoEditingPermissions editing_permissions => _editing_permissions;
|
||||
public string display_name => impl?.display_name ?? "(Default)";
|
||||
public AlgoDefi definition => _definition.value;
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors { get; } = new ObservableCollection<IAlgoSelector>();
|
||||
public dynamic children_vm => impl?.children_vm;
|
||||
|
||||
public void assign_to(AlgoDef value) => value.MergeFrom(definition.CloneToMutable());
|
||||
public bool key_match(KeyEventArgs e) => false;
|
||||
public void load_from(AlgoDefi value) => impl?.load_from(value);
|
||||
public void set_permissions(AlgoEditingPermissions editing_permissions) {
|
||||
_editing_permissions = editing_permissions;
|
||||
impl?.set_permissions(editing_permissions);
|
||||
}
|
||||
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
|
||||
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
|
||||
|
||||
@@ -98,6 +92,13 @@ namespace mbts.spark {
|
||||
_on_impl_definition_changed();
|
||||
}
|
||||
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) {
|
||||
Set(ref _editing_permissions, editing_permissions, nameof(this.editing_permissions));
|
||||
(impl as IObserver<AlgoEditingPermissions>)?.OnNext(editing_permissions);
|
||||
}
|
||||
|
||||
void _on_impl_definition_changed() {
|
||||
var orig_def = definition;
|
||||
_definition.increment_version();
|
||||
@@ -105,6 +106,7 @@ namespace mbts.spark {
|
||||
definition_changed?.Invoke();
|
||||
if (order_type != orig_def.CustomCase)
|
||||
RaisePropertyChanged(nameof(order_type), nameof(meta));
|
||||
RaisePropertyChanged(nameof(children_vm));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -11,8 +12,9 @@ using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
public class DynamicOrderHelper
|
||||
{
|
||||
class DynamicOrderHelper {
|
||||
public readonly IMaskIndexInt32<string> dynamic_type_index;
|
||||
|
||||
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
|
||||
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
|
||||
|
||||
@@ -21,6 +23,7 @@ namespace mbts.spark {
|
||||
private DynamicOrderHelper(OrderService order_service)
|
||||
{
|
||||
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
|
||||
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
|
||||
}
|
||||
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
@@ -30,9 +33,9 @@ namespace mbts.spark {
|
||||
{
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
|
||||
var ddef = dorder.Dynamic;
|
||||
//Add only the descriptor here for reflection later
|
||||
foreach(var p in d.ParamDesc)
|
||||
dorder.Dynamic.OrderParams.Add( p.ParamName, new DynamicOrderParam() );
|
||||
add_dynamic_params(ref ddef);
|
||||
defs.Add(new CustomOrderDefinitionImmutable(dorder));
|
||||
}
|
||||
}
|
||||
@@ -42,39 +45,35 @@ namespace mbts.spark {
|
||||
|
||||
internal DynamicOrderDefinition create_def(string dynamic_type) {
|
||||
var desc = get_descriptor(dynamic_type);
|
||||
var def = new DynamicOrderDefinition();
|
||||
foreach (var p in desc.ParamDesc) {
|
||||
if (_is_child_algo_desc(p)) {
|
||||
def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
|
||||
} else if (_is_value_param_desc(p)) {
|
||||
var value_case = _get_value_case(p.ParamType.Primitive);
|
||||
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
|
||||
var dop = new DynamicOrderParam();
|
||||
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
|
||||
def.OrderParams[p.ParamName] = dop;
|
||||
}
|
||||
}
|
||||
}
|
||||
var def = new DynamicOrderDefinition { DynamicOrderType = dynamic_type };
|
||||
add_dynamic_params(ref def);
|
||||
return def;
|
||||
}
|
||||
|
||||
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
|
||||
switch (primitive_descriptor_case) {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return DynamicOrderParam.ValueOneofCase.AsBool;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DynamicOrderParam.ValueOneofCase.AsBool;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return DynamicOrderParam.ValueOneofCase.AsInt32;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return DynamicOrderParam.ValueOneofCase.AsUint32;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return DynamicOrderParam.ValueOneofCase.AsString;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(primitive_descriptor_case, nameof(primitive_descriptor_case));
|
||||
}
|
||||
internal void add_dynamic_params(ref DynamicOrderDefinition dynamic_def) {
|
||||
var desc = get_descriptor(dynamic_def.DynamicOrderType);
|
||||
foreach (var p in desc.ParamDesc)
|
||||
dynamic_def.OrderParams[p.ParamName] = init_param(p);
|
||||
}
|
||||
|
||||
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
|
||||
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
|
||||
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
|
||||
|
||||
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
|
||||
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
|
||||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
|
||||
internal static DynamicOrderParam init_param(DynamicOrderParamDescriptor descriptor) {
|
||||
var param = new DynamicOrderParam();
|
||||
switch (descriptor.ParamType.DescriptorCase) {
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Array: param.AsArray = new DynamicOrderArray(); break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: param.AsEnumerated = new DynamicOrderEnumerated { EnumValue = descriptor.ParamType.Enumerated.Element.FirstOrDefault() }; break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive:
|
||||
switch (descriptor.ParamType.Primitive) {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: param.AsBool = false; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: param.AsDouble = 0.0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
|
||||
}
|
||||
break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Structure: param.AsStruct = new DynamicOrderStruct(); break;
|
||||
}
|
||||
return param;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,21 @@ using Google.Protobuf.Reflection;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
abstract class DynamicOrderParamVM : ViewModelBase {
|
||||
DynamicOrderParam _param = new DynamicOrderParam();
|
||||
abstract class DynamicOrderParamVM : DisposableViewModelBase {
|
||||
DynamicOrderParam _param;
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor) =>
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
|
||||
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
this.descriptor = descriptor;
|
||||
_param = param;
|
||||
}
|
||||
|
||||
public DynamicOrderParamDescriptor descriptor { get; set; }
|
||||
public DynamicOrderParam param {
|
||||
@@ -34,6 +42,7 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return new DynamicOrderParamVM<string>(descriptor, DynamicOrderParam.AsStringFieldNumber);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.Primitive, nameof(descriptor));
|
||||
}
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: return new DynamicOrderEnumeratedVM(descriptor);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
|
||||
}
|
||||
}
|
||||
@@ -55,4 +64,41 @@ namespace mbts.spark {
|
||||
|
||||
protected override void on_param_changed() => RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
|
||||
class DynamicOrderEnumeratedVM : DynamicOrderParamVM {
|
||||
static readonly FieldDescriptor _field_desc = DynamicOrderParam.Descriptor.FindFieldByNumber(DynamicOrderParam.AsEnumeratedFieldNumber);
|
||||
|
||||
public DynamicOrderEnumeratedVM(DynamicOrderParamDescriptor descriptor)
|
||||
: base(descriptor) =>
|
||||
elements = new ObservableCollection<string>(_get_elements(descriptor));
|
||||
|
||||
public ObservableCollection<string> elements { get; }
|
||||
|
||||
public string selected_element {
|
||||
get => param?.ValueCase == DynamicOrderParam.ValueOneofCase.AsEnumerated ? param.AsEnumerated.EnumValue : null;
|
||||
set {
|
||||
if (param == null) {
|
||||
param = new DynamicOrderParam { AsEnumerated = new DynamicOrderEnumerated() };
|
||||
} else if (param.ValueCase != DynamicOrderParam.ValueOneofCase.AsEnumerated) {
|
||||
param.AsEnumerated = new DynamicOrderEnumerated();
|
||||
}
|
||||
if (param.AsEnumerated.EnumValue != value) {
|
||||
param.AsEnumerated.EnumValue = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_sync_elements();
|
||||
RaisePropertyChanged(nameof(selected_element));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
|
||||
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
|
||||
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
|
||||
|
||||
void _sync_elements() =>
|
||||
elements.sync_with(_get_elements(descriptor));
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,10 @@ namespace mbts.spark {
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
|
||||
p.PropertyChanged += on_param_prop_changed;
|
||||
return Disposable.Create(() => p.PropertyChanged -= on_param_prop_changed);
|
||||
return Disposable.Create(() => {
|
||||
p.PropertyChanged -= on_param_prop_changed;
|
||||
p.Dispose();
|
||||
});
|
||||
}, true));
|
||||
void on_param_prop_changed(object sender, PropertyChangedEventArgs e) => raise_definition_changed();
|
||||
}
|
||||
@@ -61,4 +64,4 @@ namespace mbts.spark {
|
||||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,17 +22,14 @@ namespace mbts.spark {
|
||||
CustomOrderDefinition.CustomOneofCase order_type { get; }
|
||||
bool is_bts_type { get; }
|
||||
OrderMetadata meta { get; }
|
||||
AlgoEditingPermissions editing_permissions { get; }
|
||||
bool can_expand { get; }
|
||||
bool is_expanded { get; set; }
|
||||
bool can_edit_values { get; }
|
||||
bool can_edit_structure { get; }
|
||||
bool can_save_values { get; }
|
||||
bool can_save_structure { get; }
|
||||
string display_name { get; }
|
||||
CustomOrderDefinitionImmutable definition { get; }
|
||||
ObservableCollection<IAlgoSelector> child_algo_selectors { get; }
|
||||
dynamic children_vm { get; }
|
||||
bool key_match(KeyEventArgs e);
|
||||
void set_permissions(AlgoEditingPermissions editing_permissions);
|
||||
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
|
||||
}
|
||||
|
||||
@@ -91,4 +88,4 @@ namespace mbts.spark {
|
||||
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,6 @@ namespace mbts.spark {
|
||||
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
|
||||
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
|
||||
vm.load_from(def_data.value);
|
||||
//vm.is_expanded = is_expanded && vm.can_expand;
|
||||
vm.is_expanded = is_expanded && vm.can_expand;
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -84,6 +82,7 @@ namespace mbts.spark {
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
vm.is_expanded = is_expanded && vm.can_expand;
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -95,38 +94,32 @@ namespace mbts.spark {
|
||||
public static FieldDescriptor find_descriptor<T>() =>
|
||||
_algo_type_index.TryGetValue(typeof(T), out FieldDescriptor fd) ?
|
||||
fd : throw new NotSupportedException($"No {nameof(AlgoDef)} {nameof(FieldDescriptor)} exists for type '{typeof(T)}.'");
|
||||
|
||||
public static Func<TMutable, TImmutable> immutable_factory<TMutable, TImmutable>() where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
|
||||
_ImmutableActivator<TMutable, TImmutable>.factory;
|
||||
|
||||
static class _ImmutableActivator<TMutable, TImmutable> where TMutable : IMessage, new() where TImmutable : IImmutableMessage {
|
||||
public static readonly Func<TMutable, TImmutable> factory =
|
||||
m => (TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
|
||||
}
|
||||
}
|
||||
|
||||
#region OrderTypeVM Base Classes
|
||||
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM, IObserver<SecurityMD?>, IObserver<OrdersModel> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
/// TODO: maybe use explicit delegates rather than rely on reflection <see cref="_descriptor"/> <see cref="_create_immutable"/>.
|
||||
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM,
|
||||
IObserver<SecurityMD?>,
|
||||
IObserver<SecurityImmutable?>,
|
||||
IObserver<OrdersModel>,
|
||||
IObserver<AlgoEditingPermissions> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
static readonly FieldDescriptor _descriptor = OrderTypeVMUtils.find_descriptor<TMutable>();
|
||||
static readonly Func<TMutable, TImmutable> _create_immutable = OrderTypeVMUtils.immutable_factory<TMutable, TImmutable>();
|
||||
readonly IDictionary<string, object> _children_vm = new ExpandoObject();
|
||||
protected CompositeDisposable _resources = new CompositeDisposable();
|
||||
|
||||
readonly ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string> _auto_props = new ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string>(t => t.name);
|
||||
readonly ObservableKeyedCollection<AlgoSelectorData, string> _child_algo_selector_data = new ObservableKeyedCollection<AlgoSelectorData, string>(sd => sd.prop_name);
|
||||
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
|
||||
readonly Property<bool> _is_expanded, _can_save_values, _can_save_structure;
|
||||
readonly Property<bool> _is_expanded;
|
||||
readonly DeferrableActionWrapper _def_change_notifier;
|
||||
SecurityInput _security_input;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
IDisposable _pending_load;
|
||||
|
||||
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
this.editing_permissions = editing_permissions;
|
||||
_editing_permissions = editing_permissions;
|
||||
_child_algo_selectors = new ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData>(_child_algo_selector_data, sd => sd.selector);
|
||||
_can_save_values = backing(nameof(can_save_values), true);
|
||||
_can_save_structure = backing(nameof(can_save_structure), true);
|
||||
_is_expanded = backing<bool>(nameof(is_expanded));
|
||||
meta = OrderDataHelper.get_metadata(order_type);
|
||||
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
|
||||
@@ -134,10 +127,6 @@ namespace mbts.spark {
|
||||
switch (e.PropertyName) {
|
||||
// ignore props that don't affect the resultant AlgoDef
|
||||
case nameof(is_expanded):
|
||||
case nameof(can_edit_values):
|
||||
case nameof(can_edit_structure):
|
||||
case nameof(can_save_values):
|
||||
case nameof(can_save_structure):
|
||||
return;
|
||||
default:
|
||||
_def_change_notifier.execute();
|
||||
@@ -148,10 +137,7 @@ namespace mbts.spark {
|
||||
CompositeDisposable def_sub = null;
|
||||
sd.selector.selected_algo_changed += subscribe_new_algo;
|
||||
return new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_hooks(sd.selector.algos, a => {
|
||||
a.set_permissions(this.editing_permissions);
|
||||
return a as IDisposable ?? Disposable.empty;
|
||||
}, true)) {
|
||||
ObservableAutoWrapper.try_dispose_on_removed(sd.selector.algos, true)) {
|
||||
() => {
|
||||
sd.selector.selected_algo_changed -= subscribe_new_algo;
|
||||
Disposable.dispose(ref def_sub);
|
||||
@@ -170,6 +156,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event Action definition_changed;
|
||||
event Action<SecurityImmutable?> _security_changed;
|
||||
// core
|
||||
public AlgoDefi definition => new AlgoDefi(this.created_assigned());
|
||||
public AlgoType order_type => OrderDataHelper.get_order_type(new TMutable());
|
||||
@@ -182,18 +169,14 @@ namespace mbts.spark {
|
||||
public dynamic children_vm => _children_vm;
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions { get; private set; }
|
||||
public bool can_edit_values => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditValues);
|
||||
public bool can_edit_structure => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditStructure);
|
||||
public bool can_save_values => _can_save_values.Value;
|
||||
public bool can_save_structure => _can_save_structure.Value;
|
||||
// misc
|
||||
public AlgoEditingPermissions editing_permissions { get => _editing_permissions; private set => Set(ref _editing_permissions, value); }
|
||||
// expand
|
||||
public virtual bool can_expand => !meta.NoProps;
|
||||
public virtual bool is_expanded { get => _is_expanded.Value; set => _is_expanded.Value = value; }
|
||||
// protected (observed props)
|
||||
protected OrdersModel model { get; private set; }
|
||||
protected SecurityMD? security_md { get; private set; }
|
||||
protected SecurityImmutable? security => security_md?.security;
|
||||
protected SecurityMD? security_md => _security_input.security_md;
|
||||
protected SecurityImmutable? security => _security_input.security;
|
||||
|
||||
public void assign_to(AlgoDef value) {
|
||||
var algo = new TMutable();
|
||||
@@ -206,12 +189,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void set_permissions(AlgoEditingPermissions editing_permissions) {
|
||||
using (detect_property_changes(nameof(can_edit_values), nameof(can_edit_structure), nameof(can_save_values), nameof(can_save_structure)))
|
||||
this.editing_permissions = editing_permissions;
|
||||
on_permissions_changed();
|
||||
}
|
||||
|
||||
public bool key_match(KeyEventArgs key) =>
|
||||
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
|
||||
|
||||
@@ -219,22 +196,31 @@ namespace mbts.spark {
|
||||
if (value.CustomCase != order_type)
|
||||
return;
|
||||
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
|
||||
var security_src = Observable.Create<SecurityImmutable?>(o => {
|
||||
o.OnNext(security);
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load,
|
||||
new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), sd => {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}),
|
||||
Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase, is_expanded: is_expanded));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
|
||||
@@ -246,7 +232,7 @@ namespace mbts.spark {
|
||||
foreach (var sd in _child_algo_selector_data) {
|
||||
var inner_def = sd.get_order(defi);
|
||||
if (inner_def.HasValue) {
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase, is_expanded: is_expanded));
|
||||
await vm.load_from_async(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
@@ -260,11 +246,6 @@ namespace mbts.spark {
|
||||
|
||||
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
|
||||
|
||||
protected virtual void on_permissions_changed() {
|
||||
foreach (var vm in child_algo_selectors.SelectMany(s => s.algos.AsCollection()))
|
||||
vm.set_permissions(editing_permissions);
|
||||
}
|
||||
|
||||
protected void raise_definition_changed() =>
|
||||
_def_change_notifier.execute();
|
||||
|
||||
@@ -337,12 +318,11 @@ namespace mbts.spark {
|
||||
protected Property<TImmutableValue> add_property<TMutableValue, TImmutableValue>(string prop_name, int target_field_number, TImmutableValue init_value = default) where TMutableValue : class, IMessage, new() where TImmutableValue : IImmutableMessage {
|
||||
var prop = backing(prop_name, init_value);
|
||||
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
|
||||
var immutable_factory = OrderTypeVMUtils.immutable_factory<TMutableValue, TImmutableValue>();
|
||||
add_property<TMutableValue>(
|
||||
prop_name,
|
||||
mdef => field_desc.Accessor.GetValue(mdef) as TMutableValue,
|
||||
(mdef, mval) => field_desc.Accessor.SetValue(mdef, mval),
|
||||
mval => prop.set(immutable_factory(mval ?? new TMutableValue())),
|
||||
mval => prop.set(ProtoHelper.immutable_factory<TMutableValue, TImmutableValue>(mval ?? new TMutableValue())),
|
||||
() => {
|
||||
// TODO: perhaps CloneToMutable() should be a member of IImmutableMessage.
|
||||
// this method makes things so simple for the caller...but this dynamic cast is a little sketch.
|
||||
@@ -381,6 +361,7 @@ namespace mbts.spark {
|
||||
|
||||
protected virtual void on_model_internal() { }
|
||||
protected virtual void on_security_md_internal() { }
|
||||
protected virtual void on_security_internal() { }
|
||||
|
||||
// model
|
||||
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
|
||||
@@ -400,14 +381,40 @@ namespace mbts.spark {
|
||||
void IObserver<OrdersModel>.OnCompleted() { }
|
||||
// security md
|
||||
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
|
||||
this.security_md = security_md;
|
||||
using (_def_change_notifier.defer_execution()) {
|
||||
on_security_md_internal();
|
||||
_populate_selectors();
|
||||
}
|
||||
using (_def_change_notifier.defer_execution())
|
||||
using (_update_sec_input())
|
||||
_security_input = _security_input.with_security_md(security_md);
|
||||
}
|
||||
void IObserver<SecurityMD?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityMD?>.OnCompleted() { }
|
||||
// security
|
||||
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? security) {
|
||||
using (_def_change_notifier.defer_execution())
|
||||
using (_update_sec_input())
|
||||
_security_input = _security_input.with_security(security);
|
||||
}
|
||||
|
||||
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityImmutable?>.OnCompleted() { }
|
||||
// editing permissions
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) => this.editing_permissions = editing_permissions;
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
|
||||
IDisposable _update_sec_input() {
|
||||
var old_sec = security;
|
||||
var old_sec_md = security_md;
|
||||
return Disposable.Create(() => {
|
||||
if (security != old_sec) {
|
||||
on_security_internal();
|
||||
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
|
||||
_populate_selectors();
|
||||
_security_changed?.Invoke(security);
|
||||
}
|
||||
if (security_md != old_sec_md)
|
||||
on_security_md_internal();
|
||||
});
|
||||
}
|
||||
|
||||
IDisposable _selector_data_hooks;
|
||||
void _populate_selectors() {
|
||||
@@ -426,6 +433,40 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// security input may come in 2 forms:
|
||||
// 1) SecurityImmutable?
|
||||
// 2) SecurityMD?
|
||||
// This structure resolves conflicts that may arise from the different inputs
|
||||
struct SecurityInput {
|
||||
readonly SecurityMD? _raw_security_md;
|
||||
readonly SecurityImmutable? _raw_security;
|
||||
|
||||
public readonly SecurityMD? security_md;
|
||||
public readonly SecurityImmutable? security;
|
||||
|
||||
SecurityInput(SecurityImmutable? raw_security, SecurityMD? raw_security_md, SecurityImmutable? security, SecurityMD? security_md) {
|
||||
_raw_security = raw_security;
|
||||
_raw_security_md = raw_security_md;
|
||||
this.security_md = security_md;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public SecurityInput with_security(SecurityImmutable? security) =>
|
||||
new SecurityInput(
|
||||
security,
|
||||
_raw_security_md,
|
||||
security,
|
||||
security.HasValue && _raw_security_md.HasValue && _raw_security_md.Value.security.Id != security.Value.Id ?
|
||||
null : _raw_security_md);
|
||||
|
||||
public SecurityInput with_security_md(SecurityMD? security_md) =>
|
||||
new SecurityInput(
|
||||
_raw_security,
|
||||
security_md,
|
||||
security_md?.security ?? _raw_security,
|
||||
security_md);
|
||||
}
|
||||
|
||||
struct AlgoSelectorData {
|
||||
public readonly string prop_name;
|
||||
public readonly AlgoSelector selector;
|
||||
@@ -445,14 +486,15 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
|
||||
AlgoKey? _pending_default;
|
||||
|
||||
public AlgoSelector() =>
|
||||
public AlgoSelector() {
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(algos, AlgoCollectionHelper.algo_vm_comparer);
|
||||
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
|
||||
}
|
||||
|
||||
public void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
_pending_data = algo_data;
|
||||
_pending_default = default_algo;
|
||||
_sync_formats();
|
||||
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
|
||||
}
|
||||
|
||||
public event PropertyChange<IAlgoVM> selected_algo_changed;
|
||||
@@ -513,4 +555,4 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.utility;
|
||||
@@ -11,7 +12,7 @@ using SpreaderDefi = bts.api.orderman.SpreaderOrderDefinitionImmutable;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<SecurityMD?>, IObserver<OrdersModel> {
|
||||
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<OrdersModel> {
|
||||
readonly Property<SpreaderDefi?> _template;
|
||||
readonly StrategyEditorWrapper _strategy_editor_wrapper;
|
||||
IDisposable _strategy_sub;
|
||||
@@ -78,16 +79,15 @@ namespace mbts.spark {
|
||||
set_error(nameof(template), "Waiting for security and/or strategy.", true);
|
||||
else clear_error(nameof(template));
|
||||
}
|
||||
|
||||
protected override void on_security_md_internal() => _reset_strategy_sub();
|
||||
protected override void on_model_internal() => _reset_strategy_sub();
|
||||
protected override void on_security_internal() => _reset_strategy_sub();
|
||||
|
||||
void _reset_strategy_sub() {
|
||||
Disposable.dispose(ref _strategy_sub);
|
||||
if (model != null) {
|
||||
if (security_md.HasValue) {
|
||||
_strategy_sub = model.spreader.security_strategy(security_md.Value.security.Id)
|
||||
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security_md.Value.security, strategy)))
|
||||
if (security.HasValue) {
|
||||
_strategy_sub = model.spreader.security_strategy(security.Value.Id)
|
||||
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security.Value, strategy)))
|
||||
.OnDispatcher()
|
||||
.Subscribe(s => {
|
||||
is_loaded = true;
|
||||
|
||||
@@ -130,4 +130,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ namespace mbts.spark {
|
||||
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
|
||||
ot => ot.order.Key);
|
||||
on_order_tuples = order_secs_algo_meta_join;
|
||||
on_order_tuples = order_secs_algo_meta_join.Cache();
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
@@ -101,17 +101,14 @@ namespace mbts.spark {
|
||||
(f, fk, o, ok) => new FillTuple(f, o),
|
||||
(f, fk, ok) => new FillTuple(f, null),
|
||||
f => f.fill.FillKey);
|
||||
on_fill_tuples = fill_order_tuples_join;
|
||||
on_fill_tuples = fill_order_tuples_join.Cache();
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
|
||||
on_positions.Subscribe(_security_positions_demuxer),
|
||||
order_secs_algo_meta_join.Connect(),
|
||||
algo_meta_join.Connect(),
|
||||
fill_order_tuples_join.Connect(),
|
||||
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
|
||||
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
|
||||
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
|
||||
);
|
||||
}
|
||||
@@ -166,27 +163,37 @@ namespace mbts.spark {
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<bool> update_order(OrderRecordImmutable order, double price, uint size) {
|
||||
if (order.working_quantity() <= 0) return false;
|
||||
_activity.update_order(order, price, size);
|
||||
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
|
||||
if (order.working_quantity() <= 0) {
|
||||
// order already filled to completion
|
||||
return false;
|
||||
}
|
||||
var size = new_size ?? (uint)order.working_quantity();
|
||||
var price = new_price ?? order.price();
|
||||
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
|
||||
// no parameters would be changed by this update
|
||||
return false;
|
||||
}
|
||||
_activity.update_order(order, price, size, new_algo.HasValue);
|
||||
var result = await _service.update_order_async(new Update() {
|
||||
OrderKey = order.Key,
|
||||
Price = price,
|
||||
Quantity = size
|
||||
Quantity = size,
|
||||
Custom = new_algo?.CloneToMutable()
|
||||
});
|
||||
(var key, string msg) = _handle_error(result, "Failed to update order: {0}");
|
||||
bool success = key.HasValue;
|
||||
if (!success) {
|
||||
_activity.update_order_failed(order, price, size);
|
||||
_activity.update_order_failed(order, price, size, new_algo.HasValue);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public Task<bool[]> crack_orders(IEnumerable<uint> orders) =>
|
||||
Task.WhenAll(orders.Select(crack_order));
|
||||
public Task<bool[]> detach_orders(IEnumerable<uint> orders) =>
|
||||
Task.WhenAll(orders.Select(detach_order));
|
||||
|
||||
public async Task<bool> crack_order(uint order) {
|
||||
var result = await _service.crack_order_async(new Crack() { OrderKey = order });
|
||||
public async Task<bool> detach_order(uint order) {
|
||||
var result = await _service.detach_order_async(new Detach() { OrderKey = order });
|
||||
return _handle_error(result, "Failed to break order: {0}").key.HasValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class DiscreteStringPredicate : IDiscreteValuesPredicateBuilder<string>, IEquatable<DiscreteStringPredicate> {
|
||||
class DiscreteStringPredicate : IDiscreteValuesPredicateData<string>, IEquatable<DiscreteStringPredicate> {
|
||||
[ProtoMember(1)]
|
||||
string[] _values;
|
||||
[ProtoMember(2)]
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class DiscreteUIntPredicate : IDiscreteValuesPredicateBuilder<uint, uint?, uint[]>, IEquatable<DiscreteUIntPredicate> {
|
||||
class DiscreteUIntPredicate : IDiscreteValuesPredicateData<uint, uint?, uint[]>, IEquatable<DiscreteUIntPredicate> {
|
||||
[ProtoMember(1)]
|
||||
uint[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -16,7 +16,7 @@ namespace mbts {
|
||||
public uint[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<uint> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<uint> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(DiscreteUIntPredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as DiscreteUIntPredicate);
|
||||
|
||||
@@ -8,14 +8,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts {
|
||||
interface IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> : IPredicateBuilder<T> {
|
||||
interface IDiscreteValuesPredicateData<T, TSingle, TMulti> {
|
||||
TSingle is_value { get; set; }
|
||||
TSingle not_value { get; set; }
|
||||
TMulti in_set { get; set; }
|
||||
TMulti not_in_set { get; set; }
|
||||
DiscreteValuesOneofCase predicate_type_case { get; }
|
||||
}
|
||||
interface IDiscreteValuesPredicateBuilder<T> : IDiscreteValuesPredicateBuilder<T, T, T[]> { }
|
||||
interface IDiscreteValuesPredicateData<T> : IDiscreteValuesPredicateData<T, T, T[]> { }
|
||||
|
||||
public enum DiscreteValuesOneofCase {
|
||||
None,
|
||||
@@ -32,8 +32,8 @@ namespace mbts {
|
||||
|
||||
namespace mbts.ui.settings {
|
||||
static class DiscreteValuesPredicateUtils {
|
||||
public static bool equals<T>(IDiscreteValuesPredicateBuilder<T> a, IDiscreteValuesPredicateBuilder<T> b) => a.safe_equals(b, _equals);
|
||||
static bool _equals<T>(IDiscreteValuesPredicateBuilder<T> a, IDiscreteValuesPredicateBuilder<T> b) {
|
||||
public static bool equals<T>(IDiscreteValuesPredicateData<T> a, IDiscreteValuesPredicateData<T> b) => a.safe_equals(b, _equals);
|
||||
static bool _equals<T>(IDiscreteValuesPredicateData<T> a, IDiscreteValuesPredicateData<T> b) {
|
||||
var dvoc = a.predicate_type_case;
|
||||
if (dvoc != b.predicate_type_case)
|
||||
return false;
|
||||
@@ -47,8 +47,8 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static bool equals<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a, IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> b) => a.safe_equals(b, _equals);
|
||||
public static bool _equals<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a, IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> b) {
|
||||
public static bool equals<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a, IDiscreteValuesPredicateData<T, TSingle, TMulti> b) => a.safe_equals(b, _equals);
|
||||
public static bool _equals<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a, IDiscreteValuesPredicateData<T, TSingle, TMulti> b) {
|
||||
var dvoc = a.predicate_type_case;
|
||||
if (dvoc != b.predicate_type_case)
|
||||
return false;
|
||||
@@ -62,7 +62,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static int hash<T>(IDiscreteValuesPredicateBuilder<T> a) {
|
||||
public static int hash<T>(IDiscreteValuesPredicateData<T> a) {
|
||||
switch (a.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return a.is_value.GetHashCode() % MathUtils.Primes.P_17191;
|
||||
case DiscreteValuesOneofCase.NotValue: return a.not_value.GetHashCode() % MathUtils.Primes.P_83987;
|
||||
@@ -72,7 +72,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static int hash<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> a) {
|
||||
public static int hash<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> a) {
|
||||
switch (a.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return a.is_value.GetHashCode() % MathUtils.Primes.P_17191;
|
||||
case DiscreteValuesOneofCase.NotValue: return a.not_value.GetHashCode() % MathUtils.Primes.P_83987;
|
||||
@@ -88,7 +88,7 @@ namespace mbts.ui.settings {
|
||||
(negated ? DiscreteValuesOneofCase.NotValue : DiscreteValuesOneofCase.IsValue) :
|
||||
(negated ? DiscreteValuesOneofCase.NotInSet : DiscreteValuesOneofCase.IsInSet);
|
||||
|
||||
public static DiscreteValuesOneofCase get_predicate_type_case<T, TSingle, TMulti>(IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> predicate_builder) {
|
||||
public static DiscreteValuesOneofCase get_predicate_type_case<T, TSingle, TMulti>(IDiscreteValuesPredicateData<T, TSingle, TMulti> predicate_builder) {
|
||||
if (predicate_builder.is_value != null) return DiscreteValuesOneofCase.IsValue;
|
||||
if (predicate_builder.not_value != null) return DiscreteValuesOneofCase.NotValue;
|
||||
if (predicate_builder.in_set != null) return DiscreteValuesOneofCase.IsInSet;
|
||||
@@ -96,7 +96,7 @@ namespace mbts.ui.settings {
|
||||
return DiscreteValuesOneofCase.None;
|
||||
}
|
||||
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateBuilder<T> predicate_builder) {
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateData<T> predicate_builder) {
|
||||
switch (predicate_builder.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return PredicateBuilder.equals(predicate_builder.is_value);
|
||||
case DiscreteValuesOneofCase.NotValue: return PredicateBuilder.not_equals(predicate_builder.not_value);
|
||||
@@ -106,7 +106,7 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
}
|
||||
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateBuilder<T, T?, T[]> predicate_builder) where T : struct {
|
||||
public static IPredicate<T> create<T>(IDiscreteValuesPredicateData<T, T?, T[]> predicate_builder) where T : struct {
|
||||
switch (predicate_builder.predicate_type_case) {
|
||||
case DiscreteValuesOneofCase.IsValue: return PredicateBuilder.equals(predicate_builder.is_value.Value);
|
||||
case DiscreteValuesOneofCase.NotValue: return PredicateBuilder.not_equals(predicate_builder.not_value.Value);
|
||||
@@ -118,13 +118,13 @@ namespace mbts.ui.settings {
|
||||
}
|
||||
static class DiscreteValuesPredicateVMFactory {
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs_value_type<T, TPredicate>(IEnumerable<T> items)
|
||||
where T : struct where TPredicate : IDiscreteValuesPredicateBuilder<T, T?, T[]> =>
|
||||
where T : struct where TPredicate : IDiscreteValuesPredicateData<T, T?, T[]> =>
|
||||
//
|
||||
get_oneofs<T, T?, T[], TPredicate>(identity_view_value_collection(items), val => val.Value, val => val, vals => vals ?? Enumerable.Empty<T>(), vals => vals?.ToArray() ?? new T[0], get_identity_view);
|
||||
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TSingle, TMulti, TPredicate>(
|
||||
IObservableCollection<IViewValue<T>> items, Func<TSingle, T> from_single, Func<T, TSingle> to_single, Func<TMulti,IEnumerable<T>> from_multi, Func<IEnumerable<T>, TMulti> to_multi, Func<T, object> get_view)
|
||||
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> {
|
||||
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> {
|
||||
//
|
||||
return new Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> {
|
||||
{ DiscreteValuesOneofCase.IsValue, PropertyWrapperVMUtils.create_selector_vm(
|
||||
@@ -153,14 +153,14 @@ namespace mbts.ui.settings {
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(
|
||||
IEnumerable<T> items, Func<T, TKey> key, Func<TSingle, TKey> from_single, Func<TKey, TSingle> to_single, Func<TMulti, IEnumerable<TKey>> from_multi, Func<IEnumerable<TKey>, TMulti> to_multi)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> =>
|
||||
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> =>
|
||||
//
|
||||
get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(RXCollection.from_enumerable(items.Select<T, IViewValue<T>>(v => new ViewValueTuple<T>(v, v))), key, from_single, to_single, from_multi, to_multi);
|
||||
|
||||
public static Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> get_oneofs<T, TKey, TSingle, TMulti, TPredicate>(
|
||||
IObservableCollection<IViewValue<T>> items, Func<T, TKey> key, Func<TSingle, TKey> from_single, Func<TKey, TSingle> to_single, Func<TMulti, IEnumerable<TKey>> from_multi, Func<IEnumerable<TKey>, TMulti> to_multi)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
where TPredicate : IDiscreteValuesPredicateBuilder<T, TSingle, TMulti> {
|
||||
where TPredicate : IDiscreteValuesPredicateData<T, TSingle, TMulti> {
|
||||
//
|
||||
return new Dictionary<DiscreteValuesOneofCase, ITemplatedPropertyWrapperVM<TPredicate>> {
|
||||
{ DiscreteValuesOneofCase.IsValue, PropertyWrapperVMUtils.create_keyed_selector_vm<TPredicate, IViewValue<T>, TKey>(
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class ExchangePredicate : IDiscreteValuesPredicateBuilder<Venue, Venue?, Venue[]>, IEquatable<ExchangePredicate> {
|
||||
class ExchangePredicate : IDiscreteValuesPredicateData<Venue, Venue?, Venue[]>, IEquatable<ExchangePredicate> {
|
||||
[ProtoMember(1)]
|
||||
Venue[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public Venue[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<Venue> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<Venue> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(ExchangePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as ExchangePredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderStatusPredicate : IDiscreteValuesPredicateBuilder<OrderStatus, OrderStatus?, OrderStatus[]>, IEquatable<OrderStatusPredicate> {
|
||||
class OrderStatusPredicate : IDiscreteValuesPredicateData<OrderStatus, OrderStatus?, OrderStatus[]>, IEquatable<OrderStatusPredicate> {
|
||||
[ProtoMember(1)]
|
||||
OrderStatus[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public OrderStatus[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<OrderStatus> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<OrderStatus> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(OrderStatusPredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderStatusPredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderSubTypePredicate : IDiscreteValuesPredicateBuilder<OrderSubType, OrderSubType?, OrderSubType[]>, IEquatable<OrderSubTypePredicate> {
|
||||
class OrderSubTypePredicate : IDiscreteValuesPredicateData<OrderSubType, OrderSubType?, OrderSubType[]>, IEquatable<OrderSubTypePredicate> {
|
||||
[ProtoMember(1)]
|
||||
OrderSubType[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public OrderSubType[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<OrderSubType> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<OrderSubType> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(OrderSubTypePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderSubTypePredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class OrderTypePredicate : IDiscreteValuesPredicateBuilder<OrderType, OrderType?, OrderType[]>, IEquatable<OrderTypePredicate> {
|
||||
class OrderTypePredicate : IDiscreteValuesPredicateData<OrderType, OrderType?, OrderType[]>, IEquatable<OrderTypePredicate> {
|
||||
[ProtoMember(1)]
|
||||
OrderType[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public OrderType[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negated);
|
||||
public IPredicate<OrderType> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<OrderType> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(OrderTypePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderTypePredicate);
|
||||
|
||||
@@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace mbts {
|
||||
[ProtoContract]
|
||||
class TimeInForcePredicate : IDiscreteValuesPredicateBuilder<TimeInForce, TimeInForce?, TimeInForce[]>, IEquatable<TimeInForcePredicate> {
|
||||
class TimeInForcePredicate : IDiscreteValuesPredicateData<TimeInForce, TimeInForce?, TimeInForce[]>, IEquatable<TimeInForcePredicate> {
|
||||
[ProtoMember(1)]
|
||||
TimeInForce[] _values;
|
||||
[ProtoMember(2)]
|
||||
@@ -17,7 +17,7 @@ namespace mbts {
|
||||
public TimeInForce[] not_in_set { get => _get_set(true); set => _set(true, value); }
|
||||
|
||||
public DiscreteValuesOneofCase predicate_type_case => DiscreteValuesPredicateUtils.get_predicate_type_case(_values, _negate);
|
||||
public IPredicate<TimeInForce> create(object obj) => DiscreteValuesPredicateUtils.create(this);
|
||||
public IPredicate<TimeInForce> create() => DiscreteValuesPredicateUtils.create(this);
|
||||
|
||||
public bool Equals(TimeInForcePredicate other) => DiscreteValuesPredicateUtils.equals(this, other);
|
||||
public override bool Equals(object obj) => Equals(obj as TimeInForcePredicate);
|
||||
|
||||
@@ -4,7 +4,6 @@ using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -14,7 +13,7 @@ namespace mbts {
|
||||
static class OrderCanceledEventPredicateVMFactory {
|
||||
public static Dictionary<OrderCanceledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCanceledEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderCanceledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCanceledEventPredicate>> {
|
||||
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderCanceledEventPredicate>(p => p.security = null) },
|
||||
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderCanceledEventPredicate>(p => p.security = null) },
|
||||
{ OrderCanceledEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderCanceledEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null)
|
||||
@@ -26,7 +25,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderCanceledEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderCanceledEventPredicate> {
|
||||
class OrderCanceledEventPredicate : IEquatable<OrderCanceledEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[Description("(any order)")]
|
||||
AnyOrder,
|
||||
@@ -52,7 +51,7 @@ namespace mbts {
|
||||
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Canceled, security, data_cache);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
|
||||
new MyToaster(security?.create(data_cache), data_cache);
|
||||
new MyToaster(security?.create(), data_cache);
|
||||
|
||||
public bool Equals(OrderCanceledEventPredicate other) => other != null && other.security.safe_equals(other.security);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderCanceledEventPredicate);
|
||||
|
||||
@@ -4,7 +4,6 @@ using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.settings;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -14,7 +13,7 @@ namespace mbts {
|
||||
static class OrderCompletedEventPredicateVMFactory {
|
||||
public static Dictionary<OrderCompletedEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCompletedEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderCompletedEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderCompletedEventPredicate>> {
|
||||
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderCompletedEventPredicate>(p => p.security = null) },
|
||||
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderCompletedEventPredicate>(p => p.security = null) },
|
||||
{ OrderCompletedEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderCompletedEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null)
|
||||
@@ -26,7 +25,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderCompletedEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderCompletedEventPredicate> {
|
||||
class OrderCompletedEventPredicate : IEquatable<OrderCompletedEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[Description("(any order)")]
|
||||
AnyOrder,
|
||||
@@ -52,7 +51,7 @@ namespace mbts {
|
||||
OrderEventPredicateHelper.create_status_transition_predicate(OrderStatus.Completed, security, data_cache);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_toast(INotificationServiceDataCache data_cache) =>
|
||||
new MyToaster(security?.create(data_cache), data_cache);
|
||||
new MyToaster(security?.create(), data_cache);
|
||||
|
||||
public bool Equals(OrderCompletedEventPredicate other) => other != null && other.security.safe_equals(other.security);
|
||||
public override bool Equals(object obj) => Equals(obj as OrderCompletedEventPredicate);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
namespace mbts {
|
||||
@@ -39,7 +37,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static IPredicate<OrderSecurity> create_status_transition_predicate(OrderStatus status, SecurityPredicate security_predicate, INotificationServiceDataCache data_cache) =>
|
||||
new OrderStatusTransitionPredicate(status, security_predicate?.create(data_cache), data_cache);
|
||||
new OrderStatusTransitionPredicate(status, security_predicate?.create(), data_cache);
|
||||
|
||||
class OrderStatusTransitionPredicate : IPredicate<OrderSecurity>, IEquatable<OrderStatusTransitionPredicate> {
|
||||
readonly IPredicate<SecurityImmutable> _security_predicate;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbts {
|
||||
static class OrderFilledEventPredicateVMFactory {
|
||||
public static Dictionary<OrderFilledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderFilledEventPredicate>> get_oneofs(IBus bus) =>
|
||||
new Dictionary<OrderFilledEventPredicate.PredicateTypeOneofCase, ITemplatedPropertyWrapperVM<OrderFilledEventPredicate>> {
|
||||
{ OrderFilledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateBuilderVMWrapper.create<OrderFilledEventPredicate>(p => p.security = null) },
|
||||
{ OrderFilledEventPredicate.PredicateTypeOneofCase.AnyOrder, PredicateVMWrapper.create<OrderFilledEventPredicate>(p => p.security = null) },
|
||||
{ OrderFilledEventPredicate.PredicateTypeOneofCase.SecurityOrder, SecurityCriteriaVMFactory.create<OrderFilledEventPredicate>(bus, o => o.security?.is_value ?? new SecurityCriteria(),
|
||||
(p, s) => {
|
||||
if (p.security == null) {
|
||||
@@ -24,7 +24,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderFilledEventPredicate : IPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderFilledEventPredicate> {
|
||||
class OrderFilledEventPredicate : IEquatable<OrderFilledEventPredicate> {
|
||||
public enum PredicateTypeOneofCase {
|
||||
[PredicateCase("(any order)")]
|
||||
AnyOrder,
|
||||
@@ -59,7 +59,7 @@ namespace mbts {
|
||||
public override int GetHashCode() => security == null ? 0 : security.GetHashCode();
|
||||
|
||||
FillTriggerModel _create_model(INotificationServiceDataCache nsdc) =>
|
||||
new FillTriggerModel(nsdc, security?.create(nsdc));
|
||||
new FillTriggerModel(nsdc, security?.create());
|
||||
|
||||
class FillTriggerModel {
|
||||
readonly IPredicate<SecurityImmutable> _security_predicate;
|
||||
|
||||
@@ -56,16 +56,16 @@ namespace mbts {
|
||||
Func<TChildPredicate, TChildPredicateOneof> key,
|
||||
Dictionary<TChildPredicateOneof, ITemplatedPropertyWrapperVM<TChildPredicate>> oneofs) where TChildPredicateOneof : struct where TChildPredicate : new() =>
|
||||
//
|
||||
PredicateBuilderVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
|
||||
PredicateVMWrapper.wrap_child_oneof<OrderRecordTuplePredicate, TChildPredicate, TChildPredicateOneof>(get, set, key, oneofs);
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class OrderRecordTuplePredicate : INotificationPredicateBuilder<OrderSecurity, INotificationServiceDataCache>, IEquatable<OrderRecordTuplePredicate> {
|
||||
class OrderRecordTuplePredicate : IEquatable<OrderRecordTuplePredicate> {
|
||||
SecurityPredicate _security;
|
||||
SidePredicate _side;
|
||||
DoublePredicate _price, _last_price, _avg_price;
|
||||
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key;
|
||||
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key, _attached_target_order_key;
|
||||
OrderStatusPredicate _order_status;
|
||||
OrderTypePredicate _order_type;
|
||||
OrderSubTypePredicate _order_sub_type;
|
||||
@@ -125,7 +125,9 @@ namespace mbts {
|
||||
[PredicateCase("When order is completed", PredicateCategory.Event)]
|
||||
CompletedEvent,
|
||||
[PredicateCase("When order is canceled", PredicateCategory.Event)]
|
||||
CanceledEvent
|
||||
CanceledEvent,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
AttachedTargetOrderKey
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
@@ -176,6 +178,8 @@ namespace mbts {
|
||||
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
|
||||
[ProtoMember(24)]
|
||||
public bool is_toast_enabled { get; set; }
|
||||
[ProtoMember(25)]
|
||||
public UIntPredicate attached_order_target_key { get => _attached_target_order_key; set => _set(ref _attached_target_order_key, value); }
|
||||
|
||||
public PTOC predicate_type_case {
|
||||
get {
|
||||
@@ -202,45 +206,44 @@ namespace mbts {
|
||||
if (_filled_event != null) return PTOC.FilledEvent;
|
||||
if (_completed_event != null) return PTOC.CompletedEvent;
|
||||
if (_canceled_event != null) return PTOC.CanceledEvent;
|
||||
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
|
||||
return PTOC.None;
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<OrderSecurity> create_predicate(INotificationServiceDataCache data_cache) {
|
||||
switch (predicate_type_case) {
|
||||
case PTOC.Security: return wrap(_security, r => r.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
case PTOC.Time: return wrap(_time_created, r => r.order.created());
|
||||
case PTOC.LastChange: return wrap(_last_change, r => r.order.last_change());
|
||||
case PTOC.User: return wrap(_user, r => r.order.user_id());
|
||||
case PTOC.Account: return wrap(_account, r => r.order.account_id());
|
||||
case PTOC.Exchange: return wrap(_exchange, r => r.security.exchange_or_default());
|
||||
case PTOC.TimeInForce: return wrap(_time_in_force, r => r.order.time_in_force());
|
||||
case PTOC.OrderType: return wrap(_order_type, r => r.order.order_type());
|
||||
case PTOC.SubType: return wrap(_order_sub_type, r => r.order.sub_type());
|
||||
case PTOC.Side: return wrap(_side, r => r.order.side());
|
||||
case PTOC.Size: return wrap(_quantity, r => r.order.quantity());
|
||||
case PTOC.Price: return wrap(_price, r => r.order.price());
|
||||
case PTOC.Filled: return wrap(_filled_quantity, r => r.order.filled());
|
||||
case PTOC.AvgFilled: return wrap(_avg_price, r => r.order.avg_price());
|
||||
case PTOC.LastPrice: return wrap(_last_price, r => r.order.last_price());
|
||||
case PTOC.Status: return wrap(_order_status, r => r.order.status());
|
||||
case PTOC.LastMessage: return wrap(_last_message, r => r.order.last_msg());
|
||||
case PTOC.SecurityKey: return wrap(_security_key, r => r.order.security_key());
|
||||
case PTOC.OrderKey: return wrap(_order_key, r => r.order.Key);
|
||||
case PTOC.ParentKey: return wrap(_parent_key, r => r.order.ParentKey);
|
||||
case PTOC.Security: return wrap(_security.create(), r => r.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
case PTOC.Time: return wrap(_time_created.create(), r => r.order.created());
|
||||
case PTOC.LastChange: return wrap(_last_change.create(), r => r.order.last_change());
|
||||
case PTOC.User: return wrap(_user.create(), r => r.order.user_id());
|
||||
case PTOC.Account: return wrap(_account.create(), r => r.order.account_id());
|
||||
case PTOC.Exchange: return wrap(_exchange.create(), r => r.security.exchange_or_default());
|
||||
case PTOC.TimeInForce: return wrap(_time_in_force.create(), r => r.order.time_in_force());
|
||||
case PTOC.OrderType: return wrap(_order_type.create(), r => r.order.order_type());
|
||||
case PTOC.SubType: return wrap(_order_sub_type.create(), r => r.order.sub_type());
|
||||
case PTOC.Side: return wrap(_side.create(), r => r.order.side());
|
||||
case PTOC.Size: return wrap(_quantity.create(), r => r.order.quantity());
|
||||
case PTOC.Price: return wrap(_price.create(), r => r.order.price());
|
||||
case PTOC.Filled: return wrap(_filled_quantity.create(), r => r.order.filled());
|
||||
case PTOC.AvgFilled: return wrap(_avg_price.create(), r => r.order.avg_price());
|
||||
case PTOC.LastPrice: return wrap(_last_price.create(), r => r.order.last_price());
|
||||
case PTOC.Status: return wrap(_order_status.create(), r => r.order.status());
|
||||
case PTOC.LastMessage: return wrap(_last_message.create(), r => r.order.last_msg());
|
||||
case PTOC.SecurityKey: return wrap(_security_key.create(), r => r.order.security_key());
|
||||
case PTOC.OrderKey: return wrap(_order_key.create(), r => r.order.Key);
|
||||
case PTOC.ParentKey: return wrap(_parent_key.create(), r => r.order.ParentKey);
|
||||
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
|
||||
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
|
||||
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
|
||||
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key, r => r.order.attached_target_order_key());
|
||||
default: return null;
|
||||
}
|
||||
//
|
||||
IPredicate<OrderSecurity> wrap<T>(IPredicateBuilder<T> trigger, Func<OrderSecurity, T> select) =>
|
||||
trigger.create(data_cache).wrap<OrderSecurity,T>(select);
|
||||
IPredicate<OrderSecurity> wrap<T>(IPredicate<T> trigger, Func<OrderSecurity, T> select) =>
|
||||
trigger.wrap<OrderSecurity, T>(select);
|
||||
}
|
||||
|
||||
IPredicate<OrderSecurity> IPredicateBuilder<OrderSecurity>.create(object obj) =>
|
||||
create_predicate((INotificationServiceDataCache)obj);
|
||||
|
||||
public INotificationPredicate<OrderSecurity> create_notification_predicate(INotificationServiceDataCache notification_service_data_cache) {
|
||||
if (!is_toast_enabled)
|
||||
return null;
|
||||
@@ -252,9 +255,6 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
INotificationPredicate<OrderSecurity> INotificationPredicateBuilder<OrderSecurity>.create_notification_predicate(object obj) =>
|
||||
create_notification_predicate((INotificationServiceDataCache)obj);
|
||||
|
||||
public bool Equals(OrderRecordTuplePredicate other) {
|
||||
if (other == null)
|
||||
return false;
|
||||
@@ -285,6 +285,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
|
||||
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
|
||||
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
|
||||
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
|
||||
case PTOC.None: return true;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
}
|
||||
@@ -319,6 +320,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
|
||||
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
|
||||
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
|
||||
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
|
||||
case PTOC.None: raw_hash = 0; break;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
}
|
||||
@@ -353,6 +355,7 @@ namespace mbts {
|
||||
_filled_event = null;
|
||||
_completed_event = null;
|
||||
_canceled_event = null;
|
||||
_attached_target_order_key = null;
|
||||
}
|
||||
field = value;
|
||||
}
|
||||
@@ -378,9 +381,9 @@ namespace mbts {
|
||||
_sound_animations = new List<SoundNotification> ( sound_animations );
|
||||
}
|
||||
|
||||
public OrderRecordTuplePredicate predicate_builder => _predicate;
|
||||
public OrderRecordTuplePredicate predicate_data => _predicate;
|
||||
public IReadOnlyList<CellAnimation> cell_animations => _cell_animations ?? (_cell_animations = new List<CellAnimation>());
|
||||
public IReadOnlyList<RowAnimation> row_animations => _row_animations ?? (_row_animations = new List<RowAnimation>());
|
||||
public IReadOnlyList<SoundNotification> sound_notifications => _sound_animations ?? (_sound_animations = new List<SoundNotification>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -24,7 +23,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class DoublePredicate : IPredicateBuilder<double>, IEquatable<DoublePredicate> {
|
||||
class DoublePredicate : IEquatable<DoublePredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// greater than vs less than for specific values, or inside vs outside for value-ranges
|
||||
// trading a little more code for more clarity
|
||||
@@ -62,7 +61,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<double> create(object obj) {
|
||||
public IPredicate<double> create() {
|
||||
switch (predicate_type_case) {
|
||||
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
|
||||
case PredicateTypeOneofCase.LessThan: return PredicateBuilder.less_than(_less_than.Value);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
@@ -23,7 +22,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class SidePredicate : IPredicateBuilder<Side>, IEquatable<SidePredicate> {
|
||||
class SidePredicate : IEquatable<SidePredicate> {
|
||||
Side? _is_value;
|
||||
Side? _not_value;
|
||||
|
||||
@@ -48,7 +47,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<Side> create(object obj) {
|
||||
public IPredicate<Side> create() {
|
||||
switch (predicate_type_case) {
|
||||
case PredicateTypeOneofCase.IsValue: return PredicateBuilder.equals(_is_value.Value);
|
||||
case PredicateTypeOneofCase.NotValue: return PredicateBuilder.not_equals(_not_value.Value);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -27,7 +25,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class StringPredicate : IPredicateBuilder<string>, IEquatable<StringPredicate> {
|
||||
class StringPredicate : IEquatable<StringPredicate> {
|
||||
StringIgnoreCase? _is_string, _not_string, _contains_string, _not_contains_string;
|
||||
|
||||
public enum PredicateTypeOneofCase {
|
||||
@@ -61,7 +59,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<string> create(object obj) {
|
||||
public IPredicate<string> create() {
|
||||
switch (predicate_type_case) {
|
||||
case PTOC.IsMatch: return PredicateBuilder.equals(_is_string.Value.text, _get_eq_comparer(_is_string));
|
||||
case PTOC.NotMatch: return PredicateBuilder.not_equals(_not_string.Value.text, _get_eq_comparer(_not_string));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -21,7 +20,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class TimePredicate : IPredicateBuilder<DateTime>, IEquatable<TimePredicate> {
|
||||
class TimePredicate : IEquatable<TimePredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// before vs after for specific times, or inside vs outside for time-ranges
|
||||
// trading a little more code for clarity
|
||||
@@ -59,7 +58,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<DateTime> create(object obj) {
|
||||
public IPredicate<DateTime> create() {
|
||||
switch (predicate_type_case) {
|
||||
case PTOC.Before: return PredicateBuilder.less_than(_before.Value.get_date_time());
|
||||
case PTOC.After: return PredicateBuilder.greater_than(_after.Value.get_date_time());
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -24,7 +23,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class UIntPredicate : IPredicateBuilder<uint>, IEquatable<UIntPredicate> {
|
||||
class UIntPredicate : IEquatable<UIntPredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// greater than vs less than for specific values, or inside vs outside for value-ranges
|
||||
// trading a little more code for more clarity
|
||||
@@ -62,7 +61,7 @@ namespace mbts {
|
||||
}
|
||||
}
|
||||
|
||||
public IPredicate<uint> create(object obj) {
|
||||
public IPredicate<uint> create() {
|
||||
switch (predicate_type_case) {
|
||||
case PredicateTypeOneofCase.GreaterThan: return PredicateBuilder.greater_than(_greater_than.Value);
|
||||
case PredicateTypeOneofCase.LessThan: return PredicateBuilder.less_than(_less_than.Value);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -24,7 +23,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class UShortPredicate : IPredicateBuilder<ushort>, IEquatable<UShortPredicate> {
|
||||
class UShortPredicate : IEquatable<UShortPredicate> {
|
||||
// not using a 'negate' flag here bc its meaning is not consistent for all value-types
|
||||
// greater than vs less than for specific values, or inside vs outside for value-ranges
|
||||
// trading a little more code for more clarity
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace mbts {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class SecurityPredicate : IPredicateBuilder<SecurityImmutable>, IEquatable<SecurityPredicate> {
|
||||
class SecurityPredicate : IEquatable<SecurityPredicate> {
|
||||
[ProtoMember(1)]
|
||||
SecurityCriteria _criteria;
|
||||
[ProtoMember(2)]
|
||||
@@ -43,7 +43,8 @@ namespace mbts {
|
||||
_criteria == null ? PTOC.None :
|
||||
_negate ? PTOC.NotInCriteria : PTOC.InCriteria;
|
||||
|
||||
public IPredicate<SecurityImmutable> create(object obj) => _criteria.is_defined() ? new MyPredicate(_criteria, _negate) : null;
|
||||
public IPredicate<SecurityImmutable> create() => _criteria.is_defined() ? new MyPredicate(_criteria, _negate) : null;
|
||||
|
||||
public bool Equals(SecurityPredicate other) =>
|
||||
other != null &&
|
||||
_negate == other._negate &&
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -14,23 +12,6 @@ namespace mbts {
|
||||
bool trigger(T value);
|
||||
}
|
||||
|
||||
interface IPredicateBuilder<T> {
|
||||
IPredicate<T> create(object obj);
|
||||
}
|
||||
|
||||
interface INotificationPredicateBuilder<T> : IPredicateBuilder<T> {
|
||||
bool is_toast_enabled { get; set; }
|
||||
INotificationPredicate<T> create_notification_predicate(object obj);
|
||||
}
|
||||
|
||||
interface IPredicateBuilder<T, TArg> : IPredicateBuilder<T> {
|
||||
IPredicate<T> create_predicate(TArg arg);
|
||||
}
|
||||
|
||||
interface INotificationPredicateBuilder<T, TArg> : IPredicateBuilder<T, TArg>, INotificationPredicateBuilder<T> {
|
||||
INotificationPredicate<T> create_notification_predicate(TArg arg);
|
||||
}
|
||||
|
||||
public enum PredicateCategory {
|
||||
Normal,
|
||||
Event
|
||||
|
||||
+36
-41
@@ -13,25 +13,23 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.settings {
|
||||
interface IPredicateBuilderVM<TRowData, TPredicateBuilder, TPredicateBuilderOneof> : ITemplatedPropertyWrapperVM<TPredicateBuilder>
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData>
|
||||
where TPredicateBuilderOneof : struct {
|
||||
IReadOnlyCollection<TPredicateBuilderOneof> predicate_types { get; }
|
||||
interface IPredicateVM<TRowData, TPredicateData, TPredicateDataOneof> : ITemplatedPropertyWrapperVM<TPredicateData> where TPredicateDataOneof : struct {
|
||||
IReadOnlyCollection<TPredicateDataOneof> predicate_types { get; }
|
||||
ICollectionView predicate_types_view { get; }
|
||||
TPredicateBuilderOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TPredicateBuilder> selected_predicate { get; }
|
||||
TPredicateDataOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TPredicateData> selected_predicate { get; }
|
||||
bool can_enable_toast_notifications { get; }
|
||||
bool toast_notifications_enabled { get; }
|
||||
}
|
||||
|
||||
interface IChildPredicateBuilderVM<TSource, TChildPredicateBuilder, TChildPredicateBuilderOneof> : ITemplatedPropertyWrapperVM<TSource> where TChildPredicateBuilderOneof : struct {
|
||||
IReadOnlyCollection<TChildPredicateBuilderOneof> predicate_types { get; }
|
||||
interface IChildPredicateVM<TSource, TChildPredicate, TChildPredicateOneof> : ITemplatedPropertyWrapperVM<TSource> where TChildPredicateOneof : struct {
|
||||
IReadOnlyCollection<TChildPredicateOneof> predicate_types { get; }
|
||||
ICollectionView predicate_types_view { get; }
|
||||
TChildPredicateBuilderOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TChildPredicateBuilder> selected_predicate { get; }
|
||||
TChildPredicateOneof selected_predicate_type { get; set; }
|
||||
ITemplatedPropertyWrapperVM<TChildPredicate> selected_predicate { get; }
|
||||
}
|
||||
|
||||
static class PredicateBuilderVMWrapper {
|
||||
static class PredicateVMWrapper {
|
||||
static readonly bool _toast_notifications_enabled_global = Configuration.get<bts.config.toggles.ToastNotifications>().enabled;
|
||||
|
||||
public static ITemplatedPropertyWrapperVM<TSource> wrap_child_oneof<TSource, TChild, TChildOneof>(
|
||||
@@ -39,14 +37,17 @@ namespace mbts.ui.settings {
|
||||
where TChildOneof : struct
|
||||
where TChild : new() =>
|
||||
//
|
||||
new PredicateChildWrapperVM<TSource, TChild, TChildOneof>(get, set, get_child_oneof, oneof_vms);
|
||||
new ChildImpl<TSource, TChild, TChildOneof>(get, set, get_child_oneof, oneof_vms);
|
||||
|
||||
public static IPredicateBuilderVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
|
||||
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key)
|
||||
where TPredicateOneof : struct
|
||||
where TPredicate : IPredicateBuilder<TRowData>, new() =>
|
||||
public static IPredicateVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
|
||||
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key) where TPredicateOneof : struct =>
|
||||
//
|
||||
new PredicateWrapperVM<TRowData, TPredicate, TPredicateOneof>(oneofs, key);
|
||||
new Impl<TRowData, TPredicate, TPredicateOneof>(oneofs, key, _ => false, (p, b) => { });
|
||||
|
||||
public static IPredicateVM<TRowData, TPredicate, TPredicateOneof> create<TRowData, TPredicate, TPredicateOneof>(
|
||||
Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key, Func<TPredicate, bool> get_is_toast_enabled, Action<TPredicate, bool> set_is_toast_enabled) where TPredicateOneof : struct =>
|
||||
//
|
||||
new Impl<TRowData, TPredicate, TPredicateOneof>(oneofs, key, get_is_toast_enabled, set_is_toast_enabled);
|
||||
|
||||
public static ITemplatedPropertyWrapperVM<TSource> create<TSource>(Action<TSource> set = null) =>
|
||||
new PredicatePlaceholderVM<TSource>(set);
|
||||
@@ -63,35 +64,30 @@ namespace mbts.ui.settings {
|
||||
return cv;
|
||||
}
|
||||
|
||||
class PredicateWrapperVM<TRowData, TPredicate, TPredicateOneof> : DisposableViewModelBase, IPredicateBuilderVM<TRowData, TPredicate, TPredicateOneof>, ITemplatedPropertyWrapperVM<TPredicate>
|
||||
where TPredicateOneof : struct
|
||||
where TPredicate : IPredicateBuilder<TRowData>, new() {
|
||||
class Impl<TRowData, TPredicate, TPredicateOneof> : DisposableViewModelBase, IPredicateVM<TRowData, TPredicate, TPredicateOneof>, ITemplatedPropertyWrapperVM<TPredicate>
|
||||
where TPredicateOneof : struct {
|
||||
readonly Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> _oneofs;
|
||||
readonly Property<TPredicateOneof> _selected_predicate_type;
|
||||
readonly Property<ITemplatedPropertyWrapperVM<TPredicate>> _selected_predicate;
|
||||
readonly DelegateProperty<bool> _can_enable_toast_notifications;
|
||||
readonly Property<bool> _toast_notifications_enabled;
|
||||
readonly Func<TPredicate, TPredicateOneof> _key_selector;
|
||||
readonly Func<TPredicate, bool> _get_is_toast_enabled;
|
||||
readonly Action<TPredicate, bool> _set_is_toast_enabeled;
|
||||
|
||||
public PredicateWrapperVM(Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key_selector) {
|
||||
public Impl(Dictionary<TPredicateOneof, ITemplatedPropertyWrapperVM<TPredicate>> oneofs, Func<TPredicate, TPredicateOneof> key_selector, Func<TPredicate, bool> get_is_toast_enabled, Action<TPredicate, bool> set_is_toast_enabled) {
|
||||
_oneofs = oneofs;
|
||||
_key_selector = key_selector;
|
||||
_get_is_toast_enabled = get_is_toast_enabled;
|
||||
_set_is_toast_enabeled = set_is_toast_enabled;
|
||||
_selected_predicate = backing<ITemplatedPropertyWrapperVM<TPredicate>>(nameof(selected_predicate));
|
||||
_selected_predicate_type = backing<TPredicateOneof>(nameof(selected_predicate_type));
|
||||
predicate_types_view = _create_predicate_types_view(predicate_types);
|
||||
if (typeof(TPredicate).GetInterfaces().Contains(typeof(INotificationPredicateBuilder<TRowData>))) {
|
||||
// simple_predicates are only referred to when TPredicate is an INotificationPredicateBuilder, but does not support INotificationPredicates for every predicate-case
|
||||
// (this way we can gradually add notifications as-needed beasis)
|
||||
// for ex: we may release a cut that supports fill notifications without having to create a notification-predicate for every predicate-case in OrderRecordTuplePredicate.
|
||||
// Note: it is assumed that all TPredicateOneof cases DO SUPPORT notification-predicates UNLESS THEY SPECIFY OTHERWISE IN A PREDICATECASEATTRIBUTE.
|
||||
var simple_predicates = new HashSet<TPredicateOneof>(oneofs.Keys.Where(k => {
|
||||
var pca = EnumHelper.get_attribute<TPredicateOneof, PredicateCaseAttribute>(k);
|
||||
return pca != null && !pca.is_toastable;
|
||||
}));
|
||||
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => !simple_predicates.Contains(selected_predicate_type), nameof(selected_predicate_type));
|
||||
} else {
|
||||
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => false);
|
||||
}
|
||||
var toastable_predicates = new HashSet<TPredicateOneof>(oneofs.Keys.Where(k => {
|
||||
var pca = EnumHelper.get_attribute<TPredicateOneof, PredicateCaseAttribute>(k);
|
||||
return pca != null && pca.is_toastable;
|
||||
}));
|
||||
_can_enable_toast_notifications = backing(nameof(can_enable_toast_notifications), () => toastable_predicates.Contains(selected_predicate_type), nameof(selected_predicate_type));
|
||||
_toast_notifications_enabled = backing<bool>(nameof(toast_notifications_enabled));
|
||||
selected_predicate_type = predicate_types.FirstOrDefault();
|
||||
}
|
||||
@@ -118,19 +114,18 @@ namespace mbts.ui.settings {
|
||||
public void load_from(TPredicate value) {
|
||||
selected_predicate_type = _key_selector(value);
|
||||
selected_predicate?.load_from(value);
|
||||
if (can_enable_toast_notifications) {
|
||||
_toast_notifications_enabled.set(((INotificationPredicateBuilder<TRowData>)value).is_toast_enabled);
|
||||
}
|
||||
if (can_enable_toast_notifications)
|
||||
_toast_notifications_enabled.set(_get_is_toast_enabled(value));
|
||||
}
|
||||
|
||||
public void assign_to(TPredicate value) {
|
||||
selected_predicate?.assign_to(value);
|
||||
if (toast_notifications_enabled && value is INotificationPredicateBuilder<TRowData> tp)
|
||||
tp.is_toast_enabled = true;
|
||||
if (can_enable_toast_notifications)
|
||||
_set_is_toast_enabeled(value, toast_notifications_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
class PredicateChildWrapperVM<TPredicate, TChild, TChildKey> : DisposableViewModelBase, IChildPredicateBuilderVM<TPredicate, TChild, TChildKey> where TChildKey : struct where TChild : new() {
|
||||
class ChildImpl<TPredicate, TChild, TChildKey> : DisposableViewModelBase, IChildPredicateVM<TPredicate, TChild, TChildKey> where TChildKey : struct where TChild : new() {
|
||||
readonly Property<ITemplatedPropertyWrapperVM<TChild>> _selected_predicate;
|
||||
readonly Property<TChildKey> _selected_predicate_type;
|
||||
readonly Dictionary<TChildKey, ITemplatedPropertyWrapperVM<TChild>> _children;
|
||||
@@ -139,7 +134,7 @@ namespace mbts.ui.settings {
|
||||
readonly Func<TChild, TChildKey> _key;
|
||||
readonly string _template_key;
|
||||
|
||||
public PredicateChildWrapperVM(Func<TPredicate, TChild> get, Action<TPredicate, TChild> set, Func<TChild, TChildKey> key,
|
||||
public ChildImpl(Func<TPredicate, TChild> get, Action<TPredicate, TChild> set, Func<TChild, TChildKey> key,
|
||||
Dictionary<TChildKey, ITemplatedPropertyWrapperVM<TChild>> oneofs, string template = PredicateTemplateKeys.Recursive) {
|
||||
_get = get;
|
||||
_set = set;
|
||||
@@ -1,7 +1,10 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:control="clr-namespace:mbts.ui.controls">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:control="clr-namespace:mbts.ui.controls"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Images/Vector/Icons.xaml" />
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml" />
|
||||
@@ -516,9 +519,16 @@
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<control:SparkMenuItem
|
||||
Content="{Binding title}" Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||
Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
|
||||
/>
|
||||
>
|
||||
<control:SparkMenuItem.Content>
|
||||
<MultiBinding Converter="{x:Static bv:SparkWindowView.HeaderConverter}">
|
||||
<Binding Path="title"/>
|
||||
<Binding Path="key_gesture"/>
|
||||
</MultiBinding>
|
||||
</control:SparkMenuItem.Content>
|
||||
</control:SparkMenuItem>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,13 +12,15 @@ using static mbts.spark.SparkTags;
|
||||
|
||||
namespace bts.spark {
|
||||
public abstract class SparkCommand {
|
||||
public readonly KeyGesture key_gesture;
|
||||
string _title;
|
||||
bool _show_in_toolbar, _show_in_context_menu;
|
||||
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true) {
|
||||
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true, KeyGesture key_gesture = null) {
|
||||
_title = title;
|
||||
this.icon = icon;
|
||||
_show_in_toolbar = show_in_toolbar;
|
||||
_show_in_context_menu = show_in_context_menu;
|
||||
this.key_gesture = key_gesture;
|
||||
}
|
||||
|
||||
public event Action title_changed, show_in_toolbar_changed, show_in_context_menu_changed;
|
||||
@@ -78,11 +81,15 @@ namespace bts.spark {
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, Func<T, bool> can_execute) =>
|
||||
create(title, icon, tag, parameter, parameter.Select(can_execute));
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, Func<T, bool> can_execute, Func<bool> is_confirmation_affirmed) =>
|
||||
create(title, icon, tag, parameter, parameter.Select(can_execute), is_confirmation_affirmed);
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T, TSource>(string title, ImageSource icon, ITag<T> tag, IObservable<TSource> parameter_source, Func<TSource, bool> parameter_source_where, Func<TSource, T> parameter_selector) =>
|
||||
create(title, icon, tag, parameter_source.Select(parameter_selector), parameter_source.Select(parameter_source_where));
|
||||
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) =>
|
||||
new SparkCommandParamObserver<T>(title, icon, tag, parameter, can_execute);
|
||||
public static SparkCommandParamObserver<T> create<T>(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute, Func<bool> is_confirmation_affirmed = null) =>
|
||||
new SparkCommandParamObserver<T>(title, icon, tag, parameter, can_execute, is_confirmation_affirmed);
|
||||
|
||||
}
|
||||
|
||||
public static class SparkCommandExtensions {
|
||||
@@ -132,11 +139,15 @@ namespace bts.spark {
|
||||
public SparkCommandParamObserver<T> complete(IObservable<T> value, Func<T, bool> can_execute) =>
|
||||
SparkCommand.create(title, icon, tag, value, can_execute);
|
||||
|
||||
public SparkCommandParamObserver<T> complete(IObservable<T> value, Func<T, bool> can_execute, Func<bool> is_confirmation_affirmed) =>
|
||||
SparkCommand.create(title, icon, tag, value, can_execute, is_confirmation_affirmed);
|
||||
|
||||
public SparkCommandParamObserver<T> complete<TSource>(IObservable<TSource> value_source, Func<TSource, bool> value_source_where, Func<TSource, T> value_selector) =>
|
||||
SparkCommand.create(title, icon, tag, value_source, value_source_where, value_selector);
|
||||
|
||||
public SparkCommandParamObserver<T> complete(IObservable<T> parameter, IObservable<bool> can_execute) =>
|
||||
SparkCommand.create(title, icon, tag, parameter, can_execute);
|
||||
|
||||
}
|
||||
|
||||
public class SparkCommandWrapper : SparkCommand, IDisposable {
|
||||
@@ -199,7 +210,8 @@ namespace bts.spark {
|
||||
public class SparkCommandCommand : SparkCommand {
|
||||
readonly ICommand _a;
|
||||
|
||||
public SparkCommandCommand(string title, ImageSource icon, ICommand a) : base(title, icon) {
|
||||
public SparkCommandCommand(string title, ImageSource icon, ICommand a, KeyGesture key_gesture = null)
|
||||
: base(title, icon, key_gesture: key_gesture) {
|
||||
_a = a;
|
||||
}
|
||||
|
||||
@@ -222,8 +234,9 @@ namespace bts.spark {
|
||||
}
|
||||
|
||||
public class SparkCommandParamObserver<T> : SparkCommand, IDisposable {
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute, Func<bool> is_confirmation_affirmed = null) : base(title, icon) {
|
||||
_tag = tag;
|
||||
_is_confirmation_affirmed = is_confirmation_affirmed;
|
||||
_subs = Disposable.Create(
|
||||
parameter.OnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.OnDispatcher().Subscribe(ce => {
|
||||
@@ -237,10 +250,12 @@ namespace bts.spark {
|
||||
IDisposable _subs;
|
||||
T _parameter;
|
||||
bool _can_execute;
|
||||
Func<bool> _is_confirmation_affirmed;
|
||||
|
||||
|
||||
public override bool canExecute() => _can_execute;
|
||||
public override void execute(IBus bus) {
|
||||
if (_can_execute) {
|
||||
if (_can_execute && (_is_confirmation_affirmed == null || _is_confirmation_affirmed())) {
|
||||
try {
|
||||
bus.post(_tag, _parameter);
|
||||
} catch (Exception e) {
|
||||
@@ -314,6 +329,7 @@ namespace bts.spark {
|
||||
}
|
||||
|
||||
public string title => _cmd.title;
|
||||
public KeyGesture key_gesture => _cmd.key_gesture;
|
||||
public ImageSource icon => _cmd.icon;
|
||||
public bool show_in_toolbar => _cmd.show_in_toolbar;
|
||||
public bool show_in_context_menu => _cmd.show_in_context_menu;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark
|
||||
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
|
||||
|
||||
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> CrackOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> DetachOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
|
||||
|
||||
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbts.ui.controls {
|
||||
var rv = new ObservableAggregateCollection<TInner>();
|
||||
var aggregator = new Aggregator<TInner>(rv);
|
||||
foreach (var o in source)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
source.CollectionChanged += _mk_outter_collection_changed_handler(aggregator, select_inner);
|
||||
return rv;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace mbts.ui.controls {
|
||||
aggregate.Clear();
|
||||
var aggregator = new Aggregator<TInner>(aggregate);
|
||||
foreach (var o in source)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
var handler = _mk_outter_collection_changed_handler(aggregator, select_inner);
|
||||
source.CollectionChanged += handler;
|
||||
return Disposable.Create(() => source.CollectionChanged -= handler);
|
||||
@@ -37,23 +37,23 @@ namespace mbts.ui.controls {
|
||||
add_collections(e.NewStartingIndex, e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
aggregator.clear_collections();
|
||||
aggregator.Clear();
|
||||
foreach (TOutter o in (IEnumerable<TOutter>)s)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
break;
|
||||
}
|
||||
};
|
||||
void add_collections(int new_item_index, IList new_items) {
|
||||
foreach (TOutter o in new_items)
|
||||
aggregator.insert_collection(new_item_index++, select_inner(o));
|
||||
aggregator.Insert(new_item_index++, select_inner(o));
|
||||
}
|
||||
void remove_collections(int old_item_index, IList old_items) {
|
||||
foreach (TOutter o in old_items)
|
||||
aggregator.remove_collection_at(old_item_index);
|
||||
aggregator.RemoveAt(old_item_index);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Aggregator<T> : IDisposable {
|
||||
internal class Aggregator<T> : IDisposable, IList<ObservableCollection<T>>, IReadOnlyList<ObservableCollection<T>> {
|
||||
readonly ObservableCollection<T> _target;
|
||||
readonly List<ObservableCollection<T>> _collections = new List<ObservableCollection<T>>();
|
||||
bool _is_disposed;
|
||||
@@ -61,28 +61,39 @@ namespace mbts.ui.controls {
|
||||
public Aggregator(ObservableCollection<T> target, params ObservableCollection<T>[] collections) {
|
||||
_target = target;
|
||||
foreach (var c in collections)
|
||||
add_collection(c);
|
||||
Add(c);
|
||||
}
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) {
|
||||
public ObservableCollection<T> this[int index] {
|
||||
get => _collections[index];
|
||||
set => Insert(index, value);
|
||||
}
|
||||
|
||||
public int Count => _collections.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(ObservableCollection<T> collection) {
|
||||
_check_disposed();
|
||||
_target.AddRange(collection);
|
||||
_collections.Add(collection);
|
||||
collection.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public void remove_collection(ObservableCollection<T> collection) {
|
||||
public bool Remove(ObservableCollection<T> collection) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(collection, out int _, out int collection_index);
|
||||
_collections.RemoveAt(collection_index);
|
||||
collection.CollectionChanged -= _on_collection_changed;
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) {
|
||||
public void Insert(int index, ObservableCollection<T> collection) {
|
||||
_check_disposed();
|
||||
if (index < 0 || index > _collections.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (index == _collections.Count)
|
||||
add_collection(collection);
|
||||
Add(collection);
|
||||
else {
|
||||
_collections.Insert(index, collection);
|
||||
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
|
||||
@@ -90,12 +101,12 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_collection_at(int index) {
|
||||
public void RemoveAt(int index) {
|
||||
_check_disposed();
|
||||
remove_collection(_collections[index]);
|
||||
Remove(_collections[index]);
|
||||
}
|
||||
|
||||
public void clear_collections() {
|
||||
public void Clear() {
|
||||
_check_disposed();
|
||||
foreach (var c in _collections)
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
@@ -103,6 +114,15 @@ namespace mbts.ui.controls {
|
||||
_target.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(ObservableCollection<T> item) =>
|
||||
_collections.Contains(item);
|
||||
|
||||
public void CopyTo(ObservableCollection<T>[] array, int arrayIndex) =>
|
||||
_collections.CopyTo(array, arrayIndex);
|
||||
|
||||
public int IndexOf(ObservableCollection<T> item) =>
|
||||
_collections.IndexOf(item);
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
foreach (var c in _collections)
|
||||
@@ -111,6 +131,9 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<ObservableCollection<T>> GetEnumerator() => _collections.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
_check_disposed();
|
||||
int offset = _get_collection_start(sender);
|
||||
@@ -166,10 +189,12 @@ namespace mbts.ui.controls {
|
||||
public ObservableAggregateCollection(params ObservableCollection<T>[] collections) =>
|
||||
_aggregator = new ObservableAggregateCollection.Aggregator<T>(this, collections);
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) => _aggregator.add_collection(collection);
|
||||
public void remove_collection(ObservableCollection<T> collection) => _aggregator.remove_collection(collection);
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.insert_collection(index, collection);
|
||||
public void remove_collection_at(int index) => _aggregator.remove_collection_at(index);
|
||||
public void clear_collections() => _aggregator.clear_collections();
|
||||
public IReadOnlyList<ObservableCollection<T>> source_collections => _aggregator;
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) => _aggregator.Add(collection);
|
||||
public void remove_collection(ObservableCollection<T> collection) => _aggregator.Remove(collection);
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.Insert(index, collection);
|
||||
public void remove_collection_at(int index) => _aggregator.RemoveAt(index);
|
||||
public void clear_collections() => _aggregator.Clear();
|
||||
}
|
||||
}
|
||||
@@ -192,16 +192,16 @@ namespace mbts.ui.utility {
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) {
|
||||
foreach (T v in source)
|
||||
action(v);
|
||||
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
|
||||
}
|
||||
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) =>
|
||||
connect_item_actions(source, source, action);
|
||||
|
||||
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) {
|
||||
foreach (T v in source)
|
||||
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) =>
|
||||
connect_item_actions(source, source, action);
|
||||
|
||||
public static IDisposable connect_item_actions<T>(INotifyCollectionChanged notifier, IEnumerable<T> collection, Action<T> action) {
|
||||
foreach (T v in collection)
|
||||
action(v);
|
||||
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
|
||||
return notifier.Subscribe((s, e) => CollectionHelper.process_new_items(collection, action, e));
|
||||
}
|
||||
|
||||
public static IDisposable connect_item_hooks<T>(IReadOnlyObservableCollection<T> source, Func<T, IDisposable> get_item_hook) {
|
||||
@@ -218,6 +218,18 @@ namespace mbts.ui.utility {
|
||||
source.Subscribe((s, e) => CollectionHelper.reflect_change(item_hooks, source, get_item_hook, d => d.Dispose(), e)));
|
||||
}
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) where T : IDisposable =>
|
||||
connect_item_hooks(source, v => v);
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose) where T : IDisposable =>
|
||||
connect_item_hooks(source, v => v, clear_source_on_dispose);
|
||||
|
||||
public static IDisposable try_dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) =>
|
||||
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty);
|
||||
|
||||
public static IDisposable try_dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose = false) =>
|
||||
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty, clear_source_on_dispose);
|
||||
|
||||
class SortedWrapper<T> : IReadOnlyObservableCollection<T> {
|
||||
const int _min_capacity = 4;
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mbts.ui.utilities
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly TSourceCollection _source;
|
||||
readonly IList<T> _target;
|
||||
IDictionary<T, int> _cache = new Dictionary<T, int>();
|
||||
IDictionary<T, int> _cache;
|
||||
IDisposable _sub;
|
||||
|
||||
public DistinctCollectionResolver(TSourceCollection source, IList<T> target, Func<TSource, T> get_value)
|
||||
@@ -38,6 +38,7 @@ namespace mbts.ui.utilities
|
||||
_target = target;
|
||||
_get_value = get_value;
|
||||
_comparer = comparer;
|
||||
_cache = new Dictionary<T, int>(comparer);
|
||||
_sync();
|
||||
_sub = _source.Subscribe(_source_CollectionChanged);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,11 @@ namespace mbts.ui.utility {
|
||||
foreach (var value in source_collection.OfType<INotifyPropertyChanged>())
|
||||
value.PropertyChanged += on_item_PropertyChanged;
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
|
||||
if (source_collection.Count == 0) {
|
||||
filtered_collection.Clear();
|
||||
} else {
|
||||
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,4 +217,4 @@ namespace mbts.ui.utility {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,8 @@ namespace mbts {
|
||||
return (uint)value;
|
||||
}
|
||||
}
|
||||
|
||||
public static double? nan_nullable(double value) =>
|
||||
double.IsNaN(value) ? (double?)null : value;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,17 @@ namespace System.Collections.Generic {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static T SafeSingleOrDefault<T>(this IEnumerable<T> source) {
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
var rv_candidate = en.Current;
|
||||
if (!en.MoveNext())
|
||||
return rv_candidate;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
foreach (T el in source) {
|
||||
@@ -86,6 +97,28 @@ namespace System.Collections.Generic {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T>(this IEnumerable<T> source, out T result) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
result = en.Current;
|
||||
if (!en.MoveNext())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T, TResult>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T, TResult> convert, out TResult result) {
|
||||
if (TryGetSingle(source.Where(predicate), out T v)) {
|
||||
result = convert(v);
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T default_rv) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
foreach (T element in source)
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Reactive.Linq;
|
||||
using System.Collections;
|
||||
using mbts.utils;
|
||||
using System.Threading.Tasks;
|
||||
using bts;
|
||||
using mbts;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using System.Collections.Specialized;
|
||||
using mbts.spark;
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
namespace mbts
|
||||
{
|
||||
namespace mbts {
|
||||
public static class GlobalExtensions
|
||||
{
|
||||
#region AddRange
|
||||
@@ -105,8 +98,8 @@ namespace mbts
|
||||
return items.GroupBy(key_selector).ToDictionary(g => g.Key, g => g.AsEnumerable());
|
||||
}
|
||||
|
||||
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_equality_comparer) {
|
||||
return items.GroupBy(key_selector, key_equality_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate));
|
||||
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_comparer) {
|
||||
return items.GroupBy(key_selector, key_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate), key_comparer);
|
||||
}
|
||||
|
||||
public static int aggregate_hash<T>(this IEnumerable<T> series) where T : class {
|
||||
@@ -238,8 +231,7 @@ namespace mbts
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Collections.Specialized
|
||||
{
|
||||
namespace System.Collections.Specialized {
|
||||
public static class NotifyCollectionChangedExtensions
|
||||
{
|
||||
public static IDisposable SuspendHandler(this INotifyCollectionChanged source, NotifyCollectionChangedEventHandler handler) {
|
||||
@@ -258,8 +250,7 @@ namespace System.Collections.Specialized
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Reactive.Linq
|
||||
{
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -397,4 +388,4 @@ namespace System.Reactive.Linq
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.shell;
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -98,8 +99,12 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
|
||||
public static string diagnostic_name(this IShellWindow window) =>
|
||||
$"[{window.window_type}]{window.Title}";
|
||||
public static string diagnostic_name(this IShellWindow window) {
|
||||
var sb = new StringBuilderEx { "[", window.window_type, "]", window.Title };
|
||||
if (window is ISharedWindow shared_win)
|
||||
sb.Add("(thread: ", shared_win.thread_id, ")");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static IObservable<string> observe_title(this IShellWindow window) {
|
||||
var src = Observable.FromEvent<Action, string>(cb => () => cb(window.Title), cb => window.TitleChanged += cb, cb => window.TitleChanged -= cb);
|
||||
|
||||
@@ -90,20 +90,25 @@ namespace mbts.utils {
|
||||
}
|
||||
|
||||
// Note: this is not optimized, be wary of use with large collections
|
||||
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src) =>
|
||||
public static void sync_with_never_duplicate<T>(IList<T> dest, IEnumerable<T> src) =>
|
||||
sync_with_never_duplicate(dest, src, EqualityComparer<T>.Default);
|
||||
|
||||
// Note: this is not optimized, be wary of use with large collections
|
||||
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
int i = 0;
|
||||
foreach (var val in src.empty_if_null()) {
|
||||
int curr_index = dest.IndexOf(val);
|
||||
public static void sync_with_never_duplicate<T>(IList<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
var src_list = src as IList<T> ?? new List<T>(src);
|
||||
var src_set = src as ISet<T> ?? new HashSet<T>(src_list);
|
||||
for (int i = 0; i < dest.Count; i++) {
|
||||
if (!src_set.Contains(dest[i]))
|
||||
dest.RemoveAt(i);
|
||||
}
|
||||
for (int i = 0; i < src_list.Count; i++) {
|
||||
int curr_index = dest.IndexOf(src_list[i]);
|
||||
if (curr_index == i) {
|
||||
continue;
|
||||
} else if (curr_index > 0 && curr_index < dest.Count) {
|
||||
} else if (curr_index >= 0 && curr_index < dest.Count) {
|
||||
dest.RemoveAt(curr_index);
|
||||
}
|
||||
dest.Insert(i++, val);
|
||||
dest.Insert(i, src_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,11 +117,16 @@ namespace mbts.utils {
|
||||
|
||||
// Note: this is not optimized, be wary of use with large collections
|
||||
public static void sync_with_never_duplicate<T, TSource, TKey>(ObservableCollection<T> dest, IEnumerable<TSource> src, Func<TSource, T> create, Func<T, TKey> key_selector, Func<TSource, TKey> src_key_selector, IEqualityComparer<TKey> key_comparer) {
|
||||
int i = 0;
|
||||
var src_map = src.ToDictionary(src_key_selector);
|
||||
var src_list = src as IList<TSource> ?? new List<TSource>(src);
|
||||
var src_map = src_list.ToDictionary(src_key_selector);
|
||||
for (int i = 0; i < dest.Count; i++) {
|
||||
var k = key_selector(dest[i]);
|
||||
if (!src_map.ContainsKey(k))
|
||||
dest.RemoveAt(i);
|
||||
}
|
||||
var dest_map = dest.ToDictionary(key_selector);
|
||||
foreach (var val in src.empty_if_null()) {
|
||||
TKey key = src_key_selector(val);
|
||||
for (int i = 0; i < src_list.Count; i++) {
|
||||
TKey key = src_key_selector(src_list[i]);
|
||||
if (dest_map.TryGetValue(key, out T dval)) {
|
||||
int curr_index = dest.IndexOf(dval);
|
||||
if (curr_index != i) {
|
||||
@@ -124,9 +134,8 @@ namespace mbts.utils {
|
||||
dest.Insert(i, dval);
|
||||
}
|
||||
} else {
|
||||
dest.Insert(i, create(val));
|
||||
dest.Insert(i, create(src_list[i]));
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +145,5 @@ namespace mbts.utils {
|
||||
rv[i] = selector(source[i]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> collection) =>
|
||||
collection.Subscribe((s, e) => {
|
||||
if (e.OldItems != null) {
|
||||
foreach (T item in e.OldItems)
|
||||
(item as IDisposable)?.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using mbts.collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.utils {
|
||||
class CommonValueMonitor<T> {
|
||||
readonly RefCountMap<T> _values;
|
||||
T _default_value;
|
||||
|
||||
public CommonValueMonitor()
|
||||
: this(default, EqualityComparer<T>.Default) { }
|
||||
|
||||
public CommonValueMonitor(T default_value)
|
||||
: this(default_value, EqualityComparer<T>.Default) { }
|
||||
|
||||
public CommonValueMonitor(T default_value, IEqualityComparer<T> comparer) {
|
||||
_default_value = default_value;
|
||||
_values = new RefCountMap<T>(comparer);
|
||||
}
|
||||
|
||||
public event Action<T> common_value_changed;
|
||||
|
||||
public ICollection<T> distinct_values => _values.keys;
|
||||
public T common_value => _values.keys.SafeSingleOrDefault();
|
||||
|
||||
public void add(T value) {
|
||||
if (_values.increment_adds_key(value)) {
|
||||
switch (_values.Count) {
|
||||
case 1: common_value_changed?.Invoke(value); break;
|
||||
case 2: common_value_changed?.Invoke(_default_value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(T value) {
|
||||
if (_values.decrement_removes_key(value)) {
|
||||
switch (_values.Count) {
|
||||
case 0: common_value_changed?.Invoke(_default_value); break;
|
||||
case 1: common_value_changed?.Invoke(_values.keys.First()); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CommonValueMonitorMultiplexor<TSource> {
|
||||
int _member_id_counter;
|
||||
readonly Dictionary<int, (Action<TSource> add, Action<TSource> remove)> _members = new Dictionary<int, (Action<TSource> add, Action<TSource> remove)>();
|
||||
|
||||
public void add(TSource value) {
|
||||
foreach ((var add, var remove) in _members.Values)
|
||||
add(value);
|
||||
}
|
||||
|
||||
public void remove(TSource value) {
|
||||
foreach ((var add, var remove) in _members.Values)
|
||||
remove(value);
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value) {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => member_mon.add(get_value(v)),
|
||||
v => member_mon.remove(get_value(v))));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value, Func<T, bool> where) {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
if (where(res))
|
||||
member_mon.add(res);
|
||||
},
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
if (where(res))
|
||||
member_mon.remove(res);
|
||||
}));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
}
|
||||
|
||||
static class CommonValueMonitorExtensions {
|
||||
public static bool try_get_common_value<T>(this CommonValueMonitor<T> monitor, out T result) =>
|
||||
monitor.distinct_values.TryGetSingle(out result);
|
||||
|
||||
public static T? common_value_nullable<T>(this CommonValueMonitor<T> monitor) where T : struct =>
|
||||
monitor.try_get_common_value(out T result) ? result : (T?)null;
|
||||
|
||||
public static IObservable<T> SubscribeThrottled<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) =>
|
||||
Observable.Create<T>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
o.OnNext(monitor.common_value);
|
||||
monitor.common_value_changed += on_changed;
|
||||
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
|
||||
async void on_changed(T _) {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
do {
|
||||
o.OnNext(monitor.common_value);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<T> SubscribeThrottled<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle, T default_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
notify();
|
||||
monitor.common_value_changed += on_changed;
|
||||
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
|
||||
async void on_changed(T _) {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
do {
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
void notify() =>
|
||||
o.OnNext(monitor.try_get_common_value(out T result) ? result : default_value);
|
||||
});
|
||||
|
||||
public static IObservable<TResult> SubscribeThrottled<T, TResult>(this CommonValueMonitor<T> monitor, TimeSpan throttle, Func<T, TResult> selector, TResult default_value) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
notify();
|
||||
monitor.common_value_changed += on_changed;
|
||||
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
|
||||
async void on_changed(T _) {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
do {
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
void notify() =>
|
||||
o.OnNext(monitor.try_get_common_value(out T result) ? selector(result) : default_value);
|
||||
});
|
||||
|
||||
public static IObservable<T?> SubscribeThrottledNullable<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) where T : struct =>
|
||||
monitor.SubscribeThrottled<T, T?>(throttle, val => val, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.utils {
|
||||
class PropertyUpdateThrottle<T> {
|
||||
readonly TimeSpan _throttle;
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
bool _busy, _dirty;
|
||||
T _value, _last_fired;
|
||||
|
||||
public PropertyUpdateThrottle(TimeSpan throttle)
|
||||
: this(throttle, EqualityComparer<T>.Default) { }
|
||||
|
||||
public PropertyUpdateThrottle(TimeSpan throttle, IEqualityComparer<T> comparer) {
|
||||
_throttle = throttle;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
public T value {
|
||||
get => _value;
|
||||
set {
|
||||
if (!_comparer.Equals(_value, value)) {
|
||||
_value = value;
|
||||
_fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChange<T> changed;
|
||||
|
||||
async void _fire() {
|
||||
if (!_busy) {
|
||||
_busy = true;
|
||||
do {
|
||||
var old = _last_fired;
|
||||
_last_fired = _value;
|
||||
changed?.Invoke(old, _value);
|
||||
_dirty = false;
|
||||
await Task.Delay(_throttle);
|
||||
} while (_dirty);
|
||||
_busy = false;
|
||||
} else {
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,115 @@
|
||||
using mbts;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mbts.utils;
|
||||
|
||||
namespace bts.utils {
|
||||
static class ProtoHelper {
|
||||
const string
|
||||
_case_suffix = "Case",
|
||||
_oneof_case_suffix = "OneofCase";
|
||||
|
||||
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
|
||||
_DescriptorImpl<T>.value;
|
||||
|
||||
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
|
||||
prop_name = prop_name.iremove_suffix(_case_suffix);
|
||||
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
|
||||
where T : IMessage, new()
|
||||
where TOneofCase : struct =>
|
||||
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
|
||||
|
||||
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
|
||||
string oneof_name = oneof_case.ToString();
|
||||
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
|
||||
}
|
||||
|
||||
public static Type get_field_type(FieldDescriptor field_descriptor) {
|
||||
switch (field_descriptor.FieldType) {
|
||||
case FieldType.Bool: return typeof(bool);
|
||||
case FieldType.Bytes: return typeof(ByteString);
|
||||
case FieldType.Double: return typeof(double);
|
||||
case FieldType.Float: return typeof(float);
|
||||
case FieldType.UInt32: return typeof(uint);
|
||||
case FieldType.UInt64: return typeof(ulong);
|
||||
// int
|
||||
case FieldType.SFixed32:
|
||||
case FieldType.Fixed32:
|
||||
case FieldType.SInt32:
|
||||
case FieldType.Int32: return typeof(int);
|
||||
// long
|
||||
case FieldType.SFixed64:
|
||||
case FieldType.Fixed64:
|
||||
case FieldType.SInt64:
|
||||
case FieldType.Int64: return typeof(long);
|
||||
//
|
||||
case FieldType.String: return typeof(string);
|
||||
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
|
||||
case FieldType.Message: return field_descriptor.MessageType.ClrType;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
|
||||
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
|
||||
|
||||
static class _DescriptorImpl<T> where T : IMessage, new() {
|
||||
public static readonly MessageDescriptor value = new T().Descriptor;
|
||||
|
||||
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
|
||||
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
|
||||
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
|
||||
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
|
||||
var dci = _descriptor_case_indices;
|
||||
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
|
||||
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
|
||||
/// 1) clone the local copy.
|
||||
/// 2) add the new KeyValue to the clone.
|
||||
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
|
||||
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
|
||||
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
|
||||
dci = dci.ToDictionary();
|
||||
dci.Add(prop_name, rv);
|
||||
/// It is possible for add ops to be overwritten.
|
||||
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
|
||||
_descriptor_case_indices = dci;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using mbts;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mbts.utils;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.utils {
|
||||
static class ProtoHelper {
|
||||
const string
|
||||
_case_suffix = "Case",
|
||||
_oneof_case_suffix = "OneofCase";
|
||||
|
||||
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
|
||||
_DescriptorImpl<T>.value;
|
||||
|
||||
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
|
||||
prop_name = prop_name.iremove_suffix(_case_suffix);
|
||||
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
|
||||
where T : IMessage, new()
|
||||
where TOneofCase : struct =>
|
||||
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
|
||||
|
||||
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
|
||||
string oneof_name = oneof_case.ToString();
|
||||
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
|
||||
}
|
||||
|
||||
public static TImmutable immutable_factory<TMutable, TImmutable>(TMutable m) where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
|
||||
(TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
|
||||
|
||||
public static IMessage mutable_factory(Type type) =>
|
||||
MutableFactoryStore.get(type)();
|
||||
|
||||
static class MutableFactoryStore {
|
||||
[ThreadStatic]
|
||||
static readonly Dictionary<Type, Func<IMessage>> _factories = new Dictionary<Type, Func<IMessage>>();
|
||||
|
||||
public static Func<IMessage> get(Type type) =>
|
||||
_factories.GetOrAdd(type,
|
||||
t => typeof(MutableFactory<>).MakeGenericType(t)
|
||||
.GetField(nameof(MutableFactory<LimitOrderDefinition>.anonymous_factory), BindingFlags.Static | BindingFlags.Public)
|
||||
.GetValue(null) as Func<IMessage>);
|
||||
}
|
||||
|
||||
static class MutableFactory<T> where T : IMessage<T>, new() {
|
||||
public static readonly Func<IMessage> anonymous_factory = () => new T();
|
||||
}
|
||||
|
||||
public static Type get_field_type(FieldDescriptor field_descriptor) {
|
||||
switch (field_descriptor.FieldType) {
|
||||
case FieldType.Bool: return typeof(bool);
|
||||
case FieldType.Bytes: return typeof(ByteString);
|
||||
case FieldType.Double: return typeof(double);
|
||||
case FieldType.Float: return typeof(float);
|
||||
case FieldType.UInt32: return typeof(uint);
|
||||
case FieldType.UInt64: return typeof(ulong);
|
||||
// int
|
||||
case FieldType.SFixed32:
|
||||
case FieldType.Fixed32:
|
||||
case FieldType.SInt32:
|
||||
case FieldType.Int32: return typeof(int);
|
||||
// long
|
||||
case FieldType.SFixed64:
|
||||
case FieldType.Fixed64:
|
||||
case FieldType.SInt64:
|
||||
case FieldType.Int64: return typeof(long);
|
||||
//
|
||||
case FieldType.String: return typeof(string);
|
||||
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
|
||||
case FieldType.Message: return field_descriptor.MessageType.ClrType;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
|
||||
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
|
||||
|
||||
static class _DescriptorImpl<T> where T : IMessage, new() {
|
||||
public static readonly MessageDescriptor value = new T().Descriptor;
|
||||
|
||||
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
|
||||
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
|
||||
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
|
||||
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
|
||||
var dci = _descriptor_case_indices;
|
||||
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
|
||||
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
|
||||
/// 1) clone the local copy.
|
||||
/// 2) add the new KeyValue to the clone.
|
||||
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
|
||||
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
|
||||
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
|
||||
dci = dci.ToDictionary();
|
||||
dci.Add(prop_name, rv);
|
||||
/// It is possible for add ops to be overwritten.
|
||||
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
|
||||
_descriptor_case_indices = dci;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
>
|
||||
<!--Control Base Colors-->
|
||||
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
|
||||
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
|
||||
|
||||
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
|
||||
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
|
||||
<SolidColorBrush x:Key="AccentColor1" Color="{x:Static mbts:BTSColors.accent1}" />
|
||||
@@ -49,48 +52,48 @@
|
||||
<SolidColorBrush x:Key="CheckBox.Pressed.BorderBrush" Color="{StaticResource CheckBox.Pressed.BorderColor}"/>
|
||||
|
||||
<!--ComboBox-->
|
||||
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
|
||||
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
|
||||
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
|
||||
|
||||
<!--ListBox-->
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
class Adorner<T> : Adorner where T : UIElement {
|
||||
public Adorner(T adorned_element) : base(adorned_element) =>
|
||||
AdornedElement = adorned_element;
|
||||
|
||||
public new T AdornedElement { get; }
|
||||
}
|
||||
}
|
||||
Loaded 100 of 373 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user