Compare commits
481
Commits
2.3.0_rc1
...
excel_algos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0abb668a0 | ||
|
|
6a0f45ed72 | ||
|
|
83adfb8361 | ||
|
|
9baa81b14f | ||
|
|
a43e649c06 | ||
|
|
6bf39b43f8 | ||
|
|
94e77ce2b8 | ||
|
|
851a34449a | ||
|
|
5609b8d80d | ||
|
|
dffb9018da | ||
|
|
d4a97f4b0c | ||
|
|
aec1d1ee6e | ||
|
|
fa4b4fc934 | ||
|
|
ee94289f22 | ||
|
|
45ba5746a6 | ||
|
|
a327ffe7e7 | ||
|
|
2feef62b65 | ||
|
|
4b1afd914e | ||
|
|
3048cabb56 | ||
|
|
3ad41adc48 | ||
|
|
e2d0d8bbae | ||
|
|
a6b803b4c3 | ||
|
|
1d68aa7fa8 | ||
|
|
d726018b15 | ||
|
|
437158e7f3 | ||
|
|
c670d0012b | ||
|
|
8751a8e496 | ||
|
|
6756ba44eb | ||
|
|
d8b8a5d710 | ||
|
|
5536f5f585 | ||
|
|
7367673ba4 | ||
|
|
901988ec2b | ||
|
|
b676d22f1d | ||
|
|
9b7e877a0b | ||
|
|
16d5341285 | ||
|
|
2a16ac77fb | ||
|
|
9eb76bdfbc | ||
|
|
49c584047a | ||
|
|
773ea38ffa | ||
|
|
81b7770151 | ||
|
|
e480b8951f | ||
|
|
781cf3ea6d | ||
|
|
a658ef7711 | ||
|
|
5ae750b2f1 | ||
|
|
23bbe82080 | ||
|
|
bd30c3f270 | ||
|
|
4d316034aa | ||
|
|
93f86ecb94 | ||
|
|
f360b79d27 | ||
|
|
6b582e224c | ||
|
|
7bf3c51380 | ||
|
|
1192182bc8 | ||
|
|
e3a366a386 | ||
|
|
419cac3e3d | ||
|
|
2e8dbd8478 | ||
|
|
938db6b616 | ||
|
|
d6bc9298cc | ||
|
|
f0003ff3c6 | ||
|
|
df5f8f6638 | ||
|
|
d7ea71de84 | ||
|
|
3ff26f6bb0 | ||
|
|
c8badf5689 | ||
|
|
58533bf16f | ||
|
|
2881e5ea32 | ||
|
|
6b3c3ded68 | ||
|
|
d65e00fade | ||
|
|
53e787ab7f | ||
|
|
68e5d07aef | ||
|
|
68345a181e | ||
|
|
69c121a19c | ||
|
|
780ce7416f | ||
|
|
9fe338a62a | ||
|
|
4fd8530bc7 | ||
|
|
ffd5915c52 | ||
|
|
f027af66c8 | ||
|
|
6b2708b160 | ||
|
|
534474621f | ||
|
|
554614ab72 | ||
|
|
2ede69a263 | ||
|
|
ffd7a9379f | ||
|
|
9def94aceb | ||
|
|
0af30b3cc0 | ||
|
|
f3f34269d3 | ||
|
|
595c2df65c | ||
|
|
a91c2d9bc6 | ||
|
|
40f6a1db08 | ||
|
|
481c1fd2de | ||
|
|
e80fc807e0 | ||
|
|
c8e054d079 | ||
|
|
9480e76d4b | ||
|
|
538411353a | ||
|
|
70f1552ec6 | ||
|
|
acf51e1178 | ||
|
|
da6998c391 | ||
|
|
2710cdd32b | ||
|
|
60786b105c | ||
|
|
f488248abf | ||
|
|
8165d1c9d7 | ||
|
|
f3114d1664 | ||
|
|
46dce3ac2b | ||
|
|
e24d8bac7d | ||
|
|
c434374f74 | ||
|
|
5ba1299ddf | ||
|
|
ea39014307 | ||
|
|
830b590e5b | ||
|
|
88494eb0fc | ||
|
|
d5427f6c24 | ||
|
|
106788c8c8 | ||
|
|
6968312a04 | ||
|
|
4c9dc9d83b | ||
|
|
ff193b13f0 | ||
|
|
fc9582850b | ||
|
|
1d275b942d | ||
|
|
c9c93b3cf0 | ||
|
|
f1ae9030d7 | ||
|
|
fc5439e92e | ||
|
|
c40eef7b03 | ||
|
|
2bafc12502 | ||
|
|
908b50a92a | ||
|
|
eb7aa17f2e | ||
|
|
9e5d43f44a | ||
|
|
f92722c849 | ||
|
|
389359dbfc | ||
|
|
390208327b | ||
|
|
ebedd17129 | ||
|
|
0f8ed5dacb | ||
|
|
5c1965f534 | ||
|
|
8553229119 | ||
|
|
96b63ff713 | ||
|
|
4903c12533 | ||
|
|
88f23530c6 | ||
|
|
14f4452599 | ||
|
|
22002c43cf | ||
|
|
c9d371fb77 | ||
|
|
b173f80703 | ||
|
|
5b6edc455f | ||
|
|
7551e23877 | ||
|
|
22c92c3e17 | ||
|
|
613823e7c5 | ||
|
|
0b7d18301d | ||
|
|
f0a9a2415d | ||
|
|
708e63e36e | ||
|
|
9fac15b473 | ||
|
|
76ff36e514 | ||
|
|
740c14c261 | ||
|
|
ceafab93ab | ||
|
|
53990297e8 | ||
|
|
9570be8bfa | ||
|
|
a8f86c38e4 | ||
|
|
46cb249016 | ||
|
|
6b799bf93b | ||
|
|
0b15570d00 | ||
|
|
33a71f5f47 | ||
|
|
2940601f4b | ||
|
|
1f7939f49a | ||
|
|
48e273a607 | ||
|
|
e35634020d | ||
|
|
1ede053f59 | ||
|
|
93824786c7 | ||
|
|
25b8c0a728 | ||
|
|
0e4fe1b47e | ||
|
|
cd744963ff | ||
|
|
fb9d877938 | ||
|
|
087b001510 | ||
|
|
02a7c0ddca | ||
|
|
6afa1d581d | ||
|
|
5e04abe573 | ||
|
|
3422a44c87 | ||
|
|
d5fdcc6796 | ||
|
|
f12217d76c | ||
|
|
d85a914046 | ||
|
|
4fdc890cb5 | ||
|
|
3ed0bd9052 | ||
|
|
dbdc915ff2 | ||
|
|
10476c6662 | ||
|
|
5fbc1397b5 | ||
|
|
74a91a7d2e | ||
|
|
72a6689cd4 | ||
|
|
05e09cb977 | ||
|
|
38bd2ef321 | ||
|
|
dab4f5acbb | ||
|
|
a20c53b23d | ||
|
|
8206f457b5 | ||
|
|
d4e6d6293d | ||
|
|
d1a2197ba4 | ||
|
|
369fb55e90 | ||
|
|
37dacff74d | ||
|
|
fb08533488 | ||
|
|
d297b703c7 | ||
|
|
59c00203a9 | ||
|
|
33f533af9b | ||
|
|
085d6402f4 | ||
|
|
f4395ef833 | ||
|
|
e7a9412124 | ||
|
|
da4ee38abd | ||
|
|
706e5bc73b | ||
|
|
af39fac368 | ||
|
|
4f997505f3 | ||
|
|
fcbb66361d | ||
|
|
fa16321691 | ||
|
|
4ecbfe5f5b | ||
|
|
ceb3542e15 | ||
|
|
32e55d07ee | ||
|
|
e079acf91f | ||
|
|
fecedb203a | ||
|
|
6ac05a47e1 | ||
|
|
36ed80dc87 | ||
|
|
6cf395199e | ||
|
|
1707c3268f | ||
|
|
f5bbc4245f | ||
|
|
7e1463683f | ||
|
|
96fc54ff0f | ||
|
|
25b69e4f7d | ||
|
|
d871ffca6d | ||
|
|
213567bcdb | ||
|
|
b7e5c3a374 | ||
|
|
ae4a8bbf46 | ||
|
|
850386c1d4 | ||
|
|
2d4e94a636 | ||
|
|
9b73ad2486 | ||
|
|
e447096733 | ||
|
|
cd68b95a3e | ||
|
|
205eebb7da | ||
|
|
ef4dc1fc65 | ||
|
|
ebad5b389f | ||
|
|
160c49514a | ||
|
|
64430e4368 | ||
|
|
4f7fcc5634 | ||
|
|
500e5b5fe4 | ||
|
|
9e7da44cce | ||
|
|
7f2988ec57 | ||
|
|
08c2762500 | ||
|
|
509a33dc53 | ||
|
|
4e6e97d90b | ||
|
|
9d924fa4ba | ||
|
|
092e63e300 | ||
|
|
4654cd4739 | ||
|
|
f2f06d76de | ||
|
|
6bc293a58e | ||
|
|
fdb462eda4 | ||
|
|
cc579034b2 | ||
|
|
4b76e91757 | ||
|
|
53ad85cce0 | ||
|
|
8d34279ab0 | ||
|
|
7973b927a2 | ||
|
|
6997aed9bd | ||
|
|
b5d7d98098 | ||
|
|
8f22a326ab | ||
|
|
b2c3488c5e | ||
|
|
23f10eac70 | ||
|
|
cdabf3b023 | ||
|
|
8248bd7db8 | ||
|
|
cb31269a70 | ||
|
|
5b75b8718a | ||
|
|
5a256d4634 | ||
|
|
db2de3e8c4 | ||
|
|
b9dcad9549 | ||
|
|
ce104479e1 | ||
|
|
d3ae9bc891 | ||
|
|
ac90a21314 | ||
|
|
2d56eedf3e | ||
|
|
049381908c | ||
|
|
1104f4db1d | ||
|
|
0067e52155 | ||
|
|
8ed6d297a9 | ||
|
|
474401cddc | ||
|
|
a06df5099a | ||
|
|
8ddd5bdbb5 | ||
|
|
5cbe00beab | ||
|
|
3ebcf9a270 | ||
|
|
b8a9116ea6 | ||
|
|
40cd826260 | ||
|
|
8bd63f11b1 | ||
|
|
2198a8d6ed | ||
|
|
8023dc7aa8 | ||
|
|
0dcdbd540e | ||
|
|
226fcc279b | ||
|
|
a6ad70fc05 | ||
|
|
e2b0da8103 | ||
|
|
aea233d537 | ||
|
|
5756c090f3 | ||
|
|
5ab0acb0ef | ||
|
|
3950e5b634 | ||
|
|
7ce8f1ed97 | ||
|
|
5d53492ab5 | ||
|
|
90fd5bd382 | ||
|
|
beff060a4f | ||
|
|
bc3418734f | ||
|
|
ab255c8777 | ||
|
|
cc2bde1064 | ||
|
|
94d66a8e99 | ||
|
|
92f08b89cc | ||
|
|
02769479ea | ||
|
|
4ada322c56 | ||
|
|
7de7f6e496 | ||
|
|
0700e9e80d | ||
|
|
ccca85d1c5 | ||
|
|
60e933fca9 | ||
|
|
e2aa6034ab | ||
|
|
8b4c64e8f4 | ||
|
|
b5ea399e7e | ||
|
|
62baa6aa31 | ||
|
|
87433aa7c3 | ||
|
|
3252cab4bb | ||
|
|
fbe9b570a3 | ||
|
|
1bcb209961 | ||
|
|
b0726ad3ea | ||
|
|
e0ba27a0a9 | ||
|
|
345e45c5bf | ||
|
|
490add84d9 | ||
|
|
94fc2c4248 | ||
|
|
8a7e2db4ac | ||
|
|
71c576bbc0 | ||
|
|
2e4dff4d91 | ||
|
|
73336df814 | ||
|
|
633edb43eb | ||
|
|
f4add8d464 | ||
|
|
894335d019 | ||
|
|
0fc977f5d3 | ||
|
|
3d70d35b99 | ||
|
|
66307edf91 | ||
|
|
e3e525819e | ||
|
|
ece25dd946 | ||
|
|
24f824fd81 | ||
|
|
7d992c84ab | ||
|
|
1cce474485 | ||
|
|
7186c0349b | ||
|
|
3d829da004 | ||
|
|
f20fd2136e | ||
|
|
30ce85b70f | ||
|
|
fc7a04a805 | ||
|
|
6d96c0e2a0 | ||
|
|
ae913962b3 | ||
|
|
b52f6fdc24 | ||
|
|
29d14404e6 | ||
|
|
d13f1b04ed | ||
|
|
8d234d9b8b | ||
|
|
d261d6fc31 | ||
|
|
343bf25aaf | ||
|
|
0f93757506 | ||
|
|
60b7966f2f | ||
|
|
6cad454f9e | ||
|
|
b3f6649e7f | ||
|
|
1059351334 | ||
|
|
3263cc840a | ||
|
|
8d0e93a311 | ||
|
|
179ee000a3 | ||
|
|
43129427f6 | ||
|
|
f49d73640e | ||
|
|
7afe1e103d | ||
|
|
c1b701ff2b | ||
|
|
06ca15ccac | ||
|
|
027efa9d2a | ||
|
|
026695d0d1 | ||
|
|
50775b3925 | ||
|
|
c695b43647 | ||
|
|
56ace9514a | ||
|
|
7c6a26ebd9 | ||
|
|
c6c1659953 | ||
|
|
dc696e9468 | ||
|
|
a6189d910c | ||
|
|
049cc69a4f | ||
|
|
b2766c283d | ||
|
|
0b14b048e4 | ||
|
|
ffc03a4055 | ||
|
|
992eb7b104 | ||
|
|
314eca0e89 | ||
|
|
626ec79af4 | ||
|
|
953272b231 | ||
|
|
4c185093bb | ||
|
|
6b5b4ea2b1 | ||
|
|
6f7c6db918 | ||
|
|
a1ead3d1c1 | ||
|
|
1fc35ee3cd | ||
|
|
af576bbee7 | ||
|
|
7b9ed4326a | ||
|
|
615e585c82 | ||
|
|
a014dcc984 | ||
|
|
42645f1246 | ||
|
|
1c5507a3f4 | ||
|
|
a92dd5a841 | ||
|
|
91a6c946f8 | ||
|
|
cfbd7855a5 | ||
|
|
00e9862844 | ||
|
|
4d413fb92c | ||
|
|
6e07a0c109 | ||
|
|
e936b80dc6 | ||
|
|
4046f82e06 | ||
|
|
699a6526f5 | ||
|
|
12a4c67e4d | ||
|
|
e9562b55f3 | ||
|
|
43735e1dab | ||
|
|
930723dd0e | ||
|
|
3e4ee9d6b6 | ||
|
|
c771aea311 | ||
|
|
7b35467d59 | ||
|
|
192354946d | ||
|
|
1eacb7f0f0 | ||
|
|
d0695fd5d7 | ||
|
|
ad8377d13f | ||
|
|
60ab67213f | ||
|
|
4055a06601 | ||
|
|
883582630d | ||
|
|
e7728203cf | ||
|
|
dd8586133f | ||
|
|
e47af3d7bd | ||
|
|
f4aa5cfa8c | ||
|
|
bc3f2db0f9 | ||
|
|
ed08cb11d7 | ||
|
|
719d460372 | ||
|
|
7b0549c4cb | ||
|
|
2ee0568159 | ||
|
|
df907fcb96 | ||
|
|
b945f9f090 | ||
|
|
32eeeee94f | ||
|
|
c3c427742d | ||
|
|
132bee0d27 | ||
|
|
83c432d8e2 | ||
|
|
6a4930767b | ||
|
|
685e69adcc | ||
|
|
2b7f5572db | ||
|
|
fe2b167fd5 | ||
|
|
04fbcc49b1 | ||
|
|
32229e3236 | ||
|
|
e9f61dc3c0 | ||
|
|
f13949ed0f | ||
|
|
f329470165 | ||
|
|
f2b7e692f8 | ||
|
|
2cff906c5f | ||
|
|
f711715350 | ||
|
|
e76a72e3e6 | ||
|
|
d7d319f1e3 | ||
|
|
cddadcb278 | ||
|
|
419833d398 | ||
|
|
0f88144fd6 | ||
|
|
abe35bddf0 | ||
|
|
cd262cd66c | ||
|
|
0133cc8aa5 | ||
|
|
9883553fb5 | ||
|
|
ff461d42b8 | ||
|
|
be5d037ca9 | ||
|
|
21f5fd919d | ||
|
|
159e8fba23 | ||
|
|
d1b2dbd688 | ||
|
|
bebe0c2478 | ||
|
|
61126fb3d5 | ||
|
|
3254775eb9 | ||
|
|
b0886f6b3d | ||
|
|
d4c2eb2bbc | ||
|
|
7fd8eeb1ff | ||
|
|
81bbafd3a1 | ||
|
|
dfe008922b | ||
|
|
d3de1c2c41 | ||
|
|
7428331504 | ||
|
|
5b818bf276 | ||
|
|
d8559276d0 | ||
|
|
3d11aba8ef | ||
|
|
c34d262d1a | ||
|
|
717c90867a | ||
|
|
90178908ba | ||
|
|
01956e92b6 | ||
|
|
b1e19294e6 | ||
|
|
1567ca475d | ||
|
|
938a572a54 | ||
|
|
e8bc07360d | ||
|
|
91989e6baf | ||
|
|
81ae503aa2 | ||
|
|
0a33fb37b9 | ||
|
|
729986d53d | ||
|
|
e19bb7c621 | ||
|
|
ea232fbaf1 | ||
|
|
6fc3c0b601 | ||
|
|
8aee612bfa | ||
|
|
5f125790cf | ||
|
|
117e107116 | ||
|
|
c8c6aea188 | ||
|
|
5c22b43eb5 | ||
|
|
e1f590127e | ||
|
|
3a516df593 | ||
|
|
91bc135c61 | ||
|
|
5135a21dc2 |
No files matched your search
+36
-34
@@ -1,34 +1,36 @@
|
||||
nohup.out
|
||||
build/
|
||||
cmake-build-debug/
|
||||
.idea/
|
||||
.dir-locals.el
|
||||
*.pyc
|
||||
.vscode/*
|
||||
!.vscode/*.json
|
||||
.vs
|
||||
obj/
|
||||
*.csproj.user
|
||||
*.vspx
|
||||
*.psess
|
||||
msbuild.log
|
||||
*.orig
|
||||
vcxproj.user
|
||||
src/client/spark/Properties/launchSettings.json
|
||||
compile_commands.json
|
||||
.cquery_cached_index/
|
||||
src/client/sparkcmd/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/logs/
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
.fake
|
||||
*.fsx.lock
|
||||
*.code-workspace
|
||||
src/lib/orderman/perf_analysis/logs/latency_data/
|
||||
trace_data/
|
||||
/src/lib/orderman/perf_analysis/last_pretty_log.csv
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
/doc/user/public/*
|
||||
nohup.out
|
||||
build/
|
||||
cmake-build-debug/
|
||||
.idea/
|
||||
.dir-locals.el
|
||||
*.pyc
|
||||
.vscode/*
|
||||
!.vscode/*.json
|
||||
.vs
|
||||
obj/
|
||||
*.csproj.user
|
||||
*.vspx
|
||||
*.psess
|
||||
msbuild.log
|
||||
*.orig
|
||||
vcxproj.user
|
||||
src/client/spark/Properties/launchSettings.json
|
||||
compile_commands.json
|
||||
.cquery_cached_index/
|
||||
src/client/sparkcmd/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/logs/
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
.fake
|
||||
*.fsx.lock
|
||||
*.code-workspace
|
||||
src/lib/orderman/perf_analysis/logs/latency_data/
|
||||
trace_data/
|
||||
/src/lib/orderman/perf_analysis/last_pretty_log.csv
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
src/web/ui/.ionide/symbolCache.db
|
||||
Vendored
+3
-2
@@ -44,6 +44,7 @@
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/build/ninja_debug/sdk/examples" : "${workspaceRoot}/src/sdk/examples",
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
@@ -104,7 +105,7 @@
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -249,7 +250,7 @@
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"SingleWriterConcurrentVector"
|
||||
"[algo]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
|
||||
@@ -59,6 +59,7 @@ else ()
|
||||
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/
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/excel_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+13
-4
@@ -1,11 +1,11 @@
|
||||
+++
|
||||
title = "BTS Basic Algos"
|
||||
weight = 10
|
||||
title = "Basic Algos"
|
||||
weight = 1
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
aliases = ["./bts_spark/Algos/"]
|
||||
+++
|
||||
|
||||
|
||||
## Lean
|
||||
|
||||
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
|
||||
@@ -43,4 +43,13 @@ An OCO order creates a relationship between a child order and a 'stop' or 'other
|
||||
Composing this order with Cover simulates scalping functionality
|
||||
|
||||
##### Parameters
|
||||
* Stop Ticks - ticks below or above main order to place stop / other order
|
||||
* Stop Ticks - ticks below or above main order to place stop / other order
|
||||
|
||||
## Splitter
|
||||
The splitter is useful for pro-rata markets where you want to vary your exposure independent of the order quantity that you want to fill. The splitter will complete when the number of fills reaches or exceeds the order quantity but it will put exposed_qty total size on the market. Splitter favors order size over queue position.
|
||||
|
||||
If exposed_qty is set to 0 the splitter will continue to exist in the Order Book but will cancel all of its child orders. This makes it a useful building block for custom algos that need to implement a 'paused' state.
|
||||
|
||||
##### Parameters
|
||||
* exposed_qty - The total quantity to expose on the market.
|
||||
* max_order_qty - The maximum size of a single order.
|
||||
File renamed without changes.
@@ -4,6 +4,82 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.7.2
|
||||
### User Visible Changes
|
||||
- Changed the login dialog to avoid leaking workspace names from other users.
|
||||
- Added new Splitter algo. From the manual: The splitter is useful
|
||||
for pro-rata markets where you want to vary your exposure
|
||||
independent of the order quantity that you want to fill. The
|
||||
splitter will complete when the number of fills reaches or exceeds
|
||||
the order quantity but it will put exposed_qty total size on the
|
||||
market. Splitter favors order size over queue position.
|
||||
### Internal Changes
|
||||
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
|
||||
|
||||
## 2.7.1
|
||||
### User Visible Changes
|
||||
- Fixed some issues with the Cover algo, where in some circumstances
|
||||
it would erroneously cancel launched hedges on completion.
|
||||
|
||||
## 2.7.0
|
||||
### User Visible Changes
|
||||
- Allowed algos to process "in-flight" fills while shutting down
|
||||
- Changed the cover algo to continue to hedge while shutting down
|
||||
### API Changes
|
||||
- Changed the API so that algos do not fully terminate until all
|
||||
their children are terminated. Algos that are in the process of
|
||||
shutting down in response to a complete or cancel call will still
|
||||
receive child updates, which they can use to calculate and report
|
||||
an accurate fill count (during this shutdown stage algos cannot
|
||||
create, cancel or update children). A new IAlgoServices method,
|
||||
is_shutting_down(), can be used to determine if an algo is
|
||||
shutting down.
|
||||
|
||||
## 2.6.2
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
|
||||
- Fixed a rare race condition that could lead to erroneous limit enforcement
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
|
||||
## 2.6.1
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
|
||||
|
||||
## 2.6.0
|
||||
### User Visible Changes
|
||||
- In the ladder made last trade more distinctive when overlayed on the volumn column
|
||||
- In the ladder, corrected display of volume tool tip
|
||||
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
|
||||
- Added an option for the Top Algo to delete itself if within N ticks from top of book
|
||||
- Address a rare issue that could lead to an error report on UI exit
|
||||
- Added margin risk limits
|
||||
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
|
||||
- Avoided switching the current account and order type on open ladders when limits are changed
|
||||
### Internal Changes
|
||||
- Ensure that admin logins are always allowed to login, regardless of keycode status.
|
||||
|
||||
## 2.5.0
|
||||
### User Visible Changes
|
||||
- Added loss limit risk check
|
||||
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
|
||||
- Allowed the mini order book defaults for column widths to be changed from the mini order book
|
||||
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
|
||||
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
|
||||
## 2.4.0
|
||||
### User Visible Settings
|
||||
- In the ladder, changed the last trade column to accumulate successive trades at the same price
|
||||
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
|
||||
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
|
||||
- Added ladder settings for the mini order book
|
||||
- Added a safety check to guard against unintentional market crosses
|
||||
- Improved the Queue Holder mode in the spreader:
|
||||
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
|
||||
- Improved spreader performance when in Queue Holder mode
|
||||
### Internal changes
|
||||
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
|
||||
- Addressed a potential concurrency issues with service initialization.
|
||||
|
||||
|
||||
## 2.3.0
|
||||
### User Visible Changes
|
||||
- Introduced multiple settings profiles for the ladder
|
||||
@@ -148,7 +224,7 @@ insert_anchor_links = "right"
|
||||
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
|
||||
## 1.5.7
|
||||
### User Visible Changes
|
||||
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
|
||||
- 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
|
||||
@@ -271,7 +347,7 @@ insert_anchor_links = "right"
|
||||
- The zoom slider can be enabled from Ladder Form -> Settings button -> Left Pane tab -> check Zoom Slider checkbox
|
||||
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
|
||||
- Zoom In = CTRL + Mouse wheel up
|
||||
- Zoom Out = CTRL + Mouse wheel down
|
||||
- Zoom Out = CTRL + Mouse wheel down␍
|
||||
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
|
||||
### Exchange simulator
|
||||
- Improved the exchange simulator to track real market data more closely
|
||||
@@ -376,7 +452,8 @@ insert_anchor_links = "right"
|
||||
- This is configurable from a ladder's settings as well
|
||||
### Customized settings in all windows
|
||||
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows␍
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
|
||||
|
||||
|
||||
## 1.3.1 Through 1.3.4
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using bts.api.core;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
readonly string _user_name;
|
||||
public string user_name => _user_name;
|
||||
public readonly UserTuple user;
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
@@ -24,14 +24,14 @@ namespace mbts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import("username")]string user_name) {
|
||||
_user_name = user_name;
|
||||
AdminModel(AdminService service, [Import]UserTuple user) {
|
||||
this.user = user;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
where bts_user.Name == user_name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
|
||||
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
|
||||
select bts_user;
|
||||
|
||||
IEnumerable<BTSAccountImmutable> active_accounts =
|
||||
@@ -62,14 +62,6 @@ namespace mbts.spark {
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
|
||||
|
||||
public UserTuple? get_current_user() {
|
||||
foreach (var kv in _user_map) {
|
||||
// just for good measure, there should never be > 1 element in _user_map, and it should always match _user_name
|
||||
if (kv.Value == _user_name)
|
||||
return new UserTuple(kv.Key, kv.Value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public AccountTuple? get_default_account() {
|
||||
foreach (var kv in _account_map) {
|
||||
return new AccountTuple(kv.Key, kv.Value);
|
||||
|
||||
@@ -3,6 +3,7 @@ using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -22,7 +23,7 @@ namespace bts.ui {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AboutDaemon(IBus bus, [Import("username")]string username) {
|
||||
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
|
||||
_user = username;
|
||||
_bus = bus;
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts.spark
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace bts.spark
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
@@ -82,7 +83,6 @@ namespace bts.spark
|
||||
}
|
||||
}
|
||||
|
||||
//public static async Task<Result<T>>
|
||||
public static async Task<(T value, bool success)> safe_execute<T>(this ErrorReporter error_reporter, Func<Task<Result<T>>> execute, Func<T, bool> get_success, Func<T, string> get_error_msg, string desc) {
|
||||
try {
|
||||
var result = await execute();
|
||||
@@ -101,5 +101,31 @@ namespace bts.spark
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
public static async Task<TResult> safe_execute<T, TResult>(
|
||||
this ErrorReporter error_reporter,
|
||||
Func<Task<Result<T>>> execute,
|
||||
Func<T, bool> get_success,
|
||||
Func<T, string> get_error_msg,
|
||||
Func<T, bool, TResult> result_selector,
|
||||
string desc) {
|
||||
//
|
||||
try {
|
||||
var result = await execute();
|
||||
if (!result.success) {
|
||||
_report_error(result.error_message);
|
||||
} else if (!get_success(result.value)) {
|
||||
_report_error(get_error_msg(result.value));
|
||||
} else {
|
||||
return result_selector(result.value, true);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
_report_error(ex.ToString());
|
||||
}
|
||||
return result_selector(default, false);
|
||||
//
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
using bts.md;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
@@ -27,8 +28,8 @@ namespace bts {
|
||||
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) :
|
||||
base(get_naggle(), MDSourceOptions.DeferInit) {
|
||||
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
|
||||
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
|
||||
_bus = bus;
|
||||
_token = token;
|
||||
_ss = ss;
|
||||
|
||||
@@ -69,11 +69,11 @@ namespace bts {
|
||||
cpv.Width = double.NaN;
|
||||
cpv.Height = double.NaN;
|
||||
|
||||
vm.ok_action = () => {
|
||||
vm.ok_action += () => {
|
||||
window.Close();
|
||||
message.complete(select_result(vm)).execute(_bus);
|
||||
};
|
||||
vm.cancel_action = () => window.Close();
|
||||
vm.cancel_action += () => window.Close();
|
||||
window.move_to_mouse();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.metrics;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -23,7 +24,7 @@ namespace bts.perf {
|
||||
Ping _pinger = new Ping();
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_username = user;
|
||||
|
||||
@@ -5,6 +5,7 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
@@ -18,7 +19,7 @@ namespace bts.shell
|
||||
class MarketShellModule : IShellModule
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace mbts.spark {
|
||||
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
|
||||
|
||||
// securities_connectable should NOT be connected already.
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
@@ -163,7 +163,6 @@ namespace mbts.spark {
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
securities_connectable.Connect(),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -135,6 +136,7 @@ namespace mbts.spark {
|
||||
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
|
||||
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
// commands
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
@@ -151,12 +153,11 @@ namespace mbts.spark {
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -7,6 +7,7 @@ using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -17,7 +18,6 @@ using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
@@ -77,12 +77,11 @@ namespace mbts.spark {
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ShowQuantityTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
>
|
||||
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
|
||||
<DataTemplate>
|
||||
@@ -115,6 +116,16 @@
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
|
||||
<DataTemplate>
|
||||
<!-- Note: the motivation for this Template was to enforce a Minimum via the TickUpDownUInt -->
|
||||
<!-- As of 2019/06/17, we believe such a Minimum/Maximum is not actually getting enforced and -->
|
||||
<!-- furthermore is not the appropriate way to go about enforcing said value. It is near the end -->
|
||||
<!-- of the cycle though so that is why we are leaving this Template in here for now until we -->
|
||||
<!-- can come up with a better solution. -->
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}" />
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace mbts.ui.spark {
|
||||
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
|
||||
var rv = new ContentControl();
|
||||
rv.SetBinding(ContentControl.ContentProperty, new Binding());
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding() { Converter = cell_template_converter });
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -306,17 +306,23 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate MarketDataTypeTemplate { get; set; }
|
||||
public DataTemplate MarketDataCalculationTemplate { get; set; }
|
||||
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
|
||||
public DataTemplate ShowQuantityTemplate { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
Type t = (Type)value;
|
||||
if (t == typeof(double)) return DoubleTemplate;
|
||||
else if (t == typeof(bool)) return BooleanTemplate;
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
else return null;
|
||||
if (value is ICell cell) {
|
||||
if (cell.row is ShowQuantityQuantityModel) {
|
||||
return ShowQuantityTemplate;
|
||||
}
|
||||
Type t = cell.data_type;
|
||||
if (t == typeof(double)) return DoubleTemplate;
|
||||
else if (t == typeof(bool)) return BooleanTemplate;
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -14,12 +15,14 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly SyntheticSecurityService _service;
|
||||
readonly ErrorReporter _error;
|
||||
readonly uint _user_id;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
|
||||
_bus = bus;
|
||||
_service = service;
|
||||
_error = error;
|
||||
_user_id = user_id;
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
|
||||
@@ -39,29 +42,40 @@ namespace mbts.spark {
|
||||
return (result, success);
|
||||
});
|
||||
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
await lookup_strategy_async(security.Id);
|
||||
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
lookup_strategy_async(security.Id);
|
||||
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
|
||||
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
|
||||
if (result.success) {
|
||||
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
|
||||
if (metadata != null) {
|
||||
return metadata.Strategy;
|
||||
}
|
||||
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
|
||||
_error.safe_execute(
|
||||
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
|
||||
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
|
||||
mdc => $"did not find strategy for security id '{id}.'",
|
||||
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
|
||||
"lookup strategy");
|
||||
|
||||
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return null;
|
||||
if (synthetic.Synthetic.UserId != _user_id) {
|
||||
var msynth = synthetic.CloneToMutable();
|
||||
msynth.Synthetic.UserId = _user_id;
|
||||
synthetic = new SecurityImmutable(msynth);
|
||||
}
|
||||
return null;
|
||||
return _error.safe_execute(
|
||||
() => _service.create_synthetic_async(synthetic),
|
||||
r => r.Error == CreateResult.Types.Error.Ok,
|
||||
r => r.Error.ToString(),
|
||||
(r, success) => success ? r.Key : (uint?)null,
|
||||
"creating synthetic");
|
||||
}
|
||||
|
||||
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
|
||||
var result = await _service.create_synthetic_async(synthetic.Value);
|
||||
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
|
||||
}
|
||||
|
||||
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
|
||||
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
|
||||
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
|
||||
}
|
||||
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
_error.safe_execute(
|
||||
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
|
||||
r => r.Error == UpdateResult.Types.Error.Ok,
|
||||
r => r.Error.ToString(),
|
||||
(r, success) => success,
|
||||
"saving strategy");
|
||||
|
||||
public async Task<bool> verify_name_is_unique(string name) {
|
||||
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
|
||||
@@ -108,6 +108,9 @@ namespace bts.shell {
|
||||
NTicks = 2,
|
||||
PriceType = ConditionalQuoting.Types.PriceType.Leg
|
||||
};
|
||||
public static NullableQuantity ShowQuantity => new NullableQuantity {
|
||||
Quantity = 1
|
||||
};
|
||||
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
|
||||
#endregion
|
||||
|
||||
@@ -82,11 +82,10 @@ namespace mbts.spark {
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace mbts.spark {
|
||||
|
||||
public virtual Type data_type { get; } = typeof(T);
|
||||
|
||||
public virtual IRowModel row { get => _model; }
|
||||
|
||||
public T value {
|
||||
get => _model.get_value(_leg_tup);
|
||||
set {
|
||||
@@ -64,6 +66,8 @@ namespace mbts.spark {
|
||||
|
||||
public Type data_type { get; } = typeof(QuantityMeasure);
|
||||
|
||||
public virtual IRowModel row { get => _model; }
|
||||
|
||||
public double ratio {
|
||||
get => _get_qm().Ratio;
|
||||
set {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using bts.shell;
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class ShowQuantityEnabledModel : RowModelBase<bool> {
|
||||
readonly ShowQuantityQuantityModel _quantity;
|
||||
|
||||
public ShowQuantityEnabledModel(ShowQuantityQuantityModel quantity) {
|
||||
_quantity = quantity;
|
||||
}
|
||||
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity != null;
|
||||
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.QuoteParams.ShowQuantity == null) {
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity = SyntheticDataHelper.Defaults.ShowQuantity;
|
||||
_quantity.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity = null;
|
||||
}
|
||||
_quantity.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
internal class ShowQuantityQuantityModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
|
||||
public override string display_name => "Quantity";
|
||||
|
||||
public override string help_text => string.Empty;
|
||||
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup).Quantity = _value_from_store(leg_tup);
|
||||
|
||||
|
||||
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) is NullableQuantity show_quantity ? show_quantity.Quantity : _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
if (_get_value(leg_tup) is NullableQuantity show_quantity) {
|
||||
show_quantity.Quantity = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) != null;
|
||||
|
||||
NullableQuantity _get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ShowQuantity;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark {
|
||||
|
||||
internal interface ICell {
|
||||
event Action value_changed;
|
||||
IRowModel row { get; }
|
||||
Type data_type { get; }
|
||||
bool enabled { get; }
|
||||
}
|
||||
@@ -40,7 +41,7 @@ namespace mbts.spark {
|
||||
FontWeight font_weight { get; }
|
||||
}
|
||||
|
||||
abstract class RowModelBase<T> : ViewModelBase, IRowModel {
|
||||
internal abstract class RowModelBase<T> : ViewModelBase, IRowModel {
|
||||
readonly Dictionary<uint, ReplaySubject<bool>> _enabled_store = new Dictionary<uint, ReplaySubject<bool>>();
|
||||
readonly Dictionary<uint, Subject<T>> _override_value_store = new Dictionary<uint, Subject<T>>();
|
||||
|
||||
@@ -98,6 +99,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private static class Subsections {
|
||||
public static ParameterSection ShowQuantity =>
|
||||
new ParameterSection("Show Quantity", true,
|
||||
"The quantity shown when the order is placed.");
|
||||
|
||||
public static ParameterSection Throttle =>
|
||||
new ParameterSection("Throttle", true,
|
||||
"Reduces re-quoting updates by defining a region on either side " +
|
||||
|
||||
@@ -76,8 +76,8 @@ namespace mbts.spark {
|
||||
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
|
||||
}
|
||||
}));
|
||||
contract_picker_model.cancel_action = _close_new_leg_popup;
|
||||
contract_picker_model.ok_action = () => {
|
||||
contract_picker_model.cancel_action += _close_new_leg_popup;
|
||||
contract_picker_model.ok_action += () => {
|
||||
if (!leg_tuples.Any(l => l.leg.Security.Id == contract_picker_model.result.security.Id)) {
|
||||
leg_tuples.Add(new LegSpreaderParamsTuple(
|
||||
SyntheticDataHelper.Defaults.ContractLeg(contract_picker_model.result.security),
|
||||
@@ -134,7 +134,7 @@ namespace mbts.spark {
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
uint? id = await model.create_synthetic(_synthetic);
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
|
||||
@@ -177,6 +177,8 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
var show_quantity_quantity = new ShowQuantityQuantityModel();
|
||||
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
|
||||
|
||||
@@ -211,6 +213,8 @@ namespace mbts.spark {
|
||||
//new QuoteHiddenModel(),
|
||||
new QuoteQueueHolderModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new ShowQuantityEnabledModel(show_quantity_quantity),
|
||||
show_quantity_quantity,
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using bts.api;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using mbts;
|
||||
|
||||
namespace bts.shell {
|
||||
public class BrowserWindow : IShellWindow {
|
||||
@@ -75,12 +70,15 @@ namespace bts.shell {
|
||||
public string window_type => _window_type;
|
||||
|
||||
public IShellModule Owner {get; set; }
|
||||
|
||||
public ShellWindowUI GetContent() { return new ShellWindowUI(_browser); }
|
||||
|
||||
public void GetState(System.IO.Stream save_here) {
|
||||
_isw.save(new BrowserWindowState() { uri = uri.ToString() }, save_here);
|
||||
}
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
|
||||
public void Dispose() {
|
||||
if (_browser != null) {
|
||||
_browser.Dispose();
|
||||
@@ -102,5 +100,4 @@ namespace bts.shell {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ namespace mbts.spark {
|
||||
} else if (t.definition != null) {
|
||||
return t.definition.subscribe_is_definition_dirty();
|
||||
} else return Observable.Return(false);
|
||||
}).RefCount();
|
||||
});
|
||||
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
|
||||
_vm.save_template(t).ignore();
|
||||
@@ -116,7 +116,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_commands.Add(undo_spark_cmd);
|
||||
_resources.Add(can_undo_source.Subscribe(undo_cmd));
|
||||
_resources.Add(can_undo_source.Connect());
|
||||
// redo
|
||||
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
|
||||
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
|
||||
@@ -133,7 +132,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_commands.Add(redo_spark_cmd);
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
_resources.Add(can_redo_source.Connect());
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace mbts.spark {
|
||||
_persisted_key == other._persisted_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoID && Equals(obj);
|
||||
obj is AlgoID other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
|
||||
+12
-38
@@ -1,5 +1,4 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
@@ -110,38 +109,24 @@ namespace mbts.spark {
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
return await _create_definition(definition, user_id);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.CreateDefinition);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
|
||||
await _create_definition(definition, _admin_model.user.id);
|
||||
|
||||
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.CreateTemplate);
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.UpdateTemplate);
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -175,17 +160,6 @@ namespace mbts.spark {
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
bool _try_get_user(out uint id) {
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
id = user.Value.id;
|
||||
return true;
|
||||
} else {
|
||||
id = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// used for error handling/reporting
|
||||
interface IActionMetadata {
|
||||
string desc { get; }
|
||||
|
||||
+11
-24
@@ -1,14 +1,11 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -27,7 +24,12 @@ namespace mbts.spark {
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
@@ -39,15 +41,11 @@ namespace mbts.spark {
|
||||
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
} else {
|
||||
_report_no_user_id("save template");
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -105,16 +103,5 @@ namespace mbts.spark {
|
||||
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
bool _try_get_user(out uint id) {
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
id = user.Value.id;
|
||||
return true;
|
||||
} else {
|
||||
id = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace mbts.spark {
|
||||
_dispose = new System.Reactive.Disposables.CompositeDisposable(
|
||||
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
|
||||
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).RefCount().Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
Disposable.wrap_collection(algo_map.Values)) {
|
||||
() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
|
||||
@@ -51,6 +51,9 @@ namespace mbts.spark {
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
mbts.ui.spark.AlgoContentTemplateSelector.OrderKind IAlgoSelector.order_kind =>
|
||||
new mbts.ui.spark.AlgoContentTemplateSelector.OrderKind(string.Empty, string.Empty);
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace mbts.spark {
|
||||
_model = model;
|
||||
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).RefCount().OnDispatcher());
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
|
||||
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
|
||||
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
@@ -77,7 +77,7 @@ namespace mbts.spark {
|
||||
definitions_view.Refresh();
|
||||
}),
|
||||
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).RefCount().OnDispatcher()
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
|
||||
@@ -22,7 +22,6 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
@@ -129,8 +128,7 @@ namespace mbts.spark {
|
||||
};
|
||||
_rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
// 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) =>
|
||||
@@ -181,7 +179,7 @@ namespace mbts.spark {
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
|
||||
state.SelectManyInSeries(s => _get_observable(bus, model, s));
|
||||
|
||||
static IComparer<FillTuple> _get_column_comparer(Columns col) {
|
||||
|
||||
@@ -6,6 +6,7 @@ using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -67,12 +68,11 @@ namespace mbts.spark {
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.columns.Select( c => c.create())),
|
||||
this.observe_title()
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new ShellSettingsDialog { Title = $"{window_type_name} Grid Settings", DataContext = vm };
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
|
||||
@@ -9,6 +9,7 @@ using bts.proto;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using mbts.ui.settings;
|
||||
|
||||
namespace mbts.spark {
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
@@ -220,6 +221,23 @@ namespace mbts.spark {
|
||||
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
readonly struct LadderMiniOrderBookSettings : IEquatable<LadderMiniOrderBookSettings> {
|
||||
// this is the only member now, but more may be added later (that is why a GridSettings member wasn't added directly to LadderSettings).
|
||||
[ProtoMember(1)]
|
||||
public readonly GridSettings value;
|
||||
|
||||
public LadderMiniOrderBookSettings(GridSettings value) =>
|
||||
this.value = value;
|
||||
|
||||
public bool Equals(LadderMiniOrderBookSettings other) => value == null ? other.value == null : value.Equals(other.value);
|
||||
public override bool Equals(object obj) => obj is LadderMiniOrderBookSettings other && Equals(other);
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
|
||||
public static bool operator ==(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => a.Equals(b);
|
||||
public static bool operator !=(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
// 1 important reason at this time:
|
||||
// From the settings dialog, default settings will only be persisted when they are NOT EQUAL to the current default settings.
|
||||
@@ -228,6 +246,7 @@ namespace mbts.spark {
|
||||
class LadderSettings : IEquatable<LadderSettings> {
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
const bool _default_show_warn_on_cross = true;
|
||||
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
@@ -255,6 +274,7 @@ namespace mbts.spark {
|
||||
zoom_factor = _default_zoom_factor;
|
||||
use_defaults = true;
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2);
|
||||
show_warn_on_cross = _default_show_warn_on_cross;
|
||||
}
|
||||
|
||||
public static LadderSettings Default {
|
||||
@@ -319,6 +339,7 @@ namespace mbts.spark {
|
||||
zoom_slider_visible = false,
|
||||
zoom_factor = _default_zoom_factor,
|
||||
net_change = NetChangeTerms.None,
|
||||
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
|
||||
};
|
||||
rv.row_height = LadderRender.measure_row_height(rv);
|
||||
return rv;
|
||||
@@ -460,6 +481,47 @@ namespace mbts.spark {
|
||||
get => _piq_column ?? (_piq_column = LadderSettings.Default.piq_column);
|
||||
set => _piq_column = value;
|
||||
}
|
||||
[ProtoMember(54), DefaultValue(_default_show_warn_on_cross)]
|
||||
public bool show_warn_on_cross { get; set; }
|
||||
|
||||
[ProtoMember(55)]
|
||||
public LadderMiniOrderBookSettings mini_ob { get; set; }
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
// if there are new props, make sure they are not null, invalid, etc.
|
||||
public static LadderSettings sanitize(LadderSettings s) {
|
||||
if (s == null)
|
||||
return null;
|
||||
// these MarketBreakLines are new settings props, ensure they are not null
|
||||
if (s.bid_break_line == null)
|
||||
s.bid_break_line = Default.bid_break_line;
|
||||
if (s.ask_break_line == null)
|
||||
s.ask_break_line = Default.ask_break_line;
|
||||
if (s.mid_break_line == null)
|
||||
s.mid_break_line = Default.mid_break_line;
|
||||
if (s.session_low_line == null)
|
||||
s.session_low_line = Default.session_low_line;
|
||||
if (s.session_high_line == null)
|
||||
s.session_high_line = Default.session_high_line;
|
||||
if (s.session_range_fill == null)
|
||||
s.session_range_fill = Default.session_range_fill;
|
||||
if (s.volume_column == null)
|
||||
s.volume_column = Default.volume_column;
|
||||
if (s.size_display == null)
|
||||
s.size_display = MarketSizeDisplay.get_default();
|
||||
if (s.size_display.normal == null)
|
||||
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
|
||||
if (s.mini_ob.value == null)
|
||||
s.mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
if (s.mini_ob.value.sorting_criterion.Count == 0)
|
||||
s.mini_ob = new LadderMiniOrderBookSettings(s.mini_ob.value.with(sorting_criterion: bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general.sorting_criterion));
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
other != null &&
|
||||
@@ -511,7 +573,9 @@ namespace mbts.spark {
|
||||
zoom_factor == other.zoom_factor &&
|
||||
use_defaults == other.use_defaults &&
|
||||
favorite_algos == other.favorite_algos &&
|
||||
net_change == other.net_change;
|
||||
net_change == other.net_change &&
|
||||
show_warn_on_cross == other.show_warn_on_cross &&
|
||||
mini_ob == other.mini_ob;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as LadderSettings);
|
||||
|
||||
@@ -562,7 +626,8 @@ namespace mbts.spark {
|
||||
zoom_factor.GetHashCode(),
|
||||
use_defaults.GetHashCode(),
|
||||
favorite_algos.GetHashCode(),
|
||||
net_change.GetHashCode());
|
||||
net_change.GetHashCode(),
|
||||
show_warn_on_cross.GetHashCode());
|
||||
|
||||
public struct WindowSpecifics {
|
||||
public readonly double order_entry_panel_width, zoom_factor;
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
@@ -81,24 +82,45 @@ namespace bts.shell {
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
// on_shell_window_vm should behave like an event (do not set to null)
|
||||
Action<ShellWindowVM> on_shell_window_vm = _ => { };
|
||||
ShellWindowVM shell_window_vm = null;
|
||||
IShellWindow shell_window = null;
|
||||
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
// resources
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
var profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
|
||||
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, sanitize_ladder_settings(lp.value), lp.security_criteria))),
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, LadderSettings.sanitize(lp.value), lp.security_criteria))),
|
||||
(us, lps) => us.with(ladder_profiles: lps.Values.Select(lp => new LadderSettingsProfile(lp.id, lp.name, lp.value, lp.security_criteria)).ToArray()));
|
||||
state_settings?.sanitize();
|
||||
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
|
||||
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
|
||||
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
|
||||
bool initialized = false, needs_resync = false;
|
||||
LadderSettings last_value = fit_to_sec(state_settings);
|
||||
var settings_subject = new BehaviorSubject<LadderSettings>(last_value);
|
||||
LadderVM vm = null;
|
||||
var settings_subject = new Subject<LadderSettings>();
|
||||
var settings_source = Observable.Create<LadderSettings>(o => {
|
||||
ensure_synced();
|
||||
if (last_value != null)
|
||||
o.OnNext(last_value);
|
||||
return settings_subject.Subscribe(s => {
|
||||
if (s != null)
|
||||
o.OnNext(s);
|
||||
});
|
||||
});
|
||||
// vm
|
||||
var vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_subject.Where(s => s != null));
|
||||
vm = LadderVM.create(_bus, model, LadderSettings.sanitize(last_value ?? _settings_hub.current.ladder), settings_source);
|
||||
vm.settings_invalidated += on_settings_invalidated;
|
||||
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
|
||||
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
|
||||
vm.request_settings_dialog += open_settings_dialog;
|
||||
bool initialized = false;
|
||||
var settings_subscription = _settings_hub.subscribe_default_settings().Subscribe(on_user_settings);
|
||||
vm.OnDisposed += disposing => {
|
||||
Disposable.dispose(ref settings_subscription);
|
||||
Disposable.dispose(ref active_settings_dialog);
|
||||
Disposable.dispose(ref settings_subject);
|
||||
};
|
||||
@@ -111,8 +133,25 @@ namespace bts.shell {
|
||||
LadderSettings fit_to_sec(LadderSettings s) =>
|
||||
_fit_to_sec(s, md.security.ContractCase);
|
||||
|
||||
void on_settings_invalidated() =>
|
||||
needs_resync = true;
|
||||
|
||||
void ensure_synced() {
|
||||
if (!needs_resync)
|
||||
return;
|
||||
var curr = vm.get_save_state();
|
||||
if (settings_id == 0) {
|
||||
last_value = curr;
|
||||
} else if (!curr.Equals(last_value)) {
|
||||
settings_id = 0;
|
||||
last_value = curr;
|
||||
}
|
||||
needs_resync = false;
|
||||
}
|
||||
|
||||
void on_user_settings(UserSettings us) {
|
||||
if (initialized) {
|
||||
ensure_synced();
|
||||
if (settings_id == 0) {
|
||||
// not on a profile, don't respond to default settings changes
|
||||
return;
|
||||
@@ -168,10 +207,8 @@ namespace bts.shell {
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
var dialog = new ShellSettingsDialog {
|
||||
DataContext = dialog_vm,
|
||||
Title = $"Ladder Settings - '{vm.security}'"
|
||||
};
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
dialog_vm.SubscribeDisposed(title_source.OnDispatcher().Subscribe(t => dialog_vm.title = t));
|
||||
dialog.Resources[ShellSettingsDialog.SecurityMapKey] = _securities;
|
||||
dialog_vm.RequestClose += on_request_close;
|
||||
dialog.Closed += on_closed;
|
||||
@@ -221,35 +258,15 @@ namespace bts.shell {
|
||||
}
|
||||
});
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
|
||||
return ShellWindow.create(
|
||||
return shell_window = ShellWindow.create(
|
||||
module, md.security.ToString(), _window_type, view,
|
||||
(v,s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: thread);
|
||||
// if there are new props, make sure they are not null, invalid, etc.
|
||||
LadderSettings sanitize_ladder_settings(LadderSettings s) {
|
||||
// these MarketBreakLines are new settings props, ensure they are not null
|
||||
if (s.bid_break_line == null)
|
||||
s.bid_break_line = LadderSettings.Default.bid_break_line;
|
||||
if (s.ask_break_line == null)
|
||||
s.ask_break_line = LadderSettings.Default.ask_break_line;
|
||||
if (s.mid_break_line == null)
|
||||
s.mid_break_line = LadderSettings.Default.mid_break_line;
|
||||
if (s.session_low_line == null)
|
||||
s.session_low_line = LadderSettings.Default.session_low_line;
|
||||
if (s.session_high_line == null)
|
||||
s.session_high_line = LadderSettings.Default.session_high_line;
|
||||
if (s.session_range_fill == null)
|
||||
s.session_range_fill = LadderSettings.Default.session_range_fill;
|
||||
if (s.volume_column == null)
|
||||
s.volume_column = LadderSettings.Default.volume_column;
|
||||
if (s.size_display == null)
|
||||
s.size_display = MarketSizeDisplay.get_default();
|
||||
if (s.size_display.normal == null)
|
||||
s.size_display = s.size_display.with(MarketSizeDisplay.get_default().normal);
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: thread,
|
||||
vm => {
|
||||
shell_window_vm = vm;
|
||||
title_source.connect();
|
||||
});
|
||||
}
|
||||
|
||||
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
|
||||
@@ -270,7 +287,7 @@ namespace bts.shell {
|
||||
if (s == null) return null;
|
||||
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
|
||||
if (algo_key.mode == AlgoKey.Mode.Factory) {
|
||||
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type);
|
||||
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
|
||||
if (fitted_algo.HasValue) {
|
||||
// be sure to clone ('s' may be shared)
|
||||
s = SparkUtils.clone_proto(s);
|
||||
@@ -304,4 +321,4 @@ namespace bts.shell {
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark.utilities;
|
||||
using bts.shell.orderbook;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
@@ -293,7 +294,6 @@ namespace mbts.spark {
|
||||
price_filter.minimum.value = real_price;
|
||||
price_filter.maximum.value = real_price;
|
||||
|
||||
var obvm = new OrderBookVM(null, _owner.bus, null, _owner.model.order_gateway, null, true);
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_owner.security),
|
||||
@@ -301,44 +301,22 @@ namespace mbts.spark {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
order_types = AlgoID.exchange_algo_keys
|
||||
},
|
||||
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
|
||||
bts.shell.orderbook.OrderBookStartupData.default_mini_grid.with(_owner.current_settings.mini_ob.value),
|
||||
use_defaults: false
|
||||
);
|
||||
// NOTE:
|
||||
// we might need the ObservableAppend to InsertAt
|
||||
// if these EditXXX ever support settings.
|
||||
|
||||
// The end result after this group of lines will be
|
||||
// ----------------------------------------------
|
||||
// | Cancel | Update | Qty | Price | Commit | Attach | Detach | ...
|
||||
// ----------------------------------------------
|
||||
//
|
||||
var settings_man = new WindowSettingsManager<OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true);
|
||||
_owner.settings.Subscribe(s => settings_man.instance_settings = settings_man.instance_settings.with(s.mini_ob.value));
|
||||
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Price.ToString());
|
||||
var price_index = obvm.columns.indexOf(price_col);
|
||||
if (price_index > -1) {
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditablePrice.ToString());
|
||||
var existing_editprice_index = obvm.columns.indexOf(existing_editprice_col);
|
||||
if (existing_editprice_index == -1) {
|
||||
// insert the edit price column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
|
||||
}
|
||||
}
|
||||
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Size.ToString());
|
||||
var qty_index = obvm.columns.indexOf(qty_col);
|
||||
if (qty_index > -1) {
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditableQty.ToString());
|
||||
var existing_editqty_index = obvm.columns.indexOf(existing_editqty_col);
|
||||
if (existing_editqty_index == -1) {
|
||||
// insert the edit quantity column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
|
||||
}
|
||||
}
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
|
||||
obvm.columns.Insert(0, new OrderBookVM.UpdateColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CancelColumn(nameof(OrderBookRowVM.cancel_command)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
|
||||
|
||||
// the CheckColumn must be inserted at the front of the ObservableAppendCollection,
|
||||
// and not mixed with the other columns in said ObservableAppendCollection's underlying ObservableCollection (the settings dialog uses the underlying collection).
|
||||
// The user cannot adjust this column in the settings dialog, and it should always be the left-most column.
|
||||
obvm.columns.insert_front(new OrderBookVM.CheckColumn(nameof(OrderBookRowVM.is_selected)));
|
||||
obvm.load_from(state);
|
||||
return obvm;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -40,7 +41,7 @@ namespace mbts.spark {
|
||||
TimeInForce _default_time_in_force;
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear;
|
||||
bool _auto_clear, _show_warn_on_cross;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
@@ -102,6 +103,7 @@ namespace mbts.spark {
|
||||
};
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.execute;
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
|
||||
@@ -126,6 +128,8 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
void dispose_resources() {
|
||||
mini_ob.result_changed -= _update.execute;
|
||||
mini_ob.Dispose();
|
||||
favorite_algos_vm.items.CollectionChanged -= on_favorite_algo_items_CollectionChanged;
|
||||
favorite_algos_vm.Dispose();
|
||||
foreach (var c in _observable_components)
|
||||
@@ -157,6 +161,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
// price format
|
||||
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
|
||||
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
|
||||
@@ -173,6 +178,9 @@ namespace mbts.spark {
|
||||
public bool auto_clear { get => _auto_clear; set => Set(ref _auto_clear, value); }
|
||||
public double order_entry_panel_width { get => _order_entry_panel_width; set => Set(ref _order_entry_panel_width, value); }
|
||||
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
|
||||
// safety checks
|
||||
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
|
||||
|
||||
// favorites
|
||||
public FavoriteAlgosVM favorite_algos_vm { get; }
|
||||
|
||||
@@ -293,6 +301,7 @@ namespace mbts.spark {
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
mini_ob.load_from(settings.mini_ob.value);
|
||||
// columns
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
@@ -340,11 +349,25 @@ namespace mbts.spark {
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
price_format = settings.net_change;
|
||||
// safety checks
|
||||
show_warn_on_cross = settings.show_warn_on_cross;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderSettings get_result() {
|
||||
var clone = SparkUtils.clone_proto(_current);
|
||||
LadderSettings clone;
|
||||
if (_current == null) {
|
||||
clone = new LadderSettings {
|
||||
mini_ob = new LadderMiniOrderBookSettings(mini_ob.create())
|
||||
};
|
||||
} else {
|
||||
clone = SparkUtils.clone_proto(_current);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
|
||||
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.create());
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
clone.badge_style = badge_style;
|
||||
clone.alternate_order_size = alternate_order_size;
|
||||
clone.favorite_algos = favorite_algos_vm.create();
|
||||
@@ -399,6 +422,8 @@ namespace mbts.spark {
|
||||
clone.cancel_all_visible = _cancel_all_visibility.show;
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.net_change = price_format;
|
||||
// safety checks
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -459,7 +484,7 @@ namespace mbts.spark {
|
||||
_foreground = backing(nameof(foreground), Colors.Black);
|
||||
_background = backing(nameof(background), Colors.White);
|
||||
_font_display_name = backing(nameof(font_display_name),
|
||||
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
|
||||
() => TextUtils.display_name(font_family, font_weight, font_size), new[] { nameof(font_family), nameof(font_weight), nameof(font_size) });
|
||||
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
|
||||
toggle_show_details_cmd = new DelegateCommand(() => {
|
||||
show_details = !show_details;
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace mbts.spark {
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
LadderSettings _current_settings;
|
||||
|
||||
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
@@ -53,7 +52,7 @@ namespace mbts.spark {
|
||||
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_current_settings = init;
|
||||
current_settings = init;
|
||||
this.settings = settings;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
@@ -82,8 +81,8 @@ namespace mbts.spark {
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
/// first things first: assign our <see cref="_current_settings"/>
|
||||
(s => _current_settings = s),
|
||||
/// first things first: assign our <see cref="current_settings"/>
|
||||
(s => current_settings = s),
|
||||
// brush props
|
||||
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
|
||||
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
|
||||
@@ -236,14 +235,13 @@ namespace mbts.spark {
|
||||
}
|
||||
if ((Set(ref _position, total, nameof(position)) | Set(ref _day_position, day, nameof(day_position))) && (show_day_position || show_account_position))
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}),
|
||||
position_src.Connect());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public IBus bus => _bus;
|
||||
|
||||
public event Action on_tick, request_settings_dialog;
|
||||
public event Action on_tick, request_settings_dialog, settings_invalidated;
|
||||
|
||||
public MDConsumerHandle md => _model.md;
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
@@ -278,6 +276,7 @@ namespace mbts.spark {
|
||||
remove => _on_disposed_impl -= value;
|
||||
}
|
||||
public SecurityImmutable security => _model.security;
|
||||
public LadderSettings current_settings { get; private set; }
|
||||
public double high => _high.Value;
|
||||
public double low => _low.Value;
|
||||
public double change => _change.Value;
|
||||
@@ -339,6 +338,8 @@ namespace mbts.spark {
|
||||
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
|
||||
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
|
||||
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
@@ -359,7 +360,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public LadderSettings get_save_state() {
|
||||
var rv = _current_settings.with_window_specifics(
|
||||
var rv = current_settings.with_window_specifics(
|
||||
new LadderSettings.WindowSpecifics(
|
||||
left_panel_width,
|
||||
orders_column_width,
|
||||
@@ -393,18 +394,24 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void on_mini_orderbook_closed(bts.shell.orderbook.OrderBookStartupData ob_state) {
|
||||
current_settings = SparkUtils.clone_proto(current_settings);
|
||||
current_settings.mini_ob = new LadderMiniOrderBookSettings(ob_state.orderbook_grid.general);
|
||||
settings_invalidated?.Invoke();
|
||||
}
|
||||
|
||||
public void send_order((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm == null || args.button == MouseButton.Middle)
|
||||
return;
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && _current_settings.alternate_order_size == 0)) {
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
|
||||
return;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? _current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderVMContainer : ViewModelBase, IDisposable {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
LadderVM _value;
|
||||
SecurityMD? _security_md;
|
||||
HashSet<uint> _selected_order_tree = new HashSet<uint>();
|
||||
|
||||
public LadderVMContainer(IBus bus, OrdersModel model) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
}
|
||||
|
||||
public SecurityMD? security_md {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (_security_md != value) {
|
||||
_security_md = value;
|
||||
_reset_ladder_vm();
|
||||
RaisePropertyChanged(nameof(security_md), nameof(LadderVMContainer.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
public LadderVM value => _value;
|
||||
|
||||
public void Dispose() {
|
||||
if (_value != null) {
|
||||
_value.Dispose();
|
||||
_value = null;
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void set_orders(IEnumerable<uint> orders) {
|
||||
_selected_order_tree = new HashSet<uint>(orders);
|
||||
_reset_ladder_vm();
|
||||
}
|
||||
|
||||
void _reset_ladder_vm() {
|
||||
if (_value == null && !_security_md.HasValue)
|
||||
return;
|
||||
Disposable.replace(ref _value,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,8 @@ namespace mbts.spark {
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
double _old_last_trade_price = 0.0;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
bool _tooltips_are_hooked_up = false;
|
||||
ToolTip tooltip_view = new ToolTip();
|
||||
IDisposable _parameterized_order_popup;
|
||||
@@ -339,7 +340,7 @@ namespace mbts.spark {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
return;
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
show_mbo_popup(point).ignore();
|
||||
} else {
|
||||
@@ -347,6 +348,19 @@ namespace mbts.spark {
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (_vm != null) {
|
||||
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
|
||||
// if warning enabled && (buy above opposite inside market || ask below opposite inside market)
|
||||
// when 1-sided market, _get_top_price will return NaN, and so the price comparison is false
|
||||
// meaning show no warning == correct
|
||||
if (_vm.show_warn_on_cross_from_settings &&
|
||||
(size > 0) &&
|
||||
((bs == Side.Buy && price >= _get_top_price(_asks)) || (bs == Side.Sell && price <= _get_top_price(_bids)))) {
|
||||
string prompt = $"Are you sure you want to CROSS the market to {bs.ToString().ToUpper()} {size} @ {security.spec_or_default().format(price)} ?";
|
||||
if (!BTSMessageBox.Confirm(prompt, "Cross Market Alert", MessageBoxImage.Warning)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command is ICommand cmd && cmd.CanExecute(pbs)) {
|
||||
@@ -680,12 +694,14 @@ namespace mbts.spark {
|
||||
// At any rate, we need this part hiding the tooltip so that we don't leave random tooltips around
|
||||
// and we have to reset to old row so that when we go back to what could be our preivous row, the tooltip gets drawn again
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Volume is 0");
|
||||
_old_tooltip_row = int.MaxValue;
|
||||
_hide_tool_tip();
|
||||
//_old_tooltip_row = Int32.MaxValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
|
||||
_old_tooltip_row = int.MaxValue;
|
||||
_hide_tool_tip();
|
||||
}
|
||||
}
|
||||
@@ -848,21 +864,43 @@ namespace mbts.spark {
|
||||
void _refresh_volume_at_price() {
|
||||
if (!_settings.volume_column.general.show)
|
||||
return;
|
||||
|
||||
// if the last trade has a new size then we need to explicitly
|
||||
// mark the volume container as dirty because there is a chance
|
||||
// we have arrived here off a size change and not a volume change
|
||||
// or a random change, like applying new settings
|
||||
bool last_trade_size_changed = _old_last_trade.HasValue ? _last_trade.size != _old_last_trade.Value.size : true;
|
||||
if (last_trade_size_changed && _settings.volume_column.show_ltq) {
|
||||
_volume_container.mark_dirty();
|
||||
}
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
Log.assert_d(_price_list != null);
|
||||
var ltp = _last_trade.price;
|
||||
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
|
||||
string direction_arrow = string.Empty;
|
||||
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
|
||||
// do nothing if in here b/c direction_arrow already string.empty
|
||||
} else if (ltp > _old_last_trade_price) {
|
||||
direction_arrow = "▲";
|
||||
} else {
|
||||
direction_arrow = "▼";
|
||||
}
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
|
||||
_old_last_trade_price = ltp;
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
|
||||
_old_last_trade = _last_trade;
|
||||
}
|
||||
|
||||
string _get_last_arrow_string() {
|
||||
// if _old_last_trade doesn't have a value, this is the first time the ladder has been launched
|
||||
// and so the direction should be drawn without an arrow since there is no basis for comparison
|
||||
if (!_old_last_trade.HasValue) {
|
||||
_direction_arrow = string.Empty;
|
||||
// if the Lasts are different then we need to reformat the direction arrow in some fashion
|
||||
} else if (_old_last_trade != _last_trade) {
|
||||
var ltp = _last_trade.price;
|
||||
if (MathUtils.epsilon_equals(ltp, _old_last_trade.Value.price)) {
|
||||
// update on the same price, so size must be different
|
||||
_direction_arrow = string.Empty;
|
||||
} else if (ltp > _old_last_trade.Value.price) {
|
||||
_direction_arrow = "▲";
|
||||
} else {
|
||||
_direction_arrow = "▼";
|
||||
}
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_last_trade.size} ";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interactivity;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -156,7 +154,10 @@ namespace mbts.spark {
|
||||
popup.SetBinding(System.Windows.Controls.Primitives.Popup.IsOpenProperty,
|
||||
nameof(OrderBookVM.is_row_collection_populated)
|
||||
);
|
||||
popup.Closed += (sender, event_args) => (popup.DataContext as IDisposable)?.Dispose();
|
||||
popup.Closed += (sender, event_args) => {
|
||||
(_parent.DataContext as LadderVM)?.on_mini_orderbook_closed(obvm.create());
|
||||
(popup.DataContext as IDisposable)?.Dispose();
|
||||
};
|
||||
popup.MouseUp += (sender, event_args) => {
|
||||
if (event_args.ChangedButton == MouseButton.Middle) {
|
||||
popup.IsOpen = false;
|
||||
|
||||
@@ -196,8 +196,7 @@ namespace mbts.spark {
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
NativePriceSize last_trade,
|
||||
string direction_arrow
|
||||
string last_size_and_direction
|
||||
) {
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -216,16 +215,16 @@ namespace mbts.spark {
|
||||
Brush total_brush = null;
|
||||
if (settings.total.bars.result.HasValue) {
|
||||
total_brush = new SolidColorBrush(settings.total.bars.result.Value);
|
||||
} else if (components.Count == 0) {
|
||||
} else if (components.Count == 0 && !settings.show_ltq) {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
double y = row_height * r;
|
||||
var last_trade_here = r == last_index;
|
||||
|
||||
@@ -259,7 +258,8 @@ namespace mbts.spark {
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
numbers_text.Add($"{direction_arrow} {last_trade.size} ");
|
||||
numbers_text.Add(last_size_and_direction);
|
||||
text_brush = BTSCoreColors.white;
|
||||
}
|
||||
if (numbers_text.Count > 0) {
|
||||
string str = string.Join("/", numbers_text);
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
<sys:Double x:Key="LadderColumnsForegroundColumnWidth">81</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsWidthColumnWidth">50</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsFontColumnWidth">160</sys:Double>
|
||||
<sys:Double x:Key="LadderColumnsSampleColumnWidth">153</sys:Double>
|
||||
<!--Margins-->
|
||||
<Thickness x:Key="MarginLPush" Left="{StaticResource HorizontalPush}"/>
|
||||
<Thickness x:Key="MarginLPush2" Left="{StaticResource HorizontalPush2}"/>
|
||||
@@ -190,7 +189,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
@@ -226,7 +225,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
@@ -272,7 +271,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
@@ -329,7 +328,7 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsForegroundColumnWidth}}" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsWidthColumnWidth}}" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsFontColumnWidth}}" SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource LadderColumnsSampleColumnWidth}}" SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<Grid>
|
||||
@@ -449,7 +448,7 @@
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<TabControl Width="702" MinHeight="275">
|
||||
<TabControl>
|
||||
<TabControl.ItemContainerStyle>
|
||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
@@ -458,14 +457,14 @@
|
||||
</TabControl.ItemContainerStyle>
|
||||
<!-- Ladder Columns Tab -->
|
||||
<TabItem Header="Ladder Columns" VerticalAlignment="Stretch">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid Grid.IsSharedSizeScope="True" Margin="8,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="fonts_and_colors_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}" Margin="8,0">
|
||||
<Grid x:Name="fonts_and_colors_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
@@ -473,7 +472,7 @@
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition SharedSizeGroup="c5"/>
|
||||
<ColumnDefinition SharedSizeGroup="c6"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Background" VerticalAlignment="Center"/>
|
||||
@@ -482,8 +481,8 @@
|
||||
<TextBlock Grid.Column="5" HorizontalAlignment="Center" Margin="6,0" Text="Font" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="6" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" Margin="8,0" ItemTemplateSelector="{StaticResource LadderColumnTemplateSelector}"/>
|
||||
<WrapPanel Grid.Row="3" Margin="8,0" Width="{Binding ActualWidth, ElementName=fonts_and_colors_headers, Mode=OneWay}" VerticalAlignment="Top">
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding column_items}" ItemTemplateSelector="{StaticResource LadderColumnTemplateSelector}"/>
|
||||
<WrapPanel Grid.Row="3" Width="{Binding ActualWidth, ElementName=fonts_and_colors_headers, Mode=OneWay}" VerticalAlignment="Top">
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock" BasedOn="{StaticResource HintText}">
|
||||
@@ -725,7 +724,7 @@
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}" MaxHeight="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -926,6 +925,11 @@
|
||||
</ComboBox>
|
||||
<!--Auto Clear-->
|
||||
<CheckBox Grid.Row="10" Grid.ColumnSpan="3" HorizontalAlignment="Left" VerticalAlignment="Top" VerticalContentAlignment="Top" Margin="0,2,0,0" FlowDirection="RightToLeft" IsChecked="{Binding auto_clear}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="Path">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
Auto Clear
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
@@ -980,5 +984,29 @@
|
||||
-->
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Safety Checks" VerticalAlignment="Stretch">
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Content="Warn on Market Cross" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
<usp:GridSettingsView Height="790" Margin="6" DataContext="{Binding mini_ob}" GridSectionContent="{Binding}" GridSectionContentTemplate="{DynamicResource GridSectionCoreContentTemplate}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
||||
@@ -152,20 +152,21 @@ namespace mbts.spark {
|
||||
// When middle click over bid or ask columns:
|
||||
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
|
||||
// Parameterizable order: if qty is null/0, flash and No cursor
|
||||
var pos = e.GetPosition(_host);
|
||||
var column_type = _host._column_for_point(pos).column_type;
|
||||
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
|
||||
if (e.MiddleButton == MouseButtonState.Pressed &&
|
||||
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
|
||||
if (!selected_algo_can_expand) {
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
|
||||
_host._vm.size_calculator.flash_error();
|
||||
_host.Cursor = Cursors.No;
|
||||
if (_host._vm != null) {
|
||||
var pos = e.GetPosition(_host);
|
||||
var column_type = _host._column_for_point(pos).column_type;
|
||||
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
|
||||
if (e.MiddleButton == MouseButtonState.Pressed &&
|
||||
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
|
||||
if (!selected_algo_can_expand) {
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
|
||||
_host._vm.size_calculator.flash_error();
|
||||
_host.Cursor = Cursors.No;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_is_mouse_down = true;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
@@ -63,9 +63,10 @@
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
SelectedSecurity="{Binding security, UpdateSourceTrigger=Explicit}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
mc:Ignorable="d"
|
||||
MaxHeight="240"
|
||||
MinWidth="875"
|
||||
d:DesignHeight="600" d:DesignWidth="1080"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}">
|
||||
|
||||
@@ -1177,7 +1177,6 @@ namespace mbts.spark {
|
||||
public class CommitColumn : FrameworkElementColumnBase<Columns> {
|
||||
public CommitColumn() : base(Columns.Commit) {
|
||||
id = key.ToString();
|
||||
Header = "";
|
||||
Width = 20;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
@@ -38,6 +37,8 @@ namespace mbts.spark {
|
||||
_min_order_params_height = 300,
|
||||
_min_ladder_height = 100,
|
||||
_min_fills_height = 100;
|
||||
static readonly TimeSpan _selection_change_throttle = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
@@ -46,7 +47,8 @@ namespace mbts.spark {
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
readonly SelectedOrderParamsMon _selected_params_mon;
|
||||
readonly OrderParamsAggregator _selected_params_mon;
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _selected_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly RightPanelModel _right_panel = new RightPanelModel();
|
||||
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
|
||||
double
|
||||
@@ -68,7 +70,6 @@ namespace mbts.spark {
|
||||
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
|
||||
|
||||
public OrderBookVM(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
@@ -77,7 +78,8 @@ namespace mbts.spark {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
|
||||
ladder_vm = new LadderVMContainer(bus, model);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
@@ -145,8 +147,7 @@ namespace mbts.spark {
|
||||
s => fills_vm.value.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(fills_vm.columns.Select(c => c.create())),
|
||||
Observable.Return(title)
|
||||
s.with(fills_vm.columns.Select(c => c.create()))
|
||||
),
|
||||
show_save_as_defaults: false);
|
||||
// TODO: bind to main window's Title
|
||||
@@ -154,7 +155,7 @@ namespace mbts.spark {
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
grid_settings_manager = new OrderBookGridSettingsManager(owner_title, bus, notification_service, model, rows, columns);
|
||||
grid_settings_manager = new OrderBookGridSettingsManager(bus, notification_service, model, rows, core_columns);
|
||||
grid_settings_manager.algo_tree_display_mode_changed += _on_filter_changed;
|
||||
IDisposable attach_vm_hooks = null;
|
||||
begin_attaching += on_begin_attaching;
|
||||
@@ -177,6 +178,11 @@ namespace mbts.spark {
|
||||
if (ch.newval && fills_height < _min_fills_height)
|
||||
fills_height = _min_fills_height;
|
||||
}),
|
||||
_selected_security_mon.SubscribeThrottled(_selection_change_throttle).to_security_md(bus).OnDispatcher().Subscribe(smd => ladder_vm.security_md = smd),
|
||||
_subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
|
||||
var hierarchical_order_keys = new HashSet<uint>(selected_hierarchical_rows.Select(r => r.order_key));
|
||||
ladder_vm.set_orders(hierarchical_order_keys);
|
||||
}),
|
||||
_selected_params_mon.subscribe_all_active().Subscribe(on_all_selected_orders_active_changed),
|
||||
_selected_params_mon.subscribe_security_plurality().Subscribe(p => {
|
||||
security_plurality = p;
|
||||
@@ -218,7 +224,6 @@ namespace mbts.spark {
|
||||
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
|
||||
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
|
||||
order_mod_vm.algo_vm,
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_params_mon.on_security_md_map),
|
||||
attach_vm,
|
||||
_show_fills.observe_current(true)
|
||||
.Subscribe(is_visible => {
|
||||
@@ -326,8 +331,8 @@ namespace mbts.spark {
|
||||
|
||||
IDisposable connect_order_mod_vm() =>
|
||||
Disposable.Create(
|
||||
_selected_params_mon.subscribe_algos().Subscribe(order_mod_vm.algo_vm.load_many),
|
||||
order_mod_vm.subscribe_is_dirty().Subscribe(_ => {
|
||||
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
|
||||
order_mod_vm.subscribe_is_dirty().OnDispatcher().Subscribe(_ => {
|
||||
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
|
||||
revert_selected_orders_cmd.RaiseCanExecuteChanged();
|
||||
}),
|
||||
@@ -335,10 +340,10 @@ namespace mbts.spark {
|
||||
|
||||
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
|
||||
Disposable.Create(
|
||||
_selected_params_mon.subscribe_security_md().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_params_mon.subscribe_price().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().Subscribe(newval => vm.selected_account = newval));
|
||||
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
|
||||
}
|
||||
|
||||
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -440,6 +445,7 @@ namespace mbts.spark {
|
||||
request_selection?.Invoke((_row_collection, _all_rows_selected));
|
||||
}
|
||||
}
|
||||
public LadderVMContainer ladder_vm { get; }
|
||||
|
||||
public IObservable<bool> subscribe_show_fills() => _show_fills.observe_current(true);
|
||||
public IObservable<bool> subscribe_show_ladder() => _show_ladder.observe_current(true);
|
||||
@@ -447,11 +453,13 @@ namespace mbts.spark {
|
||||
|
||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
||||
try {
|
||||
using (_selected_params_mon.begin_udpate()) {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
if (e.RemovedItems != null) {
|
||||
foreach (var row_vm in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = false;
|
||||
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
}
|
||||
@@ -460,11 +468,12 @@ namespace mbts.spark {
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
var root = _get_root(row_vm.order_tuple);
|
||||
row_vm.is_selected = true;
|
||||
_selected_security_mon.add(row_vm.security);
|
||||
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
|
||||
row_vm.updated += on_updated;
|
||||
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
|
||||
}
|
||||
_selected_params_mon.on_order_selected(root);
|
||||
_selected_params_mon.Add(root);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
@@ -476,16 +485,26 @@ namespace mbts.spark {
|
||||
unsubscribe_updated_disposable.Dispose();
|
||||
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
|
||||
}
|
||||
_selected_params_mon.on_order_unselected(_get_root(row_vm.order_tuple));
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
return;
|
||||
}
|
||||
var new_root = _get_root(change.newval);
|
||||
if (!new_root.Equals(root)) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.on_order_updated((root, new_root));
|
||||
root = new_root;
|
||||
using (_selected_params_mon.defer_updates()) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.Remove(root);
|
||||
_selected_params_mon.Add(new_root);
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
if (change.oldval.security?.Id != change.newval.security?.Id) {
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_selected_security_mon.remove(change.oldval.security);
|
||||
_selected_security_mon.add(change.newval.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,21 +608,6 @@ namespace mbts.spark {
|
||||
|
||||
public void begin_attach_algos() => is_attaching_orders = true;
|
||||
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurity parent) =>
|
||||
_cached_orders.TryGetValue(row.parent_order_key, out parent);
|
||||
|
||||
OrderBookRowVM _get_root_row(OrderBookRowVM row) {
|
||||
while (row.parent_order_key != 0 && _row_collection.get_parent(row.parent_order_key) is OrderBookRowVM prow)
|
||||
row = prow;
|
||||
return row;
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
internal void detach_orders(OrderBookRowVM row) {
|
||||
if (row.is_selected) {
|
||||
_detach_orders_async(selected_rows).ignore();
|
||||
@@ -612,17 +616,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public OrderSecurity get_ordersecurityalgometa(OrderRecordImmutable order) {
|
||||
_cached_orders.TryGetValue(order.Key, out var osam);
|
||||
return osam;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
|
||||
|
||||
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
|
||||
|
||||
internal void show_order_modify(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
|
||||
|
||||
@@ -651,6 +648,12 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
void _on_filter_changed() {
|
||||
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
|
||||
if (!fd.Equals(_cached_filter_data)) {
|
||||
@@ -737,52 +740,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
|
||||
Observable.Create<ISet<uint>>(o => {
|
||||
bool waiting_for_mouse_up = false;
|
||||
var sub_container = new DisposableContainer();
|
||||
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
var selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
|
||||
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
if (waiting_for_mouse_up)
|
||||
return;
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (OrderBookRowVM row in e.NewItems)
|
||||
selected_keys = selected_keys.Add(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (OrderBookRowVM row in e.OldItems)
|
||||
selected_keys = selected_keys.Remove(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
selected_keys = ImmutableHashSet<uint>.Empty;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
waiting_for_mouse_up = true;
|
||||
wait_for_mouse_up();
|
||||
async void wait_for_mouse_up() {
|
||||
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
|
||||
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
waiting_for_mouse_up = false;
|
||||
}
|
||||
} else {
|
||||
o.OnNext(selected_keys);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public class OrderParamsVM : DisposableViewModelBase {
|
||||
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -793,8 +750,6 @@ namespace mbts.spark {
|
||||
AlgoParamPermissions
|
||||
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
HashSet<uint> _selected_order_tree = new HashSet<uint>();
|
||||
LadderVM _ladder_vm;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner)
|
||||
@@ -845,13 +800,7 @@ namespace mbts.spark {
|
||||
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
|
||||
}
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
|
||||
owner._subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
|
||||
_selected_order_tree = new HashSet<uint>(_owner.selected_hierarchical_rows.Select(r => r.order_key));
|
||||
_reset_ladder_vm();
|
||||
})) {
|
||||
() => Disposable.dispose(ref _ladder_vm)
|
||||
};
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
|
||||
// payup methods
|
||||
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
|
||||
void payup_ask(int ticks) => payup(bbo.ask, ticks);
|
||||
@@ -879,8 +828,7 @@ namespace mbts.spark {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (Set(ref _security_md, value)) {
|
||||
_reset_ladder_vm();
|
||||
RaisePropertyChanged(nameof(security), nameof(spec), nameof(ladder_vm));
|
||||
RaisePropertyChanged(nameof(security), nameof(spec));
|
||||
bbo.security_md = value;
|
||||
_payup_bid_cmd.RaiseCanExecuteChanged();
|
||||
_payup_ask_cmd.RaiseCanExecuteChanged();
|
||||
@@ -904,7 +852,6 @@ namespace mbts.spark {
|
||||
}
|
||||
public AlgoDefinitionVM algo_vm { get; }
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
|
||||
public LadderVM ladder_vm => _ladder_vm;
|
||||
public BBOVM bbo { get; }
|
||||
public ICommand send_order { get; }
|
||||
public ICommand payup_bid_cmd => _payup_bid_cmd;
|
||||
@@ -939,17 +886,6 @@ namespace mbts.spark {
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
|
||||
|
||||
void _reset_ladder_vm() {
|
||||
if (_ladder_vm == null && !_security_md.HasValue)
|
||||
return;
|
||||
Disposable.replace(ref _ladder_vm,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_owner._bus,
|
||||
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.Default)) : null);
|
||||
RaisePropertyChanged(nameof(ladder_vm));
|
||||
}
|
||||
void _assign_param_permissions<T>(IAlgoParamVM<T> param, ref AlgoParamPermissions permissions_backing, AlgoParamPermissions permissions) {
|
||||
if (permissions_backing != permissions) {
|
||||
permissions_backing = permissions;
|
||||
@@ -996,128 +932,6 @@ namespace mbts.spark {
|
||||
EqualityComparer.aggregate_hash((int)algo_tree_display_mode, column_filters?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class SelectedOrderParamsMon {
|
||||
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SelectedOrderParamsMon(DynamicOrderHelper dyn_helper) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
|
||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
||||
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
||||
}
|
||||
|
||||
event Action _security_md_map_changed;
|
||||
|
||||
public SecurityImmutable? security => _security_mon.common_value;
|
||||
public SecurityMD? security_md => security is SecurityImmutable sec && _security_md_map.TryGetValue(sec.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
||||
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||
public uint? size => _size_mon.common_value_nullable();
|
||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _all_active_mon.common_value;
|
||||
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityMD?> subscribe_security_md() =>
|
||||
Observable.Create<SecurityMD?>(o => {
|
||||
SecurityMD? last_value = security_md;
|
||||
o.OnNext(last_value);
|
||||
_security_mon.common_value_changed += on_common_sec_changed;
|
||||
_security_md_map_changed += refresh;
|
||||
return Disposable.Create(() => {
|
||||
_security_mon.common_value_changed -= on_common_sec_changed;
|
||||
_security_md_map_changed -= refresh;
|
||||
});
|
||||
void on_common_sec_changed(SecurityImmutable? _) => refresh();
|
||||
void refresh() {
|
||||
var curr = security_md;
|
||||
if (curr != last_value)
|
||||
o.OnNext(last_value = curr);
|
||||
}
|
||||
});
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
||||
|
||||
public IDisposable begin_udpate() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
public void on_order_selected(OrderSecurity order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void on_order_unselected(OrderSecurity order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
|
||||
_security_md_map = security_md_map;
|
||||
_security_md_map_changed?.Invoke();
|
||||
}
|
||||
|
||||
public void on_order_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
|
||||
using (begin_udpate()) {
|
||||
_group_mon.remove(ch.oldval);
|
||||
_group_mon.add(ch.newval);
|
||||
}
|
||||
}
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
SecurityMD? _get_sec_md(OrderSecurity osam) =>
|
||||
osam.security.HasValue && _security_md_map.TryGetValue(osam.security.Value.Id, out SecurityMD smd) ? smd : (SecurityMD?)null;
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
class RightPanelModel {
|
||||
OrderBookRightPanelSettings.ViewMode _mode;
|
||||
OrderBookRightPanelSettings.Components _all_selected, _hidden = OrderBookRightPanelSettings.Components.None;
|
||||
@@ -1180,11 +994,13 @@ namespace mbts.spark {
|
||||
var join = config.Configuration.get<bts.config.JoinAttachUIOptions>();
|
||||
var lean = config.Configuration.get<bts.config.LeanAttachUIOptions>();
|
||||
var top = config.Configuration.get<bts.config.TopAttachUIOptions>();
|
||||
var offset = config.Configuration.get<bts.config.OffsetAttachUIOptions>();
|
||||
_algo_type_conf = new Dictionary<CustomOrderDefinition.CustomOneofCase, (bool, bool)> {
|
||||
{ CustomOrderDefinition.CustomOneofCase.Cover, (cover.show_price, cover.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Join, (join.show_price, join.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Lean, (lean.show_price, lean.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) }
|
||||
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Offset, (offset.show_price, offset.show_quantity) }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -17,10 +17,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.orderbook.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class OrderBookWindow : SparkWindow<OrderBookStartupData>, ISharedWindow {
|
||||
@@ -65,7 +63,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
_vm = new OrderBookVM(this.observe_title(), _bus, _notification_service, _model, _settings_manager);
|
||||
_vm = new OrderBookVM(_bus, _notification_service, _model, _settings_manager);
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
@@ -171,14 +169,11 @@ namespace mbts.spark {
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.general = s.general.with(_vm.core_columns.Select(c => c.create()));
|
||||
return _vm.grid_settings_manager.create_vm(s, this.observe_title());
|
||||
return _vm.grid_settings_manager.create_vm(s);
|
||||
}
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
gs_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
|
||||
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(gs_vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
|
||||
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
|
||||
});
|
||||
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_price(fs.price, o => o.order.price(), o => o.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace bts.shell.orderbook {
|
||||
IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>> IGridSettingsWithNotifications<OrderSecurity, OrderRecordTuplePredicate>.predicate_animations => predicate_animations;
|
||||
|
||||
public OrderBookGridSettings with(GridSettings general) =>
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations);
|
||||
new OrderBookGridSettings(general ?? this.general, predicate_animations) {
|
||||
algo_tree_display_mode = algo_tree_display_mode
|
||||
};
|
||||
|
||||
public bool Equals(OrderBookGridSettings other) =>
|
||||
other != null &&
|
||||
|
||||
+18
-12
@@ -14,16 +14,12 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
class OrderBookGridSettingsManager : IDisposable, IObserver<OrderBookGridSettings> {
|
||||
public static readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> hard_column_flags;
|
||||
|
||||
readonly IObserver<OrderBookGridSettings> _core_man_as_observer;
|
||||
AlgoTreeDisplayMode _algo_tree_display_mode;
|
||||
|
||||
public OrderBookGridSettingsManager(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IEnumerable<OrderBookRowVM> rows,
|
||||
ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
static OrderBookGridSettingsManager() {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
// TODO: Remove the immutable font-weight flags when cell animation is ready
|
||||
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
@@ -31,10 +27,20 @@ namespace bts.shell.orderbook {
|
||||
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
// TODO: Remove the immutable font-color flags when cell animation is ready
|
||||
foreach (var c in new[] { Columns.Side, Columns.Size, Columns.Filled }) {
|
||||
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
col_settings_flags[c.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
}
|
||||
col_settings_flags[Columns.Commit.ToString()] |= GridColumnSettingsFeatureFlags.ImmutableHeader;
|
||||
hard_column_flags = col_settings_flags;
|
||||
}
|
||||
|
||||
public OrderBookGridSettingsManager(
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IEnumerable<OrderBookRowVM> rows,
|
||||
ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
core_manager = new GridSettingsManager<OrderBookRowVM, OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
|
||||
owner_title, bus, model, rows, columns,
|
||||
bus, model, rows, columns,
|
||||
() => new OrderRecordTuplePredicate(),
|
||||
() => {
|
||||
var pvm = PredicateVMWrapper.create<OrderSecurity, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
@@ -53,7 +59,7 @@ namespace bts.shell.orderbook {
|
||||
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);
|
||||
hard_column_flags);
|
||||
_core_man_as_observer = core_manager;
|
||||
}
|
||||
|
||||
@@ -83,8 +89,8 @@ namespace bts.shell.orderbook {
|
||||
return gs;
|
||||
}
|
||||
|
||||
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs, IObservable<string> owner_title) =>
|
||||
new OrderBookSettingsVM(core_manager.create_vm(gs.general, owner_title)) {
|
||||
public OrderBookSettingsVM create_vm(OrderBookGridSettings gs) =>
|
||||
new OrderBookSettingsVM(core_manager.create_vm(gs.general)) {
|
||||
algo_tree_display_mode = gs.algo_tree_display_mode
|
||||
};
|
||||
|
||||
|
||||
@@ -137,12 +137,13 @@ namespace bts.shell.orderbook {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criterion);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
// standard
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
@@ -167,6 +168,13 @@ namespace bts.shell.orderbook {
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// editable
|
||||
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
|
||||
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
|
||||
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
|
||||
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Size, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
|
||||
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
|
||||
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -174,7 +182,7 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
@@ -203,13 +211,26 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> editable {
|
||||
get {
|
||||
yield return cancel;
|
||||
yield return update;
|
||||
yield return edit_quantity;
|
||||
yield return edit_price;
|
||||
yield return commit;
|
||||
yield return attach_detach;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in all) {
|
||||
foreach (var col_setting in editable)
|
||||
yield return col_setting;
|
||||
foreach (var col_setting in standard) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace bts.shell.orderbook {
|
||||
this.core_settings_vm = core_settings_vm;
|
||||
|
||||
public OrderBookSettingsVM(
|
||||
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
Func<OrderRecordTuplePredicate> predicate_data_factory,
|
||||
Func<OrderRecordTuplePredicate, INotificationPredicate<OrderSecurity>> notification_predicate_factory,
|
||||
Func<IPropertyWrapperVM<OrderRecordTuplePredicate>> create_predicate_vm,
|
||||
@@ -25,7 +26,7 @@ namespace bts.shell.orderbook {
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<OrderSecurity, OrderRecordTuplePredicate>>, OrderBookGridSettings> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
core_settings_vm = new GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings>(
|
||||
owner_title, bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
|
||||
bus, orders_model, predicate_data_factory, notification_predicate_factory, create_predicate_vm, create_column_predicate_vm, create_settings_obj, column_flags);
|
||||
}
|
||||
|
||||
public GridSettingsVM<OrderSecurity, OrderRecordTuplePredicate, OrderBookGridSettings> core_settings_vm { get; }
|
||||
|
||||
@@ -554,7 +554,7 @@
|
||||
BorderThickness="0,1,0,0"
|
||||
ResizeBehavior="PreviousAndNext"
|
||||
/>
|
||||
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding order_mod_vm.ladder_vm}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
|
||||
<usp:MiniLadderView x:Name="ladder" Grid.Column="1" Grid.Row="4" DataContext="{Binding ladder_vm.value}" CanUserSendOrders="False" Settings="{x:Static sp:LadderSettings.Default}"/>
|
||||
<GridSplitter
|
||||
x:Name="ladder_splitter"
|
||||
Grid.Column="1"
|
||||
|
||||
@@ -117,12 +117,12 @@ namespace bts.shell {
|
||||
var temp_sound_triggers = _sound_trigger_refs;
|
||||
foreach (var kv in temp_sound_triggers) {
|
||||
if (kv.Key.trigger(value)) {
|
||||
sounds.AddRange(kv.Value.keys);
|
||||
sounds.AddRange(kv.Value.Keys);
|
||||
}
|
||||
}
|
||||
var temp_toast_triggers = _toast_trigger_refs;
|
||||
foreach (var krc in temp_toast_triggers) {
|
||||
if (krc.key.trigger(value) is IToastNotification tn && tn.pass)
|
||||
foreach (var kv in temp_toast_triggers) {
|
||||
if (kv.Key.trigger(value) is IToastNotification tn && tn.pass)
|
||||
toast.Add(tn);
|
||||
}
|
||||
if (sounds.Count > 0 || toast.Count > 0)
|
||||
|
||||
@@ -26,7 +26,11 @@ namespace mbts.spark.utilities {
|
||||
public readonly Func<CustomOrderDefinition, CustomOrderDefinition> get_child;
|
||||
public readonly Action<CustomOrderDefinition, CustomOrderDefinition> assign_child;
|
||||
|
||||
public ChildAlgoData(string name, Func<IMessage, CustomOrderDefinition> get_child_from_impl, Action<IMessage, CustomOrderDefinition> assign_child_to_impl, Func<CustomOrderDefinition, CustomOrderDefinition> get_child, Action<CustomOrderDefinition, CustomOrderDefinition> assign_child) {
|
||||
public ChildAlgoData(string name, Func<IMessage, CustomOrderDefinition> get_child_from_impl,
|
||||
Action<IMessage, CustomOrderDefinition> assign_child_to_impl,
|
||||
Func<CustomOrderDefinition, CustomOrderDefinition> get_child,
|
||||
Action<CustomOrderDefinition, CustomOrderDefinition> assign_child)
|
||||
{
|
||||
this.name = name;
|
||||
this.get_child = get_child;
|
||||
this.assign_child = assign_child;
|
||||
@@ -168,17 +172,14 @@ namespace mbts.spark.utilities {
|
||||
// this will be the tag 50 on CME orders
|
||||
// current user might be okay, but we might also want to get it
|
||||
// from the store that serves the risk admin gui
|
||||
ManualOrderIndicator = ManualOrderIndicator.Yes
|
||||
ManualOrderIndicator = ManualOrderIndicator.Yes,
|
||||
User = _admin_model.user.name,
|
||||
UserId = _admin_model.user.id
|
||||
};
|
||||
if (account.HasValue) {
|
||||
rv.Account = account.Value.name;
|
||||
rv.AccountId = account.Value.id;
|
||||
}
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
rv.User = user.Value.name;
|
||||
rv.UserId = user.Value.id;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -215,6 +216,7 @@ namespace mbts.spark.utilities {
|
||||
public TriggerOrderDefinition trigger_order() => OrderDataHelper.Defaults.TriggerOrderDefinition();
|
||||
public DynamicOrderDefinition dynamic_order() => OrderDataHelper.Defaults.DynamicOrderDefinition();
|
||||
public SpreaderOrderDefinition spreader_order() => OrderDataHelper.Defaults.SpreaderOrderDefinition();
|
||||
public SplitterOrderDefinition splitter_order() => OrderDataHelper.Defaults.SplitterOrderDefinition();
|
||||
public SpreaderOrderDefinition spreader_order(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
|
||||
new SpreaderOrderDefinition {
|
||||
Security = security.CloneToMutable(),
|
||||
@@ -245,8 +247,12 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: rv.Offset = offset_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = trigger_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Splitter: rv.Splitter = splitter_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = dynamic_order(); break;
|
||||
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
|
||||
default:
|
||||
OrderTypeVMFactory.GenericOrderFactory.setup_generic_custom_order(rv, order_type);
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -272,6 +278,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.off_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.trigger_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
|
||||
// case CustomOrderDefinition.CustomOneofCase.Splitter: return new OrderMetadata { KeyBinding = "", Color = BTSColors.orange.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.None: goto default;
|
||||
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: return new OrderMetadata { KeyBinding = "Q", Color = BTSColors.purple.rgb_color() };
|
||||
@@ -516,7 +523,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type) {
|
||||
public static CustomOrderDefinitionImmutable? fit_to_type(CustomOrderDefinitionImmutable defi, Security.ContractOneofCase security_type, bool fit_all_to_spreader = false) {
|
||||
var mdef = defi.CloneToMutable();
|
||||
switch (security_type) {
|
||||
case Security.ContractOneofCase.Synthetic:
|
||||
@@ -542,7 +549,8 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop:
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit:
|
||||
case CustomOrderDefinition.CustomOneofCase.Market:
|
||||
def = null;
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg:
|
||||
def = fit_all_to_spreader ? new CustomOrderDefinition { Spreader = new SpreaderOrderDefinition() } : null;
|
||||
break;
|
||||
default:
|
||||
var desc_data = _algo_type_case_descriptor_data[def.CustomCase];
|
||||
@@ -591,9 +599,8 @@ namespace mbts.spark.utilities {
|
||||
new AlgoStructureKey(defi.CustomCase)));
|
||||
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[]
|
||||
// The following standard types are not yet ready for production use:
|
||||
// CustomOrderDefinition.CustomOneofCase.Tick,
|
||||
// CustomOrderDefinition.CustomOneofCase.Dynamic,
|
||||
|
||||
// exchange_order_types are the types which can be submitted on an exchange security
|
||||
exchange_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
@@ -604,24 +611,32 @@ namespace mbts.spark.utilities {
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset,
|
||||
CustomOrderDefinition.CustomOneofCase.Splitter,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
// The following spreader types are not yet ready for production use:
|
||||
// CustomOrderDefinition.CustomOneofCase.Dynamic,
|
||||
spreader_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Spreader,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Splitter,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
attachable_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset,
|
||||
CustomOrderDefinition.CustomOneofCase.Scratch,
|
||||
CustomOrderDefinition.CustomOneofCase.Prorata,
|
||||
},
|
||||
outright_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
@@ -717,7 +732,7 @@ namespace mbts.spark.utilities {
|
||||
!b.HasChild);
|
||||
|
||||
public static bool top_equals(TopOrderDefinitionImmutable a, TopOrderDefinitionImmutable b) =>
|
||||
a.WatchLevelTickOffset == b.WatchLevelTickOffset &&
|
||||
a.TicksOff == b.TicksOff &&
|
||||
(a.HasTopThreshold ?
|
||||
b.HasTopThreshold && quantity_measure_equals(a.TopThreshold, b.TopThreshold) :
|
||||
!b.HasTopThreshold) &&
|
||||
@@ -908,7 +923,7 @@ namespace mbts.spark.utilities {
|
||||
add_component(_get_hash(_get_tick_child(tick)));
|
||||
break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top:
|
||||
add_component(def.Top.WatchLevelTickOffset.GetHashCode());
|
||||
add_component(def.Top.TicksOff.GetHashCode());
|
||||
add_component(def.Top.HasTopThreshold ? def.Top.TopThreshold.GetHashCode() : 0);
|
||||
add_component(_get_top_child(def.Top).GetHashCode());
|
||||
break;
|
||||
@@ -1007,6 +1022,9 @@ namespace mbts.spark.utilities {
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = TriggerOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = SpreaderOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = DynamicOrderDefinition(); break;
|
||||
default:
|
||||
OrderTypeVMFactory.GenericOrderFactory.setup_generic_custom_order(rv, order_type);
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -1032,6 +1050,9 @@ namespace mbts.spark.utilities {
|
||||
public static TriggerOrderDefinition TriggerOrderDefinition() => new TriggerOrderDefinition { Child = CustomOrderDefinition(), TriggerCondition = new TriggerCondition(), TriggerAction = new TriggerAction(), };
|
||||
public static DynamicOrderDefinition DynamicOrderDefinition() => new DynamicOrderDefinition();
|
||||
public static SpreaderOrderDefinition SpreaderOrderDefinition() => new SpreaderOrderDefinition { ExecutionStrategy = new ExecutionStrategy() };
|
||||
|
||||
public static SplitterOrderDefinition SplitterOrderDefinition() => new SplitterOrderDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class OrderParamsAggregator {
|
||||
readonly TimeSpan _throttle;
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
|
||||
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_throttle = throttle;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
|
||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
||||
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
||||
}
|
||||
|
||||
public SecurityImmutable? security => _security_mon.common_value;
|
||||
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
||||
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||
public uint? size => _size_mon.common_value_nullable();
|
||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _all_active_mon.common_value;
|
||||
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
||||
|
||||
public IDisposable defer_updates() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
event Action<TableUpdate<OrderSecurity, uint>> _updated;
|
||||
public void Update(TableUpdate<OrderSecurity, uint> table) {
|
||||
_updated?.Invoke(table);
|
||||
}
|
||||
|
||||
public void Add(OrderSecurity order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void Remove(OrderSecurity order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,13 +105,18 @@ namespace mbts.spark {
|
||||
var inline_account_src = order_properties.AggregateLatest(
|
||||
model.factory.subscribe_accounts(),
|
||||
(last, change, cache) => convert_inline_account(change.New.account, cache),
|
||||
(last, props, change) => convert_inline_account(props.account, change.New),
|
||||
(last, props, change) => convert_inline_account(props.account, change.New, false),
|
||||
() => new InlineUserProperty<AccountTuple>(),
|
||||
new InlineOrderProperties(),
|
||||
DictionaryHelper.empty<uint, string>());
|
||||
// Note that with the .Where on the account_tuples, we will ignore any
|
||||
// updates for which all accounts have been removed.
|
||||
// This was a safer change than changing SelectorProperty's IEnumberable.OnNext
|
||||
// so that it would not reset the selected item on server reboots; i.e. this
|
||||
// change only affects this IObservable.
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
model.factory.subscribe_account_tuples(),
|
||||
model.factory.subscribe_account_tuples().Where(items => items.Any()),
|
||||
inline_account_src,
|
||||
a => new AccountTuple?(a),
|
||||
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
|
||||
@@ -263,9 +268,18 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
}
|
||||
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map) =>
|
||||
//
|
||||
// Regarding the 3rd parameter, force_override_from_inline_property:
|
||||
// When using an InlineUserProperty, in the context of assigning a default vs selected item,
|
||||
// there is somewhat of a conflict between the IObserver<InlineUserProperty<TItem>> and the
|
||||
// IObserver<IEnumerable<TItem>> OnNext methods. This 3rd paramter provides yet another override
|
||||
// to say if the force_override should come from the inline property or if the force_override should
|
||||
// be disbled, by way of the immediate return in ViewModelBase._try_auto_select when force_override
|
||||
// is false. Our intent is then accomplished because we are no longer attempting to set a default/
|
||||
// selected item when the account_tuples potentially changes
|
||||
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map, bool force_override_from_inline_property = true) =>
|
||||
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
|
||||
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), account_id.force_override) :
|
||||
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), force_override_from_inline_property ? account_id.force_override : false) :
|
||||
new InlineUserProperty<AccountTuple>();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: param.AsQuantity = new bts.spark.QuantityMeasure(); break;
|
||||
}
|
||||
break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Structure: param.AsStruct = new DynamicOrderStruct(); break;
|
||||
@@ -87,5 +88,16 @@ namespace mbts.spark {
|
||||
}
|
||||
param.AsEnumerated.EnumValue = value ?? string.Empty;
|
||||
}
|
||||
|
||||
internal static bts.spark.QuantityMeasureImmutable? get_quantity_value(DynamicOrderParam param)
|
||||
{
|
||||
return new bts.spark.QuantityMeasureImmutable(param?.AsQuantity);
|
||||
}
|
||||
internal static void set_quantity_value(DynamicOrderParam param, bts.spark.QuantityMeasureImmutable? value)
|
||||
{
|
||||
if (param == null)
|
||||
return;
|
||||
param.AsQuantity = value?.CloneToMutable();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api.orderman;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.shell;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -25,7 +26,7 @@ namespace mbts.spark {
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
|
||||
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
private DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
this.descriptor = descriptor;
|
||||
_param = param;
|
||||
}
|
||||
@@ -116,4 +117,41 @@ namespace mbts.spark {
|
||||
void _sync_elements() =>
|
||||
elements.sync_with(_get_elements(descriptor));
|
||||
}
|
||||
|
||||
|
||||
class DynamicOrderQuantityVM : DynamicOrderParamVM, IDynamicOrderParamVM<QuantityMeasureImmutable?>
|
||||
{
|
||||
static readonly FieldDescriptor _field_desc = DynamicOrderParam.Descriptor.FindFieldByNumber(DynamicOrderParam.AsQuantityFieldNumber);
|
||||
public DynamicOrderQuantityVM(DynamicOrderParamDescriptor descriptor)
|
||||
: base(descriptor) { }
|
||||
|
||||
QuantityMeasureImmutable? IDynamicOrderParamVM<QuantityMeasureImmutable?>.value { get => quantity; set => quantity= value; }
|
||||
public QuantityMeasureImmutable? quantity
|
||||
{
|
||||
get => DynamicOrderHelper.get_quantity_value(param);
|
||||
set
|
||||
{
|
||||
var orig = quantity;
|
||||
DynamicOrderHelper.set_quantity_value(param, value);
|
||||
if (quantity != orig)
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<QuantityMeasureImmutable?> subscribe_value()
|
||||
{
|
||||
return Observable.Create<QuantityMeasureImmutable?>(o =>
|
||||
{
|
||||
PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => PropertyChanged -= on_prop_changed);
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(quantity);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
RaisePropertyChanged(nameof(quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace mbts.ui.spark
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
|
||||
@@ -39,6 +40,7 @@ namespace mbts.ui.spark
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity: return QuantityMeasureTemplate;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -46,10 +47,13 @@ namespace mbts.spark {
|
||||
protected override void on_model_internal() =>
|
||||
_sync_dynamic_params();
|
||||
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_datum() =>
|
||||
OrderDataHelper.get_child_algo_data(new CustomOrderDefinition {
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_datum()
|
||||
{
|
||||
var data = OrderDataHelper.get_child_algo_data(new CustomOrderDefinition {
|
||||
Dynamic = model?.dynamic_helper?.create_def(dyn_type) ?? new DynamicOrderDefinition()
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
void _sync_dynamic_params() {
|
||||
if (model != null) {
|
||||
@@ -70,9 +74,14 @@ namespace mbts.spark {
|
||||
switch (descriptor.ParamType.Primitive) {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return mk<bool?, bool>(DynamicOrderParam.AsBoolFieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return mk<double, double>(DynamicOrderParam.AsDoubleFieldNumber, v => v, v => v, double.NaN);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<uint?, uint>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return mk<int?, int>(DynamicOrderParam.AsInt32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return mk<uint?, uint>(DynamicOrderParam.AsUint32FieldNumber, v => v, v => v.GetValueOrDefault());
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return mk<string, string>(DynamicOrderParam.AsStringFieldNumber, v => v, v => v);
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveQuantity:
|
||||
{
|
||||
var qm = new DynamicOrderQuantityVM(descriptor);
|
||||
return attached(qm, DynamicOrderHelper.get_quantity_value, DynamicOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
|
||||
}
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.Primitive, nameof(descriptor));
|
||||
}
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated:
|
||||
@@ -97,11 +106,19 @@ namespace mbts.spark {
|
||||
mdef => mdef.OrderParams.TryGetValue(pvm.descriptor.ParamName, out DynamicOrderParam p) ? selector(p) : default_value,
|
||||
(mdef, v) => assign_to_param(mdef.OrderParams.GetOrAdd(pvm.descriptor.ParamName, pn => new DynamicOrderParam()), v),
|
||||
() => pvm.value,
|
||||
v => pvm.value = v,
|
||||
v => pvm.value = set_value_not_null_string(v),
|
||||
pvm.subscribe_value(),
|
||||
default_value);
|
||||
return pvm;
|
||||
}
|
||||
|
||||
T set_value_not_null_string<T>(T val)
|
||||
{
|
||||
if (typeof(T) == typeof(string) && val == null)
|
||||
return (T)(object)string.Empty;
|
||||
else
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
[Export]
|
||||
class GenericOrderHelper
|
||||
{
|
||||
[ImportingConstructor]
|
||||
private GenericOrderHelper(OrderService order_service)
|
||||
{
|
||||
}
|
||||
|
||||
internal static string get_enumerated_value(IMessage message, FieldDescriptor fd)
|
||||
{
|
||||
var ev = fd.Accessor.GetValue(message);
|
||||
return ev.ToString();
|
||||
}
|
||||
internal static void set_enumerated_value(IMessage message, FieldDescriptor fd, string value)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var ev = fd.EnumType.Values.First(x => x.Name == value)?.Number;
|
||||
if(ev.HasValue)
|
||||
fd.Accessor.SetValue(message, ev.Value);
|
||||
}
|
||||
|
||||
internal static bts.spark.QuantityMeasureImmutable? get_quantity_value(IMessage message, FieldDescriptor fd)
|
||||
{
|
||||
var qm = (bts.spark.QuantityMeasure)fd.Accessor.GetValue(message);
|
||||
if(qm != null)
|
||||
return new bts.spark.QuantityMeasureImmutable(qm);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
internal static void set_quantity_value(IMessage message, FieldDescriptor fd, bts.spark.QuantityMeasureImmutable? value)
|
||||
{
|
||||
if (message == null)
|
||||
return;
|
||||
fd.Accessor.SetValue(message, value?.CloneToMutable());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using bts.api.orderman;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.shell;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using Google.Protobuf;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
interface IGenericOrderParamVM
|
||||
{
|
||||
FieldDescriptor descriptor { get; }
|
||||
Google.Protobuf.IMessage message { get; set; }
|
||||
}
|
||||
|
||||
interface IGenericOrderParamVM<T> : IGenericOrderParamVM
|
||||
{
|
||||
T value { get; set; }
|
||||
IObservable<T> subscribe_value();
|
||||
}
|
||||
|
||||
abstract class GenericOrderParamVM<TMutable> : DisposableViewModelBase, IGenericOrderParamVM
|
||||
where TMutable : class, IMessage, new()
|
||||
{
|
||||
TMutable _message;
|
||||
|
||||
protected GenericOrderParamVM(FieldDescriptor descriptor)
|
||||
{
|
||||
_message = new TMutable();
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
public FieldDescriptor descriptor { get; set; }
|
||||
public Google.Protobuf.IMessage message
|
||||
{
|
||||
get => _message;
|
||||
set
|
||||
{
|
||||
_message = (TMutable)value;
|
||||
on_param_changed();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void on_param_changed();
|
||||
}
|
||||
|
||||
class GenericOrderParamVM<TMutable, TView, TModel> : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<TView>
|
||||
where TMutable : class, IMessage, new()
|
||||
{
|
||||
readonly Func<TModel, TView> _convert;
|
||||
readonly Func<TView, TModel> _convert_back;
|
||||
TView _value;
|
||||
|
||||
public GenericOrderParamVM(FieldDescriptor field_descriptor, Func<TModel, TView> convert, Func<TView, TModel> convert_back) :
|
||||
base(field_descriptor)
|
||||
{
|
||||
_convert = convert;
|
||||
_convert_back = convert_back;
|
||||
}
|
||||
|
||||
public TView value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
this.descriptor.Accessor.SetValue(this.message, _convert_back(_value = value));
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<TView> subscribe_value() =>
|
||||
Observable.Create<TView>(o =>
|
||||
{
|
||||
PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => PropertyChanged -= on_prop_changed);
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(value);
|
||||
});
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
_value = _convert(ConvertUtils.try_convert<TModel>(this.descriptor.Accessor.GetValue(this.message)));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
class GenericOrderEnumeratedVM<TMutable> : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<string>
|
||||
where TMutable : class, IMessage, new()
|
||||
{
|
||||
public GenericOrderEnumeratedVM(FieldDescriptor descriptor)
|
||||
: base(descriptor)
|
||||
{
|
||||
elements = new ObservableCollection<string>(_get_elements(descriptor));
|
||||
}
|
||||
|
||||
public ObservableCollection<string> elements { get; }
|
||||
|
||||
public string selected_element
|
||||
{
|
||||
get => GenericOrderHelper.get_enumerated_value(message, descriptor);
|
||||
set
|
||||
{
|
||||
var orig = selected_element;
|
||||
GenericOrderHelper.set_enumerated_value(message, descriptor, value);
|
||||
if (selected_element != orig)
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
string IGenericOrderParamVM<string>.value { get => selected_element; set => selected_element = value; }
|
||||
|
||||
IObservable<string> IGenericOrderParamVM<string>.subscribe_value() =>
|
||||
Observable.Create<string>(o =>
|
||||
{
|
||||
PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => PropertyChanged -= on_prop_changed);
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(selected_element);
|
||||
});
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
_sync_elements();
|
||||
RaisePropertyChanged(nameof(selected_element));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_elements(FieldDescriptor descriptor)
|
||||
{
|
||||
if(descriptor.FieldType == Google.Protobuf.Reflection.FieldType.Enum)
|
||||
{
|
||||
return descriptor.EnumType.Values.Select(x => x.Name);
|
||||
}
|
||||
else
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
void _sync_elements()
|
||||
{
|
||||
elements.sync_with(_get_elements(descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class GenericOrderQuantityVM<TMutable> : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<QuantityMeasureImmutable?>
|
||||
where TMutable : class, IMessage, new()
|
||||
{
|
||||
public GenericOrderQuantityVM(FieldDescriptor descriptor)
|
||||
: base(descriptor) { }
|
||||
|
||||
QuantityMeasureImmutable? IGenericOrderParamVM<QuantityMeasureImmutable?>.value { get => quantity; set => quantity= value; }
|
||||
public QuantityMeasureImmutable? quantity
|
||||
{
|
||||
get => GenericOrderHelper.get_quantity_value(message, descriptor);
|
||||
set
|
||||
{
|
||||
var orig = quantity;
|
||||
GenericOrderHelper.set_quantity_value(message, descriptor, value);
|
||||
if (quantity != orig)
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<QuantityMeasureImmutable?> subscribe_value()
|
||||
{
|
||||
return Observable.Create<QuantityMeasureImmutable?>(o =>
|
||||
{
|
||||
PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => PropertyChanged -= on_prop_changed);
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
o.OnNext(quantity);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void on_param_changed()
|
||||
{
|
||||
RaisePropertyChanged(nameof(quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
using bts.api.orderman;
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.spark
|
||||
{
|
||||
class GenericOrderParamTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate BoolTemplate { get; set; }
|
||||
public DataTemplate DoubleTemplate { get; set; }
|
||||
public DataTemplate IntTemplate { get; set; }
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
|
||||
public override DataTemplate SelectTemplate(object item, DependencyObject container)
|
||||
{
|
||||
var vm = item as IGenericOrderParamVM;
|
||||
if (vm != null)
|
||||
return select_template(vm.descriptor);
|
||||
return null;
|
||||
}
|
||||
|
||||
public DataTemplate select_template(Google.Protobuf.Reflection.FieldDescriptor descriptor)
|
||||
{
|
||||
switch (descriptor.FieldType)
|
||||
{
|
||||
case Google.Protobuf.Reflection.FieldType.Bool:
|
||||
return BoolTemplate;
|
||||
case Google.Protobuf.Reflection.FieldType.Float:
|
||||
case Google.Protobuf.Reflection.FieldType.Double:
|
||||
return DoubleTemplate;
|
||||
case Google.Protobuf.Reflection.FieldType.Int32:
|
||||
case Google.Protobuf.Reflection.FieldType.Int64:
|
||||
return IntTemplate;
|
||||
case Google.Protobuf.Reflection.FieldType.UInt32:
|
||||
case Google.Protobuf.Reflection.FieldType.UInt64:
|
||||
return UIntTemplate;
|
||||
case Google.Protobuf.Reflection.FieldType.String:
|
||||
return StringTemplate;
|
||||
case Google.Protobuf.Reflection.FieldType.Enum:
|
||||
return EnumTemplate;
|
||||
case Google.Protobuf.Reflection.FieldType.Message:
|
||||
{
|
||||
if(descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName)
|
||||
return QuantityMeasureTemplate;
|
||||
else if (descriptor.MessageType.FullName == typeof(bts.api.orderman.CustomOrderDefinition).FullName)
|
||||
return OrderDefTemplate;
|
||||
}
|
||||
break;
|
||||
default: return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using bts.api;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
internal class GenericOrderFactory
|
||||
{
|
||||
static public IAlgoVM create_vm(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
{
|
||||
var prop = typeof(CustomOrderDefinition).GetProperty(algo_type.ToString(),
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
var rt = prop.PropertyType;
|
||||
|
||||
var propi = typeof(CustomOrderDefinitionImmutable).GetProperty(algo_type.ToString(),
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
var rti = propi.PropertyType;
|
||||
|
||||
var vmt = typeof(GenericOrderVM<,>);
|
||||
Type[] typeargs = { rt, rti };
|
||||
var makevm = vmt.MakeGenericType(typeargs);
|
||||
object vm = Activator.CreateInstance(makevm, key, algo_type, display_name, editing_permissions);
|
||||
return (IAlgoVM)vm;
|
||||
}
|
||||
static public void setup_generic_custom_order(CustomOrderDefinition custom, CustomOrderDefinition.CustomOneofCase type)
|
||||
{
|
||||
var prop = typeof(CustomOrderDefinition).GetProperty(type.ToString(),
|
||||
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
||||
var rt = prop.PropertyType;
|
||||
var rtt = Activator.CreateInstance(rt);
|
||||
|
||||
var ci = rt.GetProperties().Where(x => x.PropertyType == typeof(CustomOrderDefinition));
|
||||
foreach(var cid in ci)
|
||||
cid.SetValue(rtt, new CustomOrderDefinition { Limit = new LimitOrderDefinition() });
|
||||
prop.SetValue(custom, rtt);
|
||||
}
|
||||
}
|
||||
|
||||
class GenericOrderVM<TMutable, TImmutable> : OrderTypeVMBase<TMutable, TImmutable>
|
||||
where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage
|
||||
{
|
||||
readonly ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string> _parameters =
|
||||
new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
|
||||
readonly AlgoType _algo_type;
|
||||
|
||||
public GenericOrderVM(AlgoKey key, AlgoType algo_type, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions)
|
||||
{
|
||||
_algo_type = algo_type;
|
||||
_parameters = new ObservableKeyedCollection<GenericOrderParamVM<TMutable>, string>(vm => vm.descriptor.Name);
|
||||
_resources.AddRange(
|
||||
ObservableAutoWrapper.connect_item_hooks(_parameters, p =>
|
||||
{
|
||||
p.PropertyChanged += on_param_prop_changed;
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
p.PropertyChanged -= on_param_prop_changed;
|
||||
remove_property(p.descriptor.Name);
|
||||
p.Dispose();
|
||||
});
|
||||
}, true));
|
||||
void on_param_prop_changed(object sender, PropertyChangedEventArgs e) => raise_definition_changed();
|
||||
_sync_generic_params();
|
||||
sync_selectors();
|
||||
}
|
||||
|
||||
public string algo_type => _algo_type.ToString();
|
||||
public override string abbreviation =>
|
||||
model == null ? display_name : model.factory.abbreviate_algo(new OneOf<CustomOrderDefinition.CustomOneofCase, string>(_algo_type));
|
||||
public override AlgoDisplayGroup group => AlgoDisplayGroup.API;
|
||||
public ObservableCollection<GenericOrderParamVM<TMutable>> parameters => _parameters;
|
||||
|
||||
protected override void on_model_internal()
|
||||
{
|
||||
_sync_generic_params();
|
||||
}
|
||||
|
||||
void _sync_generic_params()
|
||||
{
|
||||
if (model != null)
|
||||
{
|
||||
Google.Protobuf.Reflection.MessageDescriptor desc = new TMutable().Descriptor;
|
||||
_parameters.SyncWithDictionary(
|
||||
desc.Fields.InDeclarationOrder().Where(_is_value_param_desc).ToDictionary(p => p.Name), _mk_param_vm);
|
||||
}
|
||||
}
|
||||
static bool _is_value_param_desc(FieldDescriptor desc)
|
||||
{
|
||||
return (desc.FieldType != Google.Protobuf.Reflection.FieldType.Message ||
|
||||
_is_quantity_measure_desc(desc));
|
||||
}
|
||||
|
||||
static bool _is_quantity_measure_desc(FieldDescriptor desc)
|
||||
{
|
||||
return desc.FieldType == Google.Protobuf.Reflection.FieldType.Message &&
|
||||
desc.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName;
|
||||
}
|
||||
|
||||
protected override IEnumerable<ChildAlgoData> get_selector_datum()
|
||||
{
|
||||
var v = OrderDataHelper.get_child_algo_data(_algo_type);
|
||||
return v;
|
||||
}
|
||||
|
||||
GenericOrderParamVM<TMutable> _mk_param_vm(FieldDescriptor descriptor)
|
||||
{
|
||||
ExceptionAssert.Argument.NotNull(descriptor, nameof(descriptor));
|
||||
|
||||
if (descriptor.FieldType == FieldType.Message &&
|
||||
descriptor.MessageType.FullName == typeof(bts.spark.QuantityMeasure).FullName)
|
||||
{
|
||||
var qm = new GenericOrderQuantityVM<TMutable>(descriptor);
|
||||
return attached(qm, GenericOrderHelper.get_quantity_value, GenericOrderHelper.set_quantity_value, new QuantityMeasure { Ratio = 1.0, Absolute = 1 });
|
||||
}
|
||||
else if(descriptor.FieldType == FieldType.Enum)
|
||||
{
|
||||
var pvm = new GenericOrderEnumeratedVM<TMutable>(descriptor);
|
||||
attached(pvm, GenericOrderHelper.get_enumerated_value, GenericOrderHelper.set_enumerated_value);
|
||||
return pvm;
|
||||
}
|
||||
else if (descriptor.FieldType == FieldType.Bool)
|
||||
return mk<bool?, bool>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
else if (descriptor.FieldType == FieldType.Double || descriptor.FieldType == FieldType.Float)
|
||||
return mk<double, double>(descriptor, v => v, v => v, double.NaN);
|
||||
else if (descriptor.FieldType == FieldType.Int32)
|
||||
return mk<int?, int>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
else if (descriptor.FieldType == FieldType.UInt32)
|
||||
return mk<uint?, uint>(descriptor, v => v, v => v.GetValueOrDefault());
|
||||
else if (descriptor.FieldType == FieldType.String)
|
||||
return mk<string, string>(descriptor, v => v, v => v);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
GenericOrderParamVM<TMutable, TView, TModel> mk<TView, TModel>(FieldDescriptor fdesc, Func<TModel, TView> convert, Func<TView, TModel> convert_back, TView default_value = default)
|
||||
{
|
||||
var pvm = new GenericOrderParamVM<TMutable, TView, TModel>(fdesc, convert, convert_back);
|
||||
return attached<TView, GenericOrderParamVM<TMutable, TView, TModel>>(pvm, get_from_param, assign_to_param, default_value);
|
||||
|
||||
TView get_from_param(IMessage param, FieldDescriptor fd) => convert(ConvertUtils.try_convert<TModel>(fdesc.Accessor.GetValue(param)));
|
||||
void assign_to_param(IMessage param, FieldDescriptor fd, TView value) => fdesc.Accessor.SetValue(param, convert_back(value));
|
||||
}
|
||||
|
||||
TParam attached<T, TParam>(TParam pvm, Func<IMessage, FieldDescriptor, T> selector, Action<IMessage, FieldDescriptor, T> assign_to_param, T default_value = default) where TParam : GenericOrderParamVM<TMutable>, IGenericOrderParamVM<T>
|
||||
{
|
||||
add_property<T>(
|
||||
pvm.descriptor.Name,
|
||||
mdef => mdef != null ? selector(mdef, pvm.descriptor) : default_value,
|
||||
(mdef, v) => assign_to_param(mdef, pvm.descriptor, v),
|
||||
() => pvm.value,
|
||||
v => pvm.value = v,
|
||||
pvm.subscribe_value(),
|
||||
default_value);
|
||||
return pvm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@ namespace mbts.spark {
|
||||
IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
|
||||
IAlgoVM selected_algo { get; set; }
|
||||
void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo);
|
||||
mbts.ui.spark.AlgoContentTemplateSelector.OrderKind order_kind {get;}
|
||||
}
|
||||
|
||||
static class OrderTypeVMExtensions {
|
||||
|
||||
@@ -57,6 +57,14 @@ namespace mbts.spark {
|
||||
public static IAlgoVM iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new IcebergOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM spreader(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new SpreaderOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
|
||||
public static IAlgoVM splitter(AlgoKey key, string display_name, AlgoEditingPermissions permissions) =>
|
||||
new SplitterAlgoVM(key, display_name, permissions);
|
||||
|
||||
public static IAlgoVM generic(AlgoKey key, string display_name,
|
||||
AlgoType algo_type, AlgoEditingPermissions editing_permissions)
|
||||
{
|
||||
return GenericOrderFactory.create_vm(key, algo_type, display_name, editing_permissions);
|
||||
}
|
||||
|
||||
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
|
||||
if (def_data.value.CustomCase == AlgoType.Composite) {
|
||||
@@ -88,8 +96,9 @@ namespace mbts.spark {
|
||||
case AlgoType.Iceberg: vm = iceberg(key, name, editing_permissions); break;
|
||||
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
case AlgoType.Splitter: vm = splitter(key,name,editing_permissions); break;
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
default: throw new NotImplementedException();
|
||||
default: vm = generic(key, name, algo_type, editing_permissions); break;
|
||||
}
|
||||
vm.is_expanded = is_expanded && vm.can_expand;
|
||||
return vm;
|
||||
@@ -950,6 +959,8 @@ namespace mbts.spark {
|
||||
|
||||
public string id { get; }
|
||||
public string display_name { get; }
|
||||
public mbts.ui.spark .AlgoContentTemplateSelector.OrderKind order_kind =>
|
||||
new mbts.ui.spark.AlgoContentTemplateSelector.OrderKind(display_name, string.Empty);
|
||||
public bool is_enabled { get; set; } = true;
|
||||
|
||||
public bool initialized { get; private set; }
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using bts.api.orderman;
|
||||
using mbts.ui.shell;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory {
|
||||
class SplitterAlgoVM : OrderTypeVMBase<SplitterOrderDefinition, SplitterOrderDefinitionImmutable>
|
||||
{
|
||||
readonly Property<uint> _clip, _exposed;
|
||||
|
||||
public SplitterAlgoVM(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(id, display_name, editing_permissions)
|
||||
{
|
||||
_clip = add_property<uint>(nameof(max_order_qty), SplitterOrderDefinition.MaxOrderQtyFieldNumber);
|
||||
_exposed = add_property<uint>(nameof(exposed), SplitterOrderDefinition.ExposedQtyFieldNumber);
|
||||
|
||||
}
|
||||
|
||||
public uint max_order_qty { get => _clip.Value; set => _clip.set(value); }
|
||||
public uint exposed { get => _exposed.Value; set => _exposed.set(value); }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,13 +5,16 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
|
||||
readonly Property<uint?> _ticks_off;
|
||||
readonly Property<QuantityMeasureImmutable?> _top_threshold;
|
||||
|
||||
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_ticks_off = add_nullable_property<uint>(nameof(ticks_off), TopDef.TicksOffFieldNumber);
|
||||
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
|
||||
}
|
||||
|
||||
public uint? ticks_off { get => _ticks_off.Value; set => _ticks_off.set(value); }
|
||||
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.ui.tests
|
||||
{
|
||||
class TestShellWindow : IShellWindow
|
||||
{
|
||||
namespace mbts.ui.tests {
|
||||
class TestShellWindow : IShellWindow {
|
||||
#region IShellWindow boilerplate
|
||||
public string Title { get; set; }
|
||||
public event Action TitleChanged = () => { };
|
||||
@@ -20,6 +15,7 @@ namespace mbts.ui.tests
|
||||
#endregion
|
||||
|
||||
public Dispatcher Dispatcher { get; private set; }
|
||||
|
||||
public ShellWindowUI GetContent() {
|
||||
this.Dispatcher = Dispatcher.CurrentDispatcher;
|
||||
var content = new System.Windows.Forms.Button() { Text = "Click Me!", Width = 100, Height = 100 };
|
||||
@@ -27,10 +23,12 @@ namespace mbts.ui.tests
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; set; }
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
public void Dispose() { IsDisposed = true; }
|
||||
}
|
||||
|
||||
class TestSlowWindow : TestShellWindow, ISlowWindow {
|
||||
public void thread_init() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
using System;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.collections {
|
||||
interface IKeyRefCount<T> {
|
||||
T key { get; }
|
||||
int count { get; }
|
||||
}
|
||||
class RefCountMap<TKey> : IDictionary<TKey, int>, IReadOnlyDictionary<TKey, int> {
|
||||
static readonly Func<KeyRefCount, int> _count_selector = krc => krc.count;
|
||||
|
||||
class RefCountMap<TKey> : IReadOnlyCollection<IKeyRefCount<TKey>>, ICollection {
|
||||
readonly IEqualityComparer<TKey> _key_comparer;
|
||||
readonly Dictionary<TKey, KeyRefCount> _dictionary;
|
||||
readonly IReadOnlyCollection<int> _ro_values;
|
||||
readonly ICollection<int> _values;
|
||||
|
||||
public RefCountMap()
|
||||
: this(EqualityComparer<TKey>.Default) { }
|
||||
@@ -22,31 +22,54 @@ namespace mbts.collections {
|
||||
RefCountMap(Dictionary<TKey, KeyRefCount> dictionary, IEqualityComparer<TKey> key_comparer) {
|
||||
_dictionary = dictionary;
|
||||
_key_comparer = key_comparer;
|
||||
CollectionHelper.wrap_selector(dictionary.Values, _count_selector, out _ro_values, out _values);
|
||||
}
|
||||
|
||||
public int this[TKey key] {
|
||||
get => _dictionary[key].count;
|
||||
set {
|
||||
if (!_dictionary.TryGetValue(key, out var krc))
|
||||
_dictionary[key] = krc = new KeyRefCount(key);
|
||||
krc.count = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _dictionary.Count;
|
||||
public object SyncRoot => null;
|
||||
public IReadOnlyCollection<TKey> Keys => _dictionary.Keys;
|
||||
public IReadOnlyCollection<int> Values => _ro_values;
|
||||
public bool IsReadOnly => false;
|
||||
public bool IsSynchronized => false;
|
||||
public int this[TKey key] => _dictionary[key].count;
|
||||
public IReadOnlyCollection<TKey> keys => _dictionary.Keys;
|
||||
public bool contains_key(TKey key) => _dictionary.ContainsKey(key);
|
||||
public object SyncRoot => null;
|
||||
// explicit
|
||||
ICollection<TKey> IDictionary<TKey, int>.Keys => _dictionary.Keys;
|
||||
ICollection<int> IDictionary<TKey, int>.Values => _values;
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, int>.Keys => _dictionary.Keys;
|
||||
IEnumerable<int> IReadOnlyDictionary<TKey, int>.Values => _values;
|
||||
|
||||
public void CopyTo(Array array, int index) =>
|
||||
((ICollection)_dictionary).CopyTo(array, index);
|
||||
public void Add(TKey key, int value) =>
|
||||
_dictionary.Add(key, new KeyRefCount(key) { count = value });
|
||||
|
||||
public IEnumerator<IKeyRefCount<TKey>> GetEnumerator() =>
|
||||
_dictionary.Values.GetEnumerator();
|
||||
public void Add(KeyValuePair<TKey, int> item) =>
|
||||
_dictionary.Add(item.Key, new KeyRefCount(item.Key) { count = item.Value });
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
GetEnumerator();
|
||||
public void Clear() => _dictionary.Clear();
|
||||
|
||||
public void increment(TKey key) =>
|
||||
increment(key, 1);
|
||||
public RefCountMap<TKey> clone() =>
|
||||
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
|
||||
|
||||
public void increment(TKey key, int count) {
|
||||
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
|
||||
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
|
||||
else krc.count += count;
|
||||
public bool Contains(KeyValuePair<TKey, int> item) =>
|
||||
_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value;
|
||||
|
||||
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, int>[] array, int arrayIndex) {
|
||||
foreach (var kv in _dictionary)
|
||||
array[arrayIndex++] = new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index) {
|
||||
foreach (var kv in _dictionary)
|
||||
array.SetValue(new KeyValuePair<TKey, int>(kv.Key, kv.Value.count), index++);
|
||||
}
|
||||
|
||||
public bool decrement(TKey key) =>
|
||||
@@ -66,38 +89,41 @@ namespace mbts.collections {
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool remove(TKey key) =>
|
||||
_dictionary.Remove(key);
|
||||
public IEnumerator<KeyValuePair<TKey, int>> GetEnumerator() {
|
||||
foreach (var kv in _dictionary)
|
||||
yield return new KeyValuePair<TKey, int>(kv.Key, kv.Value.count);
|
||||
}
|
||||
|
||||
public void clear() => _dictionary.Clear();
|
||||
public void increment(TKey key) =>
|
||||
increment(key, 1);
|
||||
|
||||
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
if (try_lookup(key, out key_ref_count)) {
|
||||
remove(key);
|
||||
public void increment(TKey key, int count) {
|
||||
if (!_dictionary.TryGetValue(key, out KeyRefCount krc))
|
||||
_dictionary.Add(key, krc = new KeyRefCount(key) { count = count });
|
||||
else krc.count += count;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) => _dictionary.Remove(key);
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, int> item) {
|
||||
if (_dictionary.TryGetValue(item.Key, out var krc) && krc.count == item.Value) {
|
||||
_dictionary.Remove(item.Key);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
|
||||
key_ref_count = krc;
|
||||
public bool TryGetValue(TKey key, out int value) {
|
||||
if (_dictionary.TryGetValue(key, out var krc)) {
|
||||
value = krc.count;
|
||||
return true;
|
||||
}
|
||||
key_ref_count = null;
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
// explicit
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void absorb(RefCountMap<TKey> other) {
|
||||
foreach (var kv in other._dictionary) {
|
||||
if (!_dictionary.TryGetValue(kv.Key, out KeyRefCount curr))
|
||||
_dictionary.Add(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public RefCountMap<TKey> clone() =>
|
||||
new RefCountMap<TKey>(new Dictionary<TKey, KeyRefCount>(_dictionary, _key_comparer), _key_comparer);
|
||||
|
||||
class KeyRefCount : IKeyRefCount<TKey> {
|
||||
class KeyRefCount {
|
||||
public KeyRefCount(TKey key) =>
|
||||
this.key = key;
|
||||
|
||||
@@ -198,27 +224,7 @@ namespace mbts.collections {
|
||||
_dictionary.Clear();
|
||||
}
|
||||
|
||||
public bool try_pop(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
using (_execute_op()) {
|
||||
if (try_lookup(key, out key_ref_count)) {
|
||||
remove(key);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool try_lookup(TKey key, out IKeyRefCount<TKey> key_ref_count) {
|
||||
using (_execute_op()) {
|
||||
if (_dictionary.TryGetValue(key, out KeyRefCount krc)) {
|
||||
key_ref_count = krc;
|
||||
return true;
|
||||
}
|
||||
key_ref_count = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class KeyRefCount : IKeyRefCount<TKey> {
|
||||
class KeyRefCount {
|
||||
public KeyRefCount(TKey key) =>
|
||||
this.key = key;
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ 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;
|
||||
|
||||
@@ -240,84 +238,7 @@ namespace System.Collections.Specialized {
|
||||
}
|
||||
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
|
||||
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
|
||||
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
|
||||
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
|
||||
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
|
||||
/// <returns>An "Observable Heartbeat"</returns>
|
||||
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
|
||||
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
|
||||
IObservable<bool> alive = source.Select(_ => true);
|
||||
return dead.Merge(alive).DistinctUntilChanged();
|
||||
}
|
||||
|
||||
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
|
||||
// with structs it was even more annoying because they can't be null
|
||||
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
|
||||
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
|
||||
|
||||
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
|
||||
source.CombineLatest(second, (v1, v2) => (v1, v2));
|
||||
|
||||
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
|
||||
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
|
||||
PairWithPrevious(source, default);
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
|
||||
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
|
||||
|
||||
/// <summary>
|
||||
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
|
||||
/// each inner sequence is terminated when the next one begins.
|
||||
/// <para/>In other words, observers are notified by the inner observerables in "series," rather than "parallel."
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="select_next"></param>
|
||||
/// <param name="source_sub"></param>
|
||||
/// <returns></returns>
|
||||
public static IConnectableObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
|
||||
new ConnectableSeriesSource<TSource>(source).create_connectable(select_next);
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
|
||||
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
if (can_get_value())
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
|
||||
Observable.Create<T>(o => {
|
||||
callback();
|
||||
add_handler(callback);
|
||||
return Disposable.Create(() => remove_handler(callback));
|
||||
void callback() => o.OnNext(selector());
|
||||
});
|
||||
|
||||
static class SparkObservableExtensions {
|
||||
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
|
||||
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
|
||||
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
|
||||
@@ -326,47 +247,5 @@ namespace System.Reactive.Linq {
|
||||
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
|
||||
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) =>
|
||||
source.Subscribe(new CompositeObserver<T>(observers));
|
||||
|
||||
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
|
||||
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
|
||||
if (buffer_size == 0) return source.Publish().RefCount();
|
||||
int num_subscribers = 0;
|
||||
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
|
||||
IDisposable source_sub = null;
|
||||
return Observable.Create<T>(o => {
|
||||
if (Interlocked.Increment(ref num_subscribers) == 1)
|
||||
source_sub = source.Subscribe(replay.OnNext);
|
||||
IDisposable sub = replay.Subscribe(o);
|
||||
return Disposable.Create(() => {
|
||||
sub.Dispose();
|
||||
if (Interlocked.Decrement(ref num_subscribers) == 0)
|
||||
source_sub.Dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
|
||||
/// </summary>
|
||||
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
|
||||
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
|
||||
|
||||
/// <summary>
|
||||
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
|
||||
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="subscribe_value"></param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
|
||||
IDisposable value_sub = null;
|
||||
return new Disposables.CompositeDisposable(source.Subscribe(val => Disposable.replace(ref value_sub, subscribe_value(val)))) {
|
||||
() => Disposable.dispose(ref value_sub)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ namespace mbts.ui {
|
||||
// module to ask to restore the window after app restart.
|
||||
IShellModule Owner { get; }
|
||||
|
||||
void on_shell_window_vm(ShellWindowVM shell_window_vm);
|
||||
|
||||
// Return the UI controls which will be displayed
|
||||
ShellWindowUI GetContent();
|
||||
|
||||
@@ -113,5 +115,28 @@ namespace mbts.ui {
|
||||
return src.Subscribe(o);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IObservable sequence of <see cref="ShellWindowVM.Title"/>.
|
||||
/// </summary>
|
||||
public static IObservable<string> literal_title_source(IShellWindow shell_window, ShellWindowVM shell_window_vm) {
|
||||
return Observable.Create<string>(o => {
|
||||
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
|
||||
shell_window.TitleChanged += notify;
|
||||
notify();
|
||||
connect();
|
||||
return Disposable.Create(disconnect);
|
||||
void connect() {
|
||||
shell_window_vm.Model.TitleChanged += notify;
|
||||
shell_window_vm.TitleOverrideChanged += on_title_override_changed;
|
||||
}
|
||||
void disconnect() {
|
||||
shell_window_vm.Model.TitleChanged -= notify;
|
||||
shell_window_vm.TitleOverrideChanged -= on_title_override_changed;
|
||||
}
|
||||
void on_title_override_changed(string value) => notify();
|
||||
void notify() => o.OnNext(shell_window_vm.Title);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,12 @@ namespace mbts.persist {
|
||||
try {
|
||||
s = await _persist.get<UserSettings>(NAME_SPACE, DEFAULT_KEY);
|
||||
if (s != null) {
|
||||
s.ladder.sanitize();
|
||||
// ensure valid named ladder set (w/ distinct, non-zero ids)
|
||||
var ladder_profile_map = new Dictionary<uint, LadderSettingsProfile>();
|
||||
var unidentified_lps = new List<LadderSettingsProfile>();
|
||||
foreach (var lp in s.ladder_profiles) {
|
||||
lp.value.sanitize();
|
||||
if (lp.id == 0) {
|
||||
unidentified_lps.Add(lp);
|
||||
} else ladder_profile_map[lp.id] = lp;
|
||||
|
||||
@@ -40,11 +40,11 @@ namespace bts.utils {
|
||||
public event Action<T> common_value_changed;
|
||||
public event Action<Plurality> plurality_changed;
|
||||
|
||||
public IReadOnlyCollection<T> distinct_values => _values.keys;
|
||||
public T common_value => _values.keys.SafeSingleOrDefault(_default_value);
|
||||
public IReadOnlyCollection<T> distinct_values => _values.Keys;
|
||||
public T common_value => _values.Keys.SafeSingleOrDefault(_default_value);
|
||||
public Plurality plurality {
|
||||
get {
|
||||
switch (_values.keys.Count) {
|
||||
switch (_values.Count) {
|
||||
case 0: return Plurality.None;
|
||||
case 1: return Plurality.One;
|
||||
default: return Plurality.Many;
|
||||
@@ -115,7 +115,7 @@ namespace bts.utils {
|
||||
_deferred_adds = new RefCountMap<T>(value_comparer);
|
||||
_deferred_removes = new RefCountMap<T>(value_comparer);
|
||||
_value_groups = new Dictionary<TKey, RefCountMap<T>>(key_comparer);
|
||||
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.keys);
|
||||
distinct_value_groups = new MappedDictionary<TKey, RefCountMap<T>, IEnumerable<T>>(_value_groups, rcm => rcm.Keys);
|
||||
}
|
||||
|
||||
public event Action<KeyValuePair<TKey, IEnumerable<T>>?> common_value_group_changed;
|
||||
@@ -177,27 +177,27 @@ namespace bts.utils {
|
||||
++_deferral_count;
|
||||
return Disposable.Create(() => {
|
||||
if (--_deferral_count == 0 && (_deferred_adds.Count > 0 || _deferred_removes.Count > 0)) {
|
||||
foreach(var krc in _deferred_removes) {
|
||||
var k = _key(krc.key);
|
||||
foreach(var kv in _deferred_removes) {
|
||||
var k = _key(kv.Key);
|
||||
if (_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
|
||||
rcm.decrement(krc.key, krc.count);
|
||||
rcm.decrement(kv.Key, kv.Value);
|
||||
if (rcm.Count == 0)
|
||||
_value_groups.Remove(k);
|
||||
}
|
||||
}
|
||||
foreach (var krc in _deferred_adds) {
|
||||
var k = _key(krc.key);
|
||||
foreach (var kv in _deferred_adds) {
|
||||
var k = _key(kv.Key);
|
||||
if (!_value_groups.TryGetValue(k, out RefCountMap<T> rcm)) {
|
||||
_value_groups.Add(k, rcm = new RefCountMap<T>(_value_comparer));
|
||||
}
|
||||
rcm.increment(krc.key, krc.count);
|
||||
rcm.increment(kv.Key, kv.Value);
|
||||
}
|
||||
if (!EqualityComparer.nullable_equals(last_fired_common_key, common_value_group?.Key)) {
|
||||
common_value_group_changed?.Invoke(common_value_group);
|
||||
} else if (last_fired_common_key is TKey k) {
|
||||
common_values_changed?.Invoke((
|
||||
_deferred_removes.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k)),
|
||||
_deferred_adds.Select(krc => krc.key).Where(v => _key_comparer.Equals(_key(v), k))));
|
||||
_deferred_removes.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k)),
|
||||
_deferred_adds.Select(krc => krc.Key).Where(v => _key_comparer.Equals(_key(v), k))));
|
||||
}
|
||||
_deferred_adds = new RefCountMap<T>(_value_comparer);
|
||||
_deferred_removes = new RefCountMap<T>(_value_comparer);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace mbts.utils {
|
||||
_threshold = value;
|
||||
}
|
||||
}
|
||||
public IReadOnlyCollection<T> filtered_items => _filtered_items.keys;
|
||||
public IReadOnlyCollection<T> filtered_items => _filtered_items.Keys;
|
||||
|
||||
public IDisposable join(T item) =>
|
||||
_subscribe_item == null ? throw new InvalidOperationException("No 'subscribe-item' delegate was specified in the constructor.") : join(item, _subscribe_item(item));
|
||||
|
||||
@@ -820,6 +820,11 @@ namespace mbts.ui.shell {
|
||||
|
||||
void _try_auto_select() {
|
||||
if (_is_user_selected_item) {
|
||||
// if we have a user-selected item and the _desired_item for which we might want to set as the selected item
|
||||
// isn't force_override, then no change
|
||||
if (!_desired_item.force_override) {
|
||||
return;
|
||||
}
|
||||
if (desired_item_valid() && _desired_item.force_override && _try_match_item(_desired_item.value, out TValue match)) {
|
||||
_set_selected_item(match, SelectionReason.SettingsMandatedSelection);
|
||||
return;
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cp="clr-namespace:mbts.ui.controls.color_picker"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:sys="System;assembly=mscorlib"
|
||||
BorderBrush="Black"
|
||||
Padding="3,0,0,0"
|
||||
>
|
||||
@@ -21,10 +16,10 @@
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="19"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Ellipse x:Name="ellipse" Width="19" Height="19" Stroke="{TemplateBinding BorderBrush}"/>
|
||||
<Ellipse x:Name="ellipse" Width="20" Height="20" Stroke="{TemplateBinding BorderBrush}"/>
|
||||
<Path
|
||||
x:Name="arrow"
|
||||
Data="M1,1.5L4.5,5 8,1.5"
|
||||
|
||||
@@ -50,6 +50,16 @@ namespace mbts.ui.controls {
|
||||
LastMessage = 100,
|
||||
ParentKey = 100,
|
||||
AttachedTargetOrderKey = 100;
|
||||
|
||||
public static class MiniOrderBook {
|
||||
public const double
|
||||
Cancel = 37,
|
||||
AttachDetach = 45,
|
||||
Update = 45,
|
||||
EditableSize = 40,
|
||||
EditablePrice = 64,
|
||||
Commit = 20;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ShortColumnWidths {
|
||||
|
||||
@@ -37,6 +37,51 @@ namespace mbts.spark.filters {
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
// this is a copy of add_numeric except we do a tick_equals comparsion
|
||||
// if the ticks are not equals, then it falls back to a regular double comparison
|
||||
//
|
||||
// if the SecurityImmutable? doesn't have a value
|
||||
// then we fallback to double comparison
|
||||
public void add_price(DoubleMinMaxState value, Func<TRow, double> selector, Func<TRow, SecurityImmutable?> si_selector) {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var v = selector(o);
|
||||
var si = si_selector(o);
|
||||
if (si.HasValue) {
|
||||
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, v) <= 0 &&
|
||||
si.Value.ContractSpec.compare_tick_fallback_to_double(max, v) >= 0;
|
||||
}
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var si = si_selector(o);
|
||||
if (si.HasValue) {
|
||||
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, selector(o)) <= 0;
|
||||
}
|
||||
return min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var si = si_selector(o);
|
||||
if (si.HasValue) {
|
||||
return si.Value.ContractSpec.compare_tick_fallback_to_double(max, selector(o)) >= 0;
|
||||
}
|
||||
return max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add_regex(string str, Func<TRow, string> selector) {
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace mbts.ui {
|
||||
|
||||
public void set_predicate_animations(SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> predicate_animations) {
|
||||
_active_predicates.Clear();
|
||||
_animation_ref_count.clear();
|
||||
_animation_ref_count.Clear();
|
||||
_row.row_animations.Clear();
|
||||
foreach (var cs in _row.cell_stores)
|
||||
cs.animations.Clear();
|
||||
|
||||
@@ -23,9 +23,10 @@ namespace mbts.spark {
|
||||
/// For flag behavior implementation, see <see cref="GridSettingsVM{TRowData, TPredicateBuilder}.ColumnVM.apply_flags(GridColumnSettingsFeatureFlags)"/>
|
||||
/// </summary>
|
||||
enum GridColumnSettingsFeatureFlags {
|
||||
None,
|
||||
ImmutableFontColor,
|
||||
ImmutableFontWeight
|
||||
None = 0,
|
||||
ImmutableFontColor = 1,
|
||||
ImmutableFontWeight = 1 << 1,
|
||||
ImmutableHeader = 1 << 2
|
||||
}
|
||||
|
||||
partial class GridSettingsVM<TRowData, TPredicateData, TSettingsData> {
|
||||
@@ -38,27 +39,23 @@ namespace mbts.spark {
|
||||
public class ColumnVM : DisposableViewModelBase, IColumnVM {
|
||||
public readonly GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner;
|
||||
|
||||
readonly Property<string> _id, _display_name;
|
||||
readonly Property<double> _width;
|
||||
readonly Property<HorizontalAlignment> _horizontal_alignment;
|
||||
readonly Property<bool> _is_visible, _can_override_font_color, _is_font_color_overridden, _can_override_font_weight, _is_font_weight_overridden;
|
||||
readonly Property<FontWeight> _font_weight_override;
|
||||
string
|
||||
_id = string.Empty,
|
||||
_display_name = string.Empty;
|
||||
double _width = 70;
|
||||
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
|
||||
bool
|
||||
_is_visible,
|
||||
_can_override_display_name = true,
|
||||
_can_override_font_color = true,
|
||||
_is_font_color_overridden,
|
||||
_can_override_font_weight = true,
|
||||
_is_font_weight_overridden;
|
||||
FontWeight _font_weight_override;
|
||||
GridColumnSettings _settings;
|
||||
|
||||
public ColumnVM(GridSettingsVM<TRowData, TPredicateData, TSettingsData> owner) {
|
||||
this.owner = owner;
|
||||
_id = backing(nameof(id), string.Empty);
|
||||
_display_name = backing(nameof(display_name), string.Empty);
|
||||
_width = backing(nameof(width), 70.0);
|
||||
_horizontal_alignment = backing(nameof(horizontal_alignment), HorizontalAlignment.Left);
|
||||
_is_visible = backing(nameof(is_visible), false);
|
||||
// font color override
|
||||
_can_override_font_color = backing(nameof(can_override_font_color), true);
|
||||
_is_font_color_overridden = backing(nameof(is_font_color_overridden), false);
|
||||
// font weight override
|
||||
_can_override_font_weight = backing(nameof(can_override_font_weight), true);
|
||||
_is_font_weight_overridden = backing(nameof(is_font_weight_overridden), false);
|
||||
_font_weight_override = backing(nameof(font_weight_override), FontWeights.Normal);
|
||||
// animations
|
||||
predicate_animations = new ObservableEditableCollection<PredicateCellAnimationVM>(() =>
|
||||
new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory), true);
|
||||
@@ -66,24 +63,29 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(() => predicate_animations.OfType<IDisposable>().DisposeAll());
|
||||
}
|
||||
|
||||
public string id => _id.Value;
|
||||
public string display_name { get => _display_name.Value; set => _display_name.set(value); }
|
||||
public double width { get => _width.Value; set => _width.set(value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment.Value; set => _horizontal_alignment.set(value); }
|
||||
public bool is_visible { get => _is_visible.Value; set => _is_visible.set(value); }
|
||||
public string id { get => _id; private set => Set(ref _id, value); }
|
||||
public double width { get => _width; set => Set(ref _width, value); }
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => Set(ref _horizontal_alignment, value); }
|
||||
public bool is_visible { get => _is_visible; set => Set(ref _is_visible, value); }
|
||||
// display name
|
||||
public bool can_override_display_name { get => _can_override_display_name; private set => Set(ref _can_override_display_name, value); }
|
||||
public string display_name { get => _display_name; set => Set(ref _display_name, value); }
|
||||
// font color override
|
||||
public bool can_override_font_color => _can_override_font_color.Value;
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden.Value; set => _is_font_color_overridden.set(value); }
|
||||
public bool can_override_font_color { get => _can_override_font_color; private set => Set(ref _can_override_font_color, value); }
|
||||
public bool is_font_color_overridden { get => _is_font_color_overridden; set => Set(ref _is_font_color_overridden, value); }
|
||||
public ColorPickerPopupVM font_color_override { get; } = new ColorPickerPopupVM();
|
||||
// font weight override
|
||||
public bool can_override_font_weight => _can_override_font_weight.Value;
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden.Value; set => _is_font_weight_overridden.set(value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override.Value; set => _font_weight_override.set(value); }
|
||||
public bool can_override_font_weight { get => _can_override_font_weight; private set => Set(ref _can_override_font_weight, value); }
|
||||
public bool is_font_weight_overridden { get => _is_font_weight_overridden; set => Set(ref _is_font_weight_overridden, value); }
|
||||
public FontWeight font_weight_override { get => _font_weight_override; set => Set(ref _font_weight_override, value); }
|
||||
// animations
|
||||
public ObservableEditableCollection<PredicateCellAnimationVM> predicate_animations { get; }
|
||||
|
||||
public (GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) create() {
|
||||
var settings = _settings.with(display_name, is_visible, null,
|
||||
var settings = _settings.with(
|
||||
can_override_display_name ? display_name : null,
|
||||
is_visible,
|
||||
null,
|
||||
is_font_weight_overridden ? new NullableFontWeightProto(font_weight_override) : default,
|
||||
is_font_color_overridden ? new NullableColorProto(font_color_override.color) : default,
|
||||
width,
|
||||
@@ -94,19 +96,21 @@ namespace mbts.spark {
|
||||
|
||||
public void load_from((GridColumnSettings settings, ILookup<TPredicateData, CellAnimation> animations) value) {
|
||||
_settings = value.settings;
|
||||
_id.set(_settings.id);
|
||||
_display_name.set(_settings.display_name);
|
||||
_width.set(_settings.width);
|
||||
_horizontal_alignment.set(_settings.horizontal_alignment);
|
||||
_is_visible.set(_settings.is_visible);
|
||||
_is_font_weight_overridden.set(_settings.font_weight_override.HasValue);
|
||||
_is_font_color_overridden.set(_settings.font_color_override.HasValue);
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override.set(_settings.font_weight_override.Value);
|
||||
}
|
||||
if (_settings.font_color_override.HasValue) {
|
||||
id = _settings.id;
|
||||
display_name = _settings.display_name;
|
||||
width = _settings.width;
|
||||
horizontal_alignment = _settings.horizontal_alignment;
|
||||
is_visible = _settings.is_visible;
|
||||
// font color
|
||||
is_font_color_overridden = _settings.font_color_override.HasValue;
|
||||
if (is_font_color_overridden) {
|
||||
font_color_override.load_from(new SolidColorBrush(_settings.font_color_override.Value));
|
||||
}
|
||||
// font weight
|
||||
is_font_weight_overridden = _settings.font_weight_override.HasValue;
|
||||
if (is_font_weight_overridden) {
|
||||
_font_weight_override = _settings.font_weight_override.Value;
|
||||
}
|
||||
predicate_animations.SetItems(value.animations.empty_if_null().Select(g => {
|
||||
var vm = new PredicateCellAnimationVM(id, owner._create_column_predicate_vm(id), owner._predicate_data_factory);
|
||||
vm.load_from((g.Key, g));
|
||||
@@ -115,15 +119,16 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void apply_flags(GridColumnSettingsFeatureFlags flags) {
|
||||
var split_flags = EnumHelper.split_flags(flags).ToList();
|
||||
// display name
|
||||
can_override_display_name = (flags & GridColumnSettingsFeatureFlags.ImmutableHeader) == 0;
|
||||
if (!can_override_display_name)
|
||||
display_name = id;
|
||||
// font color
|
||||
_can_override_font_color.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontColor));
|
||||
if (!can_override_font_color)
|
||||
_is_font_color_overridden.set(false);
|
||||
can_override_font_color = (flags & GridColumnSettingsFeatureFlags.ImmutableFontColor) == 0;
|
||||
is_font_color_overridden &= can_override_font_color;
|
||||
// font weight
|
||||
_can_override_font_weight.set(!split_flags.Contains(GridColumnSettingsFeatureFlags.ImmutableFontWeight));
|
||||
if (!can_override_font_weight)
|
||||
_is_font_weight_overridden.set(false);
|
||||
can_override_font_weight = (flags & GridColumnSettingsFeatureFlags.ImmutableFontWeight) == 0;
|
||||
is_font_weight_overridden &= can_override_font_weight;
|
||||
}
|
||||
|
||||
public override string ToString() => $"{display_name} ({id})";
|
||||
|
||||
@@ -60,21 +60,17 @@ namespace mbts.ui.settings {
|
||||
[DefaultValue(_default_font_color)]
|
||||
readonly uint _font_color;
|
||||
[ProtoMember(5)]
|
||||
[DefaultValue(default(string))]
|
||||
readonly ColorOpacityProto _selection_highlight;
|
||||
[ProtoMember(6)]
|
||||
readonly List<GridColumnSettings> _columns;
|
||||
List<GridColumnSettings> _columns;
|
||||
[ProtoMember(7)]
|
||||
readonly List<SortingCriteria> _sorting_criterion;
|
||||
List<SortingCriteria> _sorting_criterion;
|
||||
|
||||
public GridSettings() {
|
||||
_font_family = _default_font_family;
|
||||
_font_size = _default_font_size;
|
||||
_font_weight = _default_font_weight;
|
||||
_font_color = _default_font_color;
|
||||
_selection_highlight = default;
|
||||
_columns = new List<GridColumnSettings>();
|
||||
_sorting_criterion = new List<SortingCriteria>();
|
||||
}
|
||||
|
||||
public GridSettings(
|
||||
@@ -95,9 +91,9 @@ namespace mbts.ui.settings {
|
||||
public uint font_color => _font_color;
|
||||
public FontWeight font_weight => FontWeight.FromOpenTypeWeight(_font_weight);
|
||||
public ColorOpacityProto selection_highlight => _selection_highlight;
|
||||
public IReadOnlyList<GridColumnSettings> columns => _columns;
|
||||
public IReadOnlyList<GridColumnSettings> columns => _columns ?? (_columns = new List<GridColumnSettings>(0));
|
||||
[Obsolete] // TODO: remove sorting_criterion from GridSettings entirely. This should be part of IShellWindow's StartupData, since it is an instance-specific setting, and prob not part of the shared defaults
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion;
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = new List<SortingCriteria>(0));
|
||||
|
||||
public static GridSettings get_default() =>
|
||||
new GridSettings(_default_font_family, _default_font_size, FontWeights.Normal, _default_font_color, new ColorOpacityProto(Colors.MediumSlateBlue, 0.4));
|
||||
|
||||
@@ -44,7 +44,6 @@ namespace mbts.ui {
|
||||
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>> _predicate_animations_data;
|
||||
|
||||
public GridSettingsManager(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
IEnumerable<TRow> rows,
|
||||
@@ -90,8 +89,8 @@ namespace mbts.ui {
|
||||
public SolidColorBrush font_color => _font_color.Value;
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
|
||||
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings, IObservable<string> title) {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(title, _bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
public GridSettingsVM<TRowData, TPredicateData, TSettingsData> create_vm(GridSettings settings) {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateData, TSettingsData>(_bus, _orders_model, _predicate_data_factory, _notification_predicate_factory, _create_predicate_vm, _create_column_predicate_vm, _create_settings_obj, _column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load(_create_settings_obj(settings, _predicate_animations_data));
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -54,14 +54,15 @@ namespace mbts.spark {
|
||||
readonly Property<IColumnVM> _current_column;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<string> _font_display_name, _owner_title;
|
||||
readonly Property<string> _font_display_name;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<bool> _show_predicate_animations, _show_cell_predicate_animations;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> _column_flags;
|
||||
|
||||
public GridSettingsVM(
|
||||
IObservable<string> owner_title, IBus bus, OrdersModel orders_model,
|
||||
IBus bus,
|
||||
OrdersModel orders_model,
|
||||
Func<TPredicateData> predicate_data_factory,
|
||||
Func<TPredicateData, INotificationPredicate<TRowData>> notification_predicate_factory,
|
||||
Func<IPropertyWrapperVM<TPredicateData>> create_predicate_vm,
|
||||
@@ -69,7 +70,6 @@ namespace mbts.spark {
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateData>>, TSettingsData> create_settings_obj,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
//
|
||||
_owner_title = backing(nameof(owner_title), owner_title);
|
||||
this.bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_predicate_data_factory = predicate_data_factory;
|
||||
@@ -80,7 +80,7 @@ namespace mbts.spark {
|
||||
_font_family = backing(nameof(font_family), TextUtils.StandardFontFamily);
|
||||
_font_size = backing(nameof(font_size), FontSizes.Small);
|
||||
_font_weight = backing(nameof(font_weight), FontWeights.Normal);
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), nameof(font_family), nameof(font_size));
|
||||
_font_display_name = backing(nameof(font_display_name), () => TextUtils.display_name(font_family, font_size), new[] { nameof(font_family), nameof(font_size) });
|
||||
_show_predicate_animations = backing(nameof(show_predicate_animations), true);
|
||||
_show_cell_predicate_animations = backing(nameof(show_cell_predicate_animations), true);
|
||||
_current_column = backing<IColumnVM>(nameof(current_column));
|
||||
@@ -92,7 +92,6 @@ namespace mbts.spark {
|
||||
public event EventHandler RequestClose;
|
||||
public event Action committed;
|
||||
|
||||
public string owner_title => _owner_title.Value;
|
||||
public IBus bus { get; }
|
||||
public ObservableCollection<IColumnVM> columns { get; } = new ObservableCollection<IColumnVM>();
|
||||
public IColumnVM current_column { get => _current_column.Value; set => _current_column.set(value); }
|
||||
|
||||
@@ -227,11 +227,23 @@
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:PredicateTemplateKeys.SecurityCriteria}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
SelectedCriteria="{Binding value}"
|
||||
AllowEmpty="True"
|
||||
CloseOnOK="True"
|
||||
CloseOnCancel="True"
|
||||
MaxWidth="180"
|
||||
SelectedCriteria="{Binding value, UpdateSourceTrigger=Explicit}"
|
||||
SecurityMap="{Binding Path='[security_map]', UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
|
||||
Header="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}}"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
Header="{Binding value}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding value, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedCriteriaHandler}"
|
||||
>
|
||||
<c:ContractPickerPopup.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}" TextTrimming="CharacterEllipsis"/>
|
||||
</DataTemplate>
|
||||
</c:ContractPickerPopup.HeaderTemplate>
|
||||
</c:ContractPickerPopup>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
@@ -550,13 +562,35 @@
|
||||
</c:DragDropListBox.ItemContainerStyle>
|
||||
<c:DragDropListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="column_item_content_root">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager>
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.can_override_display_name, Source={u:DirectReference column_item_content_root}, Converter={x:Static usp:GridSettingsView.ColumnVisibilityToggleVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.ColumnVisibilityToggle}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="visibility_toggle" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="visibility_toggle" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="(N/A)"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox IsChecked="{Binding is_visible}" />
|
||||
<CheckBox x:Name="visibility_toggle" ToolTipService.ShowOnDisabled="True" IsChecked="{Binding is_visible}"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding id, Converter={x:Static ut:DisplayNameHelper.CamelCaseSpacesConverter}}"/>
|
||||
<TextBlock Margin="2,0,0,0" FontStyle="Italic" Text="{Binding display_name, Mode=OneWay, StringFormat='{} ({0})'}">
|
||||
@@ -576,7 +610,29 @@
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="{Binding current_column.display_name, StringFormat='{}\'{0}\' Cell Appearance'}"/>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid x:Name="column_content_root">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager>
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.current_column.can_override_display_name, Source={u:DirectReference column_content_root}, Converter={x:Static usp:GridSettingsView.DisplayNameVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:GridSettingsView.VisualStateNames.DisplayName}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="display_name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="display_name_text_box" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="(N/A)"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
@@ -594,7 +650,7 @@
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Header</TextBlock>
|
||||
<TextBox Grid.Column="2" Text="{Binding current_column.display_name}"/>
|
||||
<TextBox x:Name="display_name_text_box" Grid.Column="2" ToolTipService.ShowOnDisabled="True" Text="{Binding current_column.display_name}"/>
|
||||
<TextBlock Grid.Row="2">Width</TextBlock>
|
||||
<c:TickUpDownDouble Grid.Row="2" Grid.Column="2" Value="{Binding current_column.width}"/>
|
||||
<TextBlock Grid.Row="4">Horizontal Alignment</TextBlock>
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for GridSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class GridSettingsView : UserControl {
|
||||
public static readonly IValueConverter
|
||||
DisplayNameVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.DisplayName.Active : VisualStateNames.DisplayName.Inactive),
|
||||
ColumnVisibilityToggleVisualStateConverter = ValueConverter.create<bool, string>(can_override => can_override ? VisualStateNames.ColumnVisibilityToggle.Active : VisualStateNames.ColumnVisibilityToggle.Inactive);
|
||||
|
||||
public GridSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -38,6 +42,24 @@ namespace mbts.ui.spark {
|
||||
ValueConverter.create<string, string, Visibility>((id, header) => id == header ? Visibility.Collapsed : Visibility.Visible);
|
||||
|
||||
public bool EnableSoundTriggers { get; set; }
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class DisplayName {
|
||||
public const string GroupName = nameof(DisplayName);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class ColumnVisibilityToggle {
|
||||
public const string GroupName = nameof(ColumnVisibilityToggle);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class PredicateTemplateKeys {
|
||||
|
||||
Loaded 100 of 329 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user