Compare commits
682
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1ed33c2ee | ||
|
|
8bc1455813 | ||
|
|
5a36af1ed8 | ||
|
|
bc12a3bd42 | ||
|
|
4e831b5f89 | ||
|
|
d886327c1a | ||
|
|
ecaa04ecf8 | ||
|
|
44e3070544 | ||
|
|
1815950835 | ||
|
|
533ff8acf7 | ||
|
|
e64eaec240 | ||
|
|
02a30142b7 | ||
|
|
3937939c0a | ||
|
|
8762fbf7ce | ||
|
|
0459171d6e | ||
|
|
be4bfb6152 | ||
|
|
fcdf72c5d9 | ||
|
|
f5caf0b7ba | ||
|
|
4ff1be11e2 | ||
|
|
dad4c1bf9b | ||
|
|
40aa6c518a | ||
|
|
9f575fcdec | ||
|
|
a0f2673b3a | ||
|
|
a807a09bea | ||
|
|
ba3439b049 | ||
|
|
ebe29fec00 | ||
|
|
aee344fc2c | ||
|
|
bd451f6f65 | ||
|
|
7476b40bf1 | ||
|
|
c5cf90017e | ||
|
|
c142ce2a8e | ||
|
|
d082c47e49 | ||
|
|
44d0b96f25 | ||
|
|
cdb8bccb5a | ||
|
|
0f355dfa8f | ||
|
|
47cb8e0b04 | ||
|
|
ae4d7f719b | ||
|
|
a75784ca02 | ||
|
|
0e35ab3a52 | ||
|
|
1bb5ea8aaa | ||
|
|
64916f6bfc | ||
|
|
83adfb8361 | ||
|
|
1a9dd1c37a | ||
|
|
d77925c568 | ||
|
|
94e77ce2b8 | ||
|
|
d4a97f4b0c | ||
|
|
aa62338401 | ||
|
|
ef5dc90406 | ||
|
|
ee94289f22 | ||
|
|
45ba5746a6 | ||
|
|
a327ffe7e7 | ||
|
|
2feef62b65 | ||
|
|
4b1afd914e | ||
|
|
3048cabb56 | ||
|
|
3ad41adc48 | ||
|
|
e2d0d8bbae | ||
|
|
a6b803b4c3 | ||
|
|
1d68aa7fa8 | ||
|
|
d726018b15 | ||
|
|
437158e7f3 | ||
|
|
c670d0012b | ||
|
|
8751a8e496 | ||
|
|
273145feaf | ||
|
|
fe71b3081c | ||
|
|
8000ccb595 | ||
|
|
b9297a86a9 | ||
|
|
a68926bb4a | ||
|
|
f4eb427241 | ||
|
|
4c9d7ef54a | ||
|
|
359a9be900 | ||
|
|
6430f6e2df | ||
|
|
adac484f25 | ||
|
|
c1f7b030ba | ||
|
|
a363421de0 | ||
|
|
7428f9dbfb | ||
|
|
72e35a5914 | ||
|
|
6756ba44eb | ||
|
|
d8b8a5d710 | ||
|
|
5536f5f585 | ||
|
|
7367673ba4 | ||
|
|
901988ec2b | ||
|
|
b676d22f1d | ||
|
|
9b7e877a0b | ||
|
|
16d5341285 | ||
|
|
2a16ac77fb | ||
|
|
9eb76bdfbc | ||
|
|
a658ef7711 | ||
|
|
5ae750b2f1 | ||
|
|
23bbe82080 | ||
|
|
bd30c3f270 | ||
|
|
4d316034aa | ||
|
|
6b582e224c | ||
|
|
e3a366a386 | ||
|
|
f0003ff3c6 | ||
|
|
df5f8f6638 | ||
|
|
3ff26f6bb0 | ||
|
|
c8badf5689 | ||
|
|
58533bf16f | ||
|
|
6b3c3ded68 | ||
|
|
d65e00fade | ||
|
|
53e787ab7f | ||
|
|
68e5d07aef | ||
|
|
68345a181e | ||
|
|
69c121a19c | ||
|
|
780ce7416f | ||
|
|
9fe338a62a | ||
|
|
f027af66c8 | ||
|
|
6b2708b160 | ||
|
|
534474621f | ||
|
|
554614ab72 | ||
|
|
2ede69a263 | ||
|
|
ffd7a9379f | ||
|
|
9def94aceb | ||
|
|
0af30b3cc0 | ||
|
|
f3f34269d3 | ||
|
|
595c2df65c | ||
|
|
a91c2d9bc6 | ||
|
|
40f6a1db08 | ||
|
|
481c1fd2de | ||
|
|
e80fc807e0 | ||
|
|
c8e054d079 | ||
|
|
9480e76d4b | ||
|
|
538411353a | ||
|
|
acf51e1178 | ||
|
|
da6998c391 | ||
|
|
2710cdd32b | ||
|
|
60786b105c | ||
|
|
f488248abf | ||
|
|
8165d1c9d7 | ||
|
|
46dce3ac2b | ||
|
|
e24d8bac7d | ||
|
|
158d63383f | ||
|
|
465b7f8bdf | ||
|
|
c434374f74 | ||
|
|
5ba1299ddf | ||
|
|
ea39014307 | ||
|
|
830b590e5b | ||
|
|
8c58454e32 | ||
|
|
feab43ee3f | ||
|
|
15fb97c688 | ||
|
|
1d275b942d | ||
|
|
c9c93b3cf0 | ||
|
|
c40eef7b03 | ||
|
|
2bafc12502 | ||
|
|
9e5d43f44a | ||
|
|
389359dbfc | ||
|
|
390208327b | ||
|
|
5c1965f534 | ||
|
|
8553229119 | ||
|
|
96b63ff713 | ||
|
|
4903c12533 | ||
|
|
88f23530c6 | ||
|
|
c9d371fb77 | ||
|
|
b173f80703 | ||
|
|
5b6edc455f | ||
|
|
0b7d18301d | ||
|
|
9fac15b473 | ||
|
|
76ff36e514 | ||
|
|
740c14c261 | ||
|
|
ceafab93ab | ||
|
|
a8f86c38e4 | ||
|
|
46cb249016 | ||
|
|
6b799bf93b | ||
|
|
33a71f5f47 | ||
|
|
2940601f4b | ||
|
|
1f7939f49a | ||
|
|
48e273a607 | ||
|
|
e35634020d | ||
|
|
1ede053f59 | ||
|
|
93824786c7 | ||
|
|
25b8c0a728 | ||
|
|
0e4fe1b47e | ||
|
|
cd744963ff | ||
|
|
fb9d877938 | ||
|
|
087b001510 | ||
|
|
02a7c0ddca | ||
|
|
6afa1d581d | ||
|
|
5e04abe573 | ||
|
|
3422a44c87 | ||
|
|
d5fdcc6796 | ||
|
|
f12217d76c | ||
|
|
d85a914046 | ||
|
|
4fdc890cb5 | ||
|
|
3ed0bd9052 | ||
|
|
dbdc915ff2 | ||
|
|
10476c6662 | ||
|
|
74a91a7d2e | ||
|
|
72a6689cd4 | ||
|
|
05e09cb977 | ||
|
|
dab4f5acbb | ||
|
|
a20c53b23d | ||
|
|
8206f457b5 | ||
|
|
d4e6d6293d | ||
|
|
d1a2197ba4 | ||
|
|
369fb55e90 | ||
|
|
37dacff74d | ||
|
|
dc922a0299 | ||
|
|
fb08533488 | ||
|
|
d297b703c7 | ||
|
|
33f533af9b | ||
|
|
085d6402f4 | ||
|
|
f4395ef833 | ||
|
|
e7a9412124 | ||
|
|
da4ee38abd | ||
|
|
af39fac368 | ||
|
|
4f997505f3 | ||
|
|
fcbb66361d | ||
|
|
fa16321691 | ||
|
|
4ecbfe5f5b | ||
|
|
ceb3542e15 | ||
|
|
32e55d07ee | ||
|
|
e079acf91f | ||
|
|
fecedb203a | ||
|
|
6ac05a47e1 | ||
|
|
36ed80dc87 | ||
|
|
729acc4ad5 | ||
|
|
f5bbc4245f | ||
|
|
7e1463683f | ||
|
|
96fc54ff0f | ||
|
|
25b69e4f7d | ||
|
|
d871ffca6d | ||
|
|
213567bcdb | ||
|
|
b7e5c3a374 | ||
|
|
ae4a8bbf46 | ||
|
|
850386c1d4 | ||
|
|
2d4e94a636 | ||
|
|
4d559b8ec1 | ||
|
|
e447096733 | ||
|
|
cd68b95a3e | ||
|
|
2561cb3960 | ||
|
|
c4ba25bdae | ||
|
|
f1f1b68ffb | ||
|
|
ebad5b389f | ||
|
|
160c49514a | ||
|
|
835f453f03 | ||
|
|
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 | ||
|
|
a06df5099a | ||
|
|
8ddd5bdbb5 | ||
|
|
5cbe00beab | ||
|
|
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 | ||
|
|
4cebe8b6be | ||
|
|
c899904f3a | ||
|
|
8e6ccd5b1b | ||
|
|
32eeeee94f | ||
|
|
007054f2b8 | ||
|
|
43822134e3 | ||
|
|
68c1415cf8 | ||
|
|
d9b25fb778 | ||
|
|
0a3e12e012 | ||
|
|
8b33c7fb5c | ||
|
|
831b042c35 | ||
|
|
c9af55f59d | ||
|
|
056176ff47 | ||
|
|
297a925f22 | ||
|
|
11ca6326f3 | ||
|
|
c3c427742d | ||
|
|
132bee0d27 | ||
|
|
efa78ee5fc | ||
|
|
c52b98d17c | ||
|
|
06506be398 | ||
|
|
d951942a95 | ||
|
|
1f99cc68f8 | ||
|
|
d4f27a65f5 | ||
|
|
2b3901fdbe | ||
|
|
fa51d7f441 | ||
|
|
2bc54b0dbb | ||
|
|
5b767482d7 | ||
|
|
8594708073 | ||
|
|
ac4cdc0e2d | ||
|
|
c66dfdaf27 | ||
|
|
83c432d8e2 | ||
|
|
dcd42367e1 | ||
|
|
1fd4212a4e | ||
|
|
aa5b88e1a2 | ||
|
|
3f86968937 | ||
|
|
0385376d04 | ||
|
|
914ec09b4f | ||
|
|
6a4930767b | ||
|
|
fd106559da | ||
|
|
a1270fb928 | ||
|
|
1f8b0bf1e6 | ||
|
|
292acd64e6 | ||
|
|
453f70259a | ||
|
|
18a80516d5 | ||
|
|
685e69adcc | ||
|
|
1ebd0e8d62 | ||
|
|
c09ef31061 | ||
|
|
613ba8004d | ||
|
|
a1ffc24ed9 | ||
|
|
c40055dd2f | ||
|
|
2b7f5572db | ||
|
|
163b77ff84 | ||
|
|
1b1129320a | ||
|
|
589476eb02 | ||
|
|
fe2b167fd5 | ||
|
|
fd0e1dfb0e | ||
|
|
e95eb4a749 | ||
|
|
8fb13bd3c3 | ||
|
|
9cfa229b04 | ||
|
|
72e7901145 | ||
|
|
c2788e4d61 | ||
|
|
0b5d2bd4dc | ||
|
|
98006a07e8 | ||
|
|
420273b861 | ||
|
|
09ed017bf2 | ||
|
|
04fbcc49b1 | ||
|
|
32229e3236 | ||
|
|
e9f61dc3c0 | ||
|
|
f13949ed0f | ||
|
|
f329470165 | ||
|
|
f2b7e692f8 | ||
|
|
2cff906c5f | ||
|
|
f711715350 | ||
|
|
8116700737 | ||
|
|
f902f163de | ||
|
|
d06bef463b | ||
|
|
38db7b28e0 | ||
|
|
e76a72e3e6 | ||
|
|
485991b046 | ||
|
|
63cd653ffd | ||
|
|
cb0ec5f3b9 | ||
|
|
64e7921280 | ||
|
|
e5d31d3c89 | ||
|
|
219da9b22a | ||
|
|
b3bff8ecc3 | ||
|
|
c1fb53ff06 | ||
|
|
220af155a9 | ||
|
|
e7286e305d | ||
|
|
770b857f90 | ||
|
|
4b9e4b25c7 | ||
|
|
0f87973ee2 | ||
|
|
b6790aa7f5 | ||
|
|
d7d319f1e3 | ||
|
|
6adbfda6c4 | ||
|
|
fef5b4edaa | ||
|
|
e8cccdb1c0 | ||
|
|
19dc7f8915 | ||
|
|
69f61757c4 | ||
|
|
cddadcb278 | ||
|
|
666765ffbb | ||
|
|
82a9b4521a | ||
|
|
7c329ceebe | ||
|
|
419833d398 | ||
|
|
0f88144fd6 | ||
|
|
abe35bddf0 | ||
|
|
cd262cd66c | ||
|
|
0133cc8aa5 | ||
|
|
66d05c63af | ||
|
|
9883553fb5 | ||
|
|
bc3ab563bf | ||
|
|
ed23bc7e2b | ||
|
|
3dda3e7f75 | ||
|
|
ff461d42b8 | ||
|
|
d678d202c1 | ||
|
|
ad0b1756b1 | ||
|
|
f34f718c72 | ||
|
|
213fb1de59 | ||
|
|
e8d799a0f6 | ||
|
|
0eb9518485 | ||
|
|
bef46390be | ||
|
|
be5d037ca9 | ||
|
|
4edd804b3c | ||
|
|
86733747f1 | ||
|
|
5202300fb1 | ||
|
|
21f5fd919d | ||
|
|
7aae61decd | ||
|
|
64f78e6077 | ||
|
|
318ea5909c | ||
|
|
83190b437a | ||
|
|
5a54b37af7 | ||
|
|
12dd3909bf | ||
|
|
5ddb0f92ea | ||
|
|
69f3f1f467 | ||
|
|
be42f9e583 | ||
|
|
6d069f5174 | ||
|
|
7a31adcc1e | ||
|
|
512175966d | ||
|
|
67de7c5a33 | ||
|
|
8add53a559 | ||
|
|
3e7e572763 | ||
|
|
159e8fba23 | ||
|
|
0d020e9821 | ||
|
|
fb1d70b08c | ||
|
|
c2f6b01567 | ||
|
|
4a758c7554 | ||
|
|
5c79a08f0e | ||
|
|
4b7adbe0c9 | ||
|
|
164c88fa60 | ||
|
|
b57d6cce85 | ||
|
|
e47706eb1b | ||
|
|
c2e91b03f6 | ||
|
|
114c9f3c27 | ||
|
|
40805be11e | ||
|
|
b21cd840c2 | ||
|
|
4854a06eda | ||
|
|
b5c70fc34e | ||
|
|
a1dba1d7d0 | ||
|
|
efdea5256f | ||
|
|
44e7cfb4a5 | ||
|
|
a7da930e69 | ||
|
|
d64df27188 | ||
|
|
93534eb801 | ||
|
|
d1b2dbd688 | ||
|
|
bebe0c2478 | ||
|
|
61126fb3d5 | ||
|
|
0af5b78318 | ||
|
|
c652032ddf | ||
|
|
7bd868b753 | ||
|
|
3254775eb9 | ||
|
|
1bc4510862 | ||
|
|
e452413b92 | ||
|
|
b0886f6b3d | ||
|
|
62c7886807 | ||
|
|
b1fb964648 | ||
|
|
ee19f7c7da | ||
|
|
a0bf7779f5 | ||
|
|
cd41632b53 | ||
|
|
d4c2eb2bbc | ||
|
|
95a5791867 | ||
|
|
ecd384501e | ||
|
|
0483460ac9 | ||
|
|
0bc24fa7b7 | ||
|
|
af365177d0 | ||
|
|
ac60a1447e | ||
|
|
e8b5d4f8fd | ||
|
|
7fd8eeb1ff | ||
|
|
7563eb7afe | ||
|
|
5df1e5f383 | ||
|
|
99750da31d | ||
|
|
49247dba3f | ||
|
|
a25257cb33 | ||
|
|
cb157dbce1 | ||
|
|
81bbafd3a1 | ||
|
|
370b4bcde9 | ||
|
|
6756b23104 | ||
|
|
c5f7ee0ed3 | ||
|
|
16eeddec7d | ||
|
|
b1c180d663 | ||
|
|
dfe008922b | ||
|
|
6afb16abcb | ||
|
|
445bbb10f5 | ||
|
|
70c006f797 | ||
|
|
d3de1c2c41 | ||
|
|
307d3d9466 | ||
|
|
3a5810d8ac | ||
|
|
9ba2ba1d75 | ||
|
|
29c772cbb6 | ||
|
|
0b03180c4d | ||
|
|
1518906b7f | ||
|
|
31fe708907 | ||
|
|
4b2f39c536 | ||
|
|
995fa3df35 | ||
|
|
18eb28dfd2 | ||
|
|
040960d33b | ||
|
|
a28d5883f7 | ||
|
|
8a1d49733e | ||
|
|
0da6b2cc3e | ||
|
|
3f245cd886 | ||
|
|
570f85377a | ||
|
|
8afb35e564 | ||
|
|
a38a6057e0 | ||
|
|
dcd8984b33 | ||
|
|
6647b28a9f | ||
|
|
99cdfadf8d | ||
|
|
fb314f0eb1 | ||
|
|
8a965d41ff | ||
|
|
7428331504 | ||
|
|
5b818bf276 | ||
|
|
d8559276d0 | ||
|
|
3d11aba8ef | ||
|
|
c34d262d1a | ||
|
|
717c90867a | ||
|
|
90178908ba | ||
|
|
01956e92b6 | ||
|
|
b1e19294e6 | ||
|
|
1567ca475d | ||
|
|
938a572a54 | ||
|
|
e8bc07360d | ||
|
|
91989e6baf | ||
|
|
7d75d87e6b | ||
|
|
f13a3325bc | ||
|
|
81ae503aa2 | ||
|
|
0a33fb37b9 | ||
|
|
729986d53d | ||
|
|
e19bb7c621 | ||
|
|
ea232fbaf1 | ||
|
|
6fc3c0b601 | ||
|
|
8aee612bfa | ||
|
|
5f125790cf | ||
|
|
117e107116 | ||
|
|
c8c6aea188 | ||
|
|
5c22b43eb5 | ||
|
|
e1f590127e | ||
|
|
3a516df593 | ||
|
|
1b4275db4c | ||
|
|
91bc135c61 | ||
|
|
5135a21dc2 | ||
|
|
592ad39a32 | ||
|
|
1fd66a17ca | ||
|
|
3ec896d609 | ||
|
|
8f1eab57b7 |
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
+4
-4
@@ -118,7 +118,7 @@
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"test_filter"
|
||||
"ApiUpdateTests"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -135,13 +135,13 @@
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
@@ -249,7 +249,7 @@
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"SingleWriterConcurrentVector"
|
||||
"[algo]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Algos/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
@@ -1,46 +0,0 @@
|
||||
+++
|
||||
title = "BTS Basic Algos"
|
||||
weight = 10
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
##### Parameters
|
||||
* Cross Thresh - size thresh in absolute or ratio terms
|
||||
* Max Chase Ticks - max levels to pay up, in ticks
|
||||
|
||||
|
||||
## Join
|
||||
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
|
||||
|
||||
##### Parameters
|
||||
* Join Thresh - size thresh in absolute or ratio terms
|
||||
|
||||
|
||||
## Top
|
||||
|
||||
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
|
||||
|
||||
##### Parameters
|
||||
* Top Thresh - size thresh in absolute or ratio terms
|
||||
|
||||
|
||||
## Cover
|
||||
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
|
||||
|
||||
##### Parameters
|
||||
* Profit Ticks - ticks above or below fill price to place profit / cover order
|
||||
|
||||
|
||||
## OCO
|
||||
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
|
||||
|
||||
Composing this order with Cover simulates scalping functionality
|
||||
|
||||
##### Parameters
|
||||
* Stop Ticks - ticks below or above main order to place stop / other order
|
||||
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.
@@ -0,0 +1,77 @@
|
||||
+++
|
||||
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
|
||||
|
||||
##### Parameters
|
||||
* Cross Thresh - size thresh in absolute or ratio terms
|
||||
* Max Chase Ticks - max levels to pay up, in ticks
|
||||
|
||||
|
||||
## Join
|
||||
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
|
||||
|
||||
##### Parameters
|
||||
* Join Thresh - size thresh in absolute or ratio terms
|
||||
|
||||
|
||||
## Top
|
||||
|
||||
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
|
||||
|
||||
##### Parameters
|
||||
* Top Thresh - size thresh in absolute or ratio terms
|
||||
|
||||
|
||||
## Cover
|
||||
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
|
||||
|
||||
##### Parameters
|
||||
* Profit Ticks - ticks above or below fill price to place profit / cover order
|
||||
|
||||
|
||||
## OCO
|
||||
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
|
||||
|
||||
Composing this order with Cover simulates scalping functionality
|
||||
|
||||
##### Parameters
|
||||
* 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.
|
||||
|
||||
|
||||
## Scratch
|
||||
The scratch algo is similar to Lean, but instead of watching the opposite inside market, it watches the same side market and pays up when this quantity recedes below the scratch_threshold
|
||||
|
||||
##### Parameters
|
||||
* scratch_threshold - The quantity on the same side market below which to scratch
|
||||
* chase_ticks - The number of ticks through the opposite market to pay up
|
||||
|
||||
|
||||
## ProRata
|
||||
The prorata algo is a special case hedging algo for pro-rata markets. Users can specify two hedge types-- normal hedge and overfill hedge, the latter is spawned when you receive more fills than your fill_limit. Further, the algo attempts to scratch when in an overfilled state AND optionally the same side size falls below stay_in_threshold
|
||||
|
||||
##### Parameters
|
||||
* hedge_ticks - The amount of ticks better hedges are placed on the opposite side market as the fill
|
||||
* chase_ticks - How many ticks through the opposite market on a scratch
|
||||
* fill_limit - The limit after which the algo considers further fills to be 'overfills'
|
||||
* stay_in_threshold - A same side size threshold that enables the algo to 'stay in' even if overfilled
|
||||
* enable_scratch_on_overfill - When true, overfills are immediately scratched upon the algo exiting
|
||||
* enable_stay_in - When true, the algo will 'stay in' so long as the same side size is above stay_in_threshold, when false the algo attempts to 'get out' (and potentially scratch overfills) as soon as it reaches overfill status
|
||||
* hedge - Child order type to send for normal hedges
|
||||
* overfill_hedge - Child order type to send for overfill hedges
|
||||
File renamed without changes.
@@ -4,6 +4,123 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.8.0
|
||||
### User Visible Changes
|
||||
- Added ProRata and Scratch algos
|
||||
- Ladder badge now grows/shrinks to fill the containing cell
|
||||
- Attached algo parameters are now displayed in the update panel
|
||||
- Fixed a bug where the update panel would show '-' values when only
|
||||
1 order was selected
|
||||
- Fixed a bug that cause cause a failure on startup if the user
|
||||
loaded a workspace with a Ladder that had a dynamic order type
|
||||
selected by default
|
||||
- Increased width of login panel to better show login error messages
|
||||
- Added optional 'Maker/Taker' pricing to Spreader algo
|
||||
### Internal changes
|
||||
- Made it easier to generate simple UI's for new order types
|
||||
### API Changes
|
||||
- Dynamic orders can now use QuantityMeasure parameter types
|
||||
|
||||
## 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
|
||||
- This allows multiple standard configuration for the ladder, optionally with security specific variations
|
||||
- Made Cover order more independent from its hedging chidren:
|
||||
- Cover continues to operate even if an hedging child is canceled
|
||||
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
|
||||
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
|
||||
- Changed manual fills to appear in day PnL rather than in the SOD records
|
||||
- Add buy/sell coloring to fill rows in fills window
|
||||
- Streamlined the mini order book:
|
||||
- Display of the mini order book popup is quicker
|
||||
- Only exchange orders are displayed
|
||||
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
|
||||
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
|
||||
- A checkbox column has been added to make clearer which rows are selected
|
||||
### API Changes
|
||||
- Enhanced the API to allow algos to implement custom cancel handling
|
||||
- Allowed algos to control their visibility in the ladder through the API
|
||||
### Internal Changes
|
||||
- Added system requirements to Spark documentation
|
||||
- Implemented keycodes
|
||||
- Temporarily rejected stop and stop limit orders, pending bug fixes
|
||||
|
||||
## 2.2.0
|
||||
### User Visible Changes
|
||||
- Made our orders easier to spot in MBO display
|
||||
@@ -18,6 +135,9 @@ insert_anchor_links = "right"
|
||||
- Fixed a number of problems with filtering on the Fills form
|
||||
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
|
||||
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
|
||||
- Added security symbol column to fills window
|
||||
- Re-organized ladder settings dialog to make it clearer and easier to use
|
||||
|
||||
## 2.1.4
|
||||
### User Visible Changes
|
||||
- Fixed handling of cancel button in security picker
|
||||
@@ -121,7 +241,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
|
||||
@@ -245,6 +365,7 @@ insert_anchor_links = "right"
|
||||
- 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
|
||||
|
||||
- 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
|
||||
@@ -351,6 +472,7 @@ insert_anchor_links = "right"
|
||||
- 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
|
||||
|
||||
|
||||
## 1.3.1 Through 1.3.4
|
||||
|
||||
### Speed Enhancements
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
+++
|
||||
title = "System Requirements"
|
||||
weight = 4
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
|
||||
### Minimum Workstation
|
||||
* New XPS Tower
|
||||
* 8th Generation Intel® Core™ i7-8700 6-Core Processor (12M Cache, up to 4.6 GHz)
|
||||
* 16GB, 2666MHz, DDR4 up to 64GB
|
||||
* Windows 10 Professional 64-bit
|
||||
* NVIDIA® GeForce® GTX 1070 with 8GB GDDR5 Graphics Memory
|
||||
* 1 x 2.5" 256GB SATA Solid State Drive
|
||||
|
||||
### Recommended Workstation
|
||||
* Similar or better than:
|
||||
* Dell Precision Tower 5820
|
||||
* Intel Xeon W-2145 3.7GHz, 4.5GHz Turbo, 8C, 11M Cache, HT, (140W) DDR4-2666
|
||||
* Windows 10 Professional 64-bit
|
||||
* NVIDIA® Quadro® P1000, 4GB, 4 mDP
|
||||
* 16GB (4x4GB) 2133MHz DDR4 RDIMM ECC
|
||||
* 2 x 2.5" 256GB SATA Solid State Drive running RAID1
|
||||
|
||||
|
||||
### BTS Recommended Server Specifications
|
||||
* Dell R640
|
||||
* 2x Intel Xeon Gold 6148 2.40GHz 20-Core Processors. For better performance BTS recommends Intel® Xeon® Platinum 8268 2.9G, 24 Core CPU
|
||||
* 192GB RAM 2666MHz DDR4
|
||||
* 3x 480GB SSD RAID 5 HBA Minicard
|
||||
* iDRAC 9 Enterprise
|
||||
* Embedded Broadcom NIC
|
||||
* Solarflare SFN8522-R2
|
||||
* Redundant Power Supply
|
||||
|
||||
### BTS Minimum Server Specifications
|
||||
* Dell R620
|
||||
* Chassis with up to 8, 2.5” Hard Drives, up to 2 PCIe Slots (With Optional Riser)
|
||||
* 2 x Intel® E5-2670 (16 total cores/ 32 total threads)
|
||||
* 64GB total memory
|
||||
* RAID 1 for H330/H730/H730P
|
||||
* PERC H730 RAID Controller, 1GB NV Cache
|
||||
* 3 x 300GB 10K RPM SATA 6Gbps 2.5in Hot-plug Hard Drive (RAID 5)
|
||||
* iDRAC8 Enterprise, integrated Dell Remote Access Controller, Enterprise (varies by hosting company)
|
||||
* Solarflare Ultra SFN7122F Dual-Port 10GbE (10 GB connectivity requirements, copper vs optical, varies by hosting company).
|
||||
* Dual, Hot-plug, Redundant Power Supply (1+1), 750W
|
||||
* Performance BIOS Setting
|
||||
* ReadyRails Sliding Rails Without Cable Management Arm (varies by hosting company)
|
||||
|
||||
### Edge Tablet Requirements
|
||||
* Similar or better than:
|
||||
* Microsoft Surface Pro 4 Or Motion R12
|
||||
* Intel i7
|
||||
* 8 GB DDR4 RAM
|
||||
@@ -1,7 +1,6 @@
|
||||
#/bin/bash
|
||||
#python -m mkdocs build -d site
|
||||
|
||||
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
|
||||
./run_zola.sh build
|
||||
|
||||
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
|
||||
@@ -14,7 +13,3 @@ sudo mv ./public/* /var/www/documentation/
|
||||
EOF
|
||||
|
||||
rm -r public
|
||||
cd ./content/bts_spark_sdk/server/
|
||||
find . -not -name '_index.md' -delete
|
||||
cd ../..
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
|
||||
namespace bts.shell {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
@@ -48,14 +49,14 @@ namespace bts.shell {
|
||||
void _show_order_ticket(SecurityMD smd) =>
|
||||
_show_order_window(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd,
|
||||
Observable.Return(_model.factory.default_ladder_settings(smd.security)))
|
||||
Observable.Return(LadderSettings.Default))
|
||||
});
|
||||
|
||||
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
|
||||
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
|
||||
_show_order_window(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
|
||||
Observable.Return(_model.factory.default_ladder_settings(smd.security).calculator_buttons))
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ namespace bts.shell {
|
||||
_show_order_window(new OrderModificationDialog {
|
||||
DataContext = new ExistingOrderTicketVM(_model,
|
||||
Observable.Return((md,ori.order)),
|
||||
Observable.Return(_model.factory.default_ladder_settings(ori.sec.Value).calculator_buttons)
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.api.persistence;
|
||||
using bts.api.position;
|
||||
using bts.api.synthetic;
|
||||
@@ -45,41 +46,11 @@ namespace Spark {
|
||||
|
||||
[Export]
|
||||
public MarketDataRelay md_replay => context.locate<MarketDataRelay>();
|
||||
//[Export]
|
||||
//public AlgoService algo_service => context.locate<AlgoService>();
|
||||
|
||||
AlgoServiceWrapper _algo_service_wrapper;
|
||||
[Export]
|
||||
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
|
||||
public AlgoDefinitionService algo_definition_service => context.locate<AlgoDefinitionService>();
|
||||
|
||||
[Export]
|
||||
public MBOService mbo_service => context.locate<MBOService>();
|
||||
}
|
||||
|
||||
class AlgoServiceWrapper {
|
||||
public enum Mode {
|
||||
None,
|
||||
Hierarchical,
|
||||
Flat
|
||||
}
|
||||
|
||||
public AlgoServiceWrapper(ServiceContext context) {
|
||||
var flat_model_options = mbts.config.Configuration.get<bts.config.AlgoStudioFlatDefinitionModelOptions>();
|
||||
if (flat_model_options.enabled) {
|
||||
flat = context.locate<bts.api.orderman.algo.AlgoDefinitionService>();
|
||||
} else {
|
||||
hierarhical = context.locate<AlgoService>();
|
||||
}
|
||||
}
|
||||
|
||||
public AlgoService hierarhical { get; }
|
||||
public bts.api.orderman.algo.AlgoDefinitionService flat { get; }
|
||||
public Mode mode {
|
||||
get {
|
||||
if (hierarhical != null) return Mode.Hierarchical;
|
||||
if (flat != null) return Mode.Flat;
|
||||
return Mode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ namespace mbts.ui {
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
|
||||
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
@@ -147,6 +148,7 @@ namespace mbts.ui {
|
||||
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
|
||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey, Brushes.White, new Pen(Brushes.Black, 1).frozen());
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
|
||||
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
|
||||
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
|
||||
</sys:String>
|
||||
|
||||
@@ -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),
|
||||
@@ -144,19 +146,18 @@ namespace mbts.spark {
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.market_view_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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 SimpleGridSettingsDialog { 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;
|
||||
}))
|
||||
|
||||
@@ -49,13 +49,8 @@ namespace mbts.spark {
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new TimeAndSalesColumn(
|
||||
col_key
|
||||
, (r) => _get_aggressor_brush(r)
|
||||
, filter
|
||||
, filter_template
|
||||
)
|
||||
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
|
||||
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||
|
||||
@@ -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 {
|
||||
@@ -70,19 +70,18 @@ namespace mbts.spark {
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
s => s.time_and_sales_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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 SimpleGridSettingsDialog { 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}"/>
|
||||
@@ -363,6 +374,72 @@
|
||||
Height="{TemplateBinding Height}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<Border.Resources>
|
||||
<!--Leg Count States-->
|
||||
<sys:String x:Key="LegCountStates">LegCountStates</sys:String>
|
||||
<sys:String x:Key="LegCountStates.TwoOrFewer">TwoOrFewerLegsState</sys:String>
|
||||
<sys:String x:Key="LegCountStates.ThreeOrMore">ThreeOrMoreLegsState</sys:String>
|
||||
<cv:ValueConverterPipe x:Key="LegCountStateConverter">
|
||||
<cv:IntGreaterThanConverter Value="2"/>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{StaticResource LegCountStates.TwoOrFewer}" ValueForTrue="{StaticResource LegCountStates.ThreeOrMore}"/>
|
||||
</cv:ValueConverterPipe>
|
||||
<!--Persistance States-->
|
||||
<sys:String x:Key="PersistanceStates">PersistanceStates</sys:String>
|
||||
<sys:String x:Key="PersistanceStates.Unpersisted">UnpersistedState</sys:String>
|
||||
<sys:String x:Key="PersistanceStates.Persisted">PersistedState</sys:String>
|
||||
<cv:BooleanToObjectConverter x:Key="PersistanceStateConverter" ValueForFalse="{StaticResource PersistanceStates.Persisted}" ValueForTrue="{StaticResource PersistanceStates.Unpersisted}"/>
|
||||
</Border.Resources>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.LegTuples.Count, RelativeSource={RelativeSource Self}, Converter={StaticResource LegCountStateConverter}}"/>
|
||||
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.IsNew, RelativeSource={RelativeSource Self}, Converter={StaticResource PersistanceStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{StaticResource LegCountStates}">
|
||||
<VisualState Name="{StaticResource LegCountStates.TwoOrFewer}"/>
|
||||
<VisualState Name="{StaticResource LegCountStates.ThreeOrMore}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_Label" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_DropDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{StaticResource PersistanceStates}">
|
||||
<VisualState Name="{StaticResource PersistanceStates.Unpersisted}"/>
|
||||
<VisualState Name="{StaticResource PersistanceStates.Persisted}">
|
||||
<Storyboard>
|
||||
<!--Name-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_TextBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<!--Tick Factors-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Picker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<!--Pricing-->
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_DropDown" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -377,8 +454,9 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
|
||||
<TextBlock x:Name="PART_Name_Label" VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
|
||||
<TextBox
|
||||
x:Name="PART_Name_TextBox"
|
||||
Grid.Column="1"
|
||||
@@ -397,20 +475,37 @@
|
||||
</u:Behavior.BindingUpdateGroups>
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
x:Name="PART_TickFactors_Label"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks"
|
||||
IsEnabled="True"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
x:Name="PART_TickFactors_Picker"
|
||||
Grid.Column="3"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="True"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
|
||||
<TextBlock
|
||||
x:Name="PART_Pricing_Label"
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
IsEnabled="True"
|
||||
Text="Pricing"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_Pricing_DropDown"
|
||||
Grid.Column="5"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Width="90"
|
||||
IsEnabled="{TemplateBinding IsNew}"
|
||||
IsEnabled="True"
|
||||
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
|
||||
SelectedItem="{Binding price_calc}"
|
||||
>
|
||||
@@ -420,6 +515,24 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_HedgePricing_Label"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Hedge"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_HedgePricing_DropDown"
|
||||
Grid.Column="7"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
ItemsSource="{TemplateBinding HedgePricingItemsSource}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="PART_DataGrid"
|
||||
|
||||
@@ -8,6 +8,7 @@ using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -183,6 +184,29 @@ namespace mbts.ui.spark {
|
||||
public mbts.spark.ContractPickerModel ContractPickerModel { get => GetValue(ContractPickerModelProperty) as mbts.spark.ContractPickerModel; set => SetValue(ContractPickerModelProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region HedgePricingItemsSource
|
||||
public static readonly DependencyProperty HedgePricingItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
|
||||
nameof(HedgePricingItemsSource));
|
||||
|
||||
public IEnumerable HedgePricingItemsSource {
|
||||
get => GetValue(HedgePricingItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(HedgePricingItemsSourceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HedgePricing
|
||||
public static readonly DependencyProperty HedgePricingProperty =
|
||||
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
|
||||
nameof(HedgePricing),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public object HedgePricing {
|
||||
get => GetValue(HedgePricingProperty);
|
||||
set => SetValue(HedgePricingProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate() =>
|
||||
_connect_leg_tuples();
|
||||
|
||||
@@ -240,7 +264,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;
|
||||
}
|
||||
}
|
||||
@@ -251,7 +275,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactors_Picker");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
@@ -306,17 +330,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
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -75,19 +75,18 @@ namespace mbts.spark {
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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 SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
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 " +
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -32,6 +33,7 @@ namespace mbts.spark {
|
||||
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
|
||||
TickFactors _tick_factors;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
|
||||
/// <summary>
|
||||
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
|
||||
@@ -76,8 +78,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 +136,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 +179,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 +215,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,
|
||||
@@ -290,6 +296,9 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
// hedge pricing
|
||||
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
|
||||
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -297,6 +306,7 @@ namespace mbts.spark {
|
||||
foreach (var lt in leg_tuples) {
|
||||
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
|
||||
}
|
||||
mutable_strategy.HedgePricing = hedge_pricing;
|
||||
return new ExecutionStrategyImmutable(mutable_strategy);
|
||||
}
|
||||
|
||||
@@ -304,6 +314,7 @@ namespace mbts.spark {
|
||||
_synthetic = synthetic.CloneToMutable();
|
||||
_synthetic.Exchange = Venue.Bts;
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
RaisePropertyChanged(nameof(name), nameof(tick_factors));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using mbts.collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class's primary purpose is to handle async AlgoService persistence calls for the AlgoStudioVM/OrderPropertiesEditorVM.
|
||||
/// When a new definition/template item is persisted for the first time, it's corresponding viewmodel (<see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>)
|
||||
/// must be removed and replaced with a new one that has the brand new persisted id (<see cref="AlgoKey"/>). As part of its duty, this class makes sure that the new persisted items are inserted into the same index as their unpersisted predecessor.
|
||||
/// </summary>
|
||||
static class AlgoPersistenceHelper {
|
||||
public static (int? def_index, int? tmp_index) selection_snapshot(IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM selected_definition, AlgoDefinitionTemplateVM selected_template) {
|
||||
var def = selected_definition;
|
||||
var tmp = selected_template;
|
||||
int? def_index = def == null ? (int?)null : definitions.IndexOf(def);
|
||||
int? tmp_index = tmp == null ? (int?)null : tmp.owner.templates.IndexOf(tmp);
|
||||
return (def_index, tmp_index);
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_definition(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM definition) {
|
||||
int def_index = definitions.IndexOf(definition);
|
||||
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, definition.templates.AsList()), t => t.def));
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_template(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionTemplateVM template) {
|
||||
switch (template.owner.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return await persist_definition(model, definitions, template.owner);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
int def_index = definitions.IndexOf(template.owner);
|
||||
int tmp_index = template.owner.templates.IndexOf(template);
|
||||
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, def.templates.WrapSubRange(tmp_index, 1)), t => t.def));
|
||||
default: return DictionaryHelper.empty<AlgoKey, AlgoKey>();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_all(OrdersModel model, ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> definitions) =>
|
||||
await persist(model, definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
|
||||
|
||||
/// <summary>
|
||||
/// Persists each pair of definition/templates, then returns a map of non-persisted keys to persisted ones (<see cref="AlgoKey"/>, <see cref="AlgoDefinitionVM.key"/>, <see cref="AlgoDefinitionTemplateVM.key"/>).
|
||||
/// The key-map gives callers the ability to replace unpersisted items with persisted ones before the next update arrives.
|
||||
/// <para>Note: use a <see cref="mbts.utils.TableUpdateBuffer{T, TKey}"/> to suspend updates while awaiting this task; don't resume them until all unpersisted items have been replaced.
|
||||
/// <see cref="AlgoDefinitionCollection"/> caters to this pre-emptive adding behavior, in it's <see cref="TableUpdate{T, TKey}"/> callback, it checks if added items are already present, and if so, treats them as changes instead.</para>
|
||||
/// <para>PS: the reason for all this madness is so newly persisted items are inserted in the same index as their placeholder counterpart. The only way to connect the non-persisted keys with their persisted counterparts
|
||||
/// is to await each save-op individually, and build a map with the results..hence the returned key-map.</para>
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="definition_templates"></param>
|
||||
/// <returns>An AlgoKey->AlgoKey map of old placeholder ids to newly persisted ones.</returns>
|
||||
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist(OrdersModel model, IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
|
||||
var placeholder_to_persisted_keys = new Dictionary<AlgoKey, AlgoKey>();
|
||||
for (int i = 0; i < definition_templates.Count; i++) {
|
||||
(var def, var templates) = definition_templates[i];
|
||||
var persisted_def = def;
|
||||
if (def.is_dirty) {
|
||||
var def_record = await model.algo_model.save_definition(def.create_algo_def());
|
||||
if (def_record.HasValue) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
persisted_def = def.to_persisted(def_record.Value);
|
||||
foreach (var tmp in templates) {
|
||||
AlgoDefinitionTemplateVM persisted_tmp;
|
||||
var tmp_record = await model.algo_model.save_template(tmp.create(def_record.Value));
|
||||
if (tmp_record.HasValue) {
|
||||
persisted_def.templates.Add(persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key)));
|
||||
} else {
|
||||
/// If the save-op fails, the unpersisted template is cloned and added to the persisted def's template items, so the user may try to save it again without having to re-enter all the templated-values.
|
||||
/// Q: "why don't we just re-add the same template to the persisted def, the save-op failed, so nothing changed...why do we need a new one?"
|
||||
/// A: "A new template vm must be created because its owner has changed (it's a readonly prop, <see cref="AlgoDefinitionTemplateVM.owner"/>)."
|
||||
persisted_def.templates.Add(persisted_tmp = new AlgoDefinitionTemplateVM(persisted_def, model, tmp.create_def_data()));
|
||||
}
|
||||
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
|
||||
}
|
||||
persisted_def.connect();
|
||||
if (def.key.mode == AlgoKey.Mode.Unpersisted) {
|
||||
definition_templates[i] = (persisted_def, persisted_def.templates);
|
||||
} else {
|
||||
definition_templates.Add((persisted_def, persisted_def.templates));
|
||||
}
|
||||
placeholder_to_persisted_keys[def.key] = persisted_def.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// so the def isn't dirty, but it still might have dirty templates
|
||||
using (persisted_def.buffer_updates()) {
|
||||
for (int j = 0; j < templates.Count; j++) {
|
||||
var tmp = templates[j];
|
||||
if (!tmp.is_dirty)
|
||||
continue;
|
||||
var tmp_record = await model.algo_model.save_template(tmp.create());
|
||||
if (tmp_record.HasValue) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
var persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key));
|
||||
templates[j] = persisted_tmp;
|
||||
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return placeholder_to_persisted_keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,11 @@
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:StateManager.VisualState="{Binding content_source.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
|
||||
u:StateManager.VisualState="{Binding selected_definition.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
@@ -33,9 +30,6 @@
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<cv:ValueMap x:Key="AlgoTemplateFavoriteToggleButtonToolTipConverter" Default="Add to Favorites">
|
||||
<cv:KeyValue Key="{u:True}" Value="Remove from Favorites"/>
|
||||
</cv:ValueMap>
|
||||
<sys:String x:Key="AlgoAbbreviationActiveState">AbbreviationActive</sys:String>
|
||||
<sys:String x:Key="AlgoAbbreviationInactiveState">AbbreviationInactive</sys:String>
|
||||
<cv:ValueMap x:Key="AlgoAbbreviationVisualStateConverter" Default="{StaticResource AlgoAbbreviationActiveState}">
|
||||
@@ -130,70 +124,8 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AlgoTemplateFavoriteToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="LightGray"/>
|
||||
<Setter Property="BorderBrush" Value="Black"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="IsThreeState" Value="False"/>
|
||||
<Setter Property="IsChecked" Value="{Binding is_favorite}"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="UseLayoutRounding" Value="True"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="ToolTip" Value="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static uag:AlgoTemplateToolBar.FavoriteToggleButtonToolTipConverter}}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
|
||||
<Ellipse x:Name="ellipse" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}" Width="0" Height="0"/>
|
||||
<Image SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}" MaxHeight="13" MaxWidth="13">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Geometry="{Binding Source={StaticResource ResourceKey={x:Static u:Icons.StarGeometryKey}}}" Brush="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" >
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen
|
||||
Brush="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Thickness="{Binding BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Foreground" Value="Gold"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="16" Duration="0:0:0.03" />
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="16" Duration="0:0:0.03" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="0" Duration="0:0:0.0" />
|
||||
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.0" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext.content_source, RelativeSource={RelativeSource AncestorType=usp:AlgoStudioView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
<Setter Property="DropDownStyleKey" Value="{x:Static usp:AlgoContentTemplateSelector.DefinitionDropDownStyleKey}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
@@ -221,94 +153,26 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TreeView
|
||||
<ListBox
|
||||
x:Name="_tree_view"
|
||||
u:TreeViewEx.SelectedItemChangedHandler="{Binding}"
|
||||
ItemsSource="{Binding definitions_view}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
MinWidth="180"
|
||||
Background="Transparent"
|
||||
Style="{StaticResource AlgoTemplateTreeViewStyle}"
|
||||
SelectedItem="{Binding selected_definition}"
|
||||
>
|
||||
<TreeView.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.AddNewItemCommand}" Executed="{x:Static u:EditableCollectionViewEx.AddNewItemExecuted}" CanExecute="{x:Static u:EditableCollectionViewEx.AddNewItemCanExecute}"/>
|
||||
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.CommitAnyCommand}" Executed="{x:Static u:EditableCollectionViewEx.CommitAnyExecuted}"/>
|
||||
</TreeView.CommandBindings>
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type sp:AlgoDefinitionVM}" ItemsSource="{Binding templates_view}">
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="badge" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="badge"
|
||||
Fill="{Binding badge_fill}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
Visibility="{DynamicResource ResourceKey={x:Static usp:AlgoStudioView.BadgesInTreeViewVisibilityKey}}"
|
||||
/>
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
|
||||
</Grid>
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}" Margin="-6,0,0,0">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="favorite_toggle" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton x:Name="favorite_toggle" Grid.Column="1" Margin="4,0,0,0" Style="{StaticResource AlgoTemplateFavoriteToggleButtonStyle}"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}" ToolTip="Double click to set as default"/>
|
||||
</Grid>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
|
||||
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
<DataTrigger.Binding>
|
||||
<MultiBinding Converter="{StaticResource OrConverter}">
|
||||
<Binding Path="is_dirty"/>
|
||||
<Binding Path="is_persisted" Converter="{StaticResource NotConverter}"/>
|
||||
</MultiBinding>
|
||||
</DataTrigger.Binding>
|
||||
<DataTrigger Binding="{Binding is_dirty}" Value="True">
|
||||
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</TreeView.Resources>
|
||||
</TreeView>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
|
||||
<Grid x:Name="edit_pane">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -430,7 +294,7 @@
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding content_source.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
/>
|
||||
<Grid x:Name="abbreviation_root" Grid.Column="2">
|
||||
<Grid.ToolTip>
|
||||
@@ -452,7 +316,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding content_source.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
|
||||
@@ -478,8 +342,8 @@
|
||||
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
|
||||
<usp:OrderTypeItem
|
||||
Margin="2,0"
|
||||
BadgeFill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding content_source.algo_root.display_name}"
|
||||
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding selected_definition.algo_root.display_name}"
|
||||
/>
|
||||
<TextBlock>).</TextBlock>
|
||||
</StackPanel>
|
||||
@@ -496,7 +360,7 @@
|
||||
x:Name="default_badge"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Fill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -506,7 +370,7 @@
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="{StaticResource Margin.LP2}"
|
||||
IsChecked="{Binding content_source.custom_badge_color.enabled}"
|
||||
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
|
||||
GroupName="Badge"
|
||||
>
|
||||
<RadioButton.ToolTip>
|
||||
@@ -526,7 +390,7 @@
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="3,0"
|
||||
Fill="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
|
||||
@@ -541,7 +405,7 @@
|
||||
<Button
|
||||
x:Name="custom_badge_color_picker_button"
|
||||
Grid.Column="1"
|
||||
Background="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
||||
/>
|
||||
@@ -549,8 +413,8 @@
|
||||
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
||||
<cp:ColorPickerControl
|
||||
Background="Transparent"
|
||||
Palettes="{Binding DataContext.content_source.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.content_source.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
/>
|
||||
</Border>
|
||||
</Popup>
|
||||
@@ -559,7 +423,7 @@
|
||||
</Grid>
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding content_source}"
|
||||
DataContext="{Binding selected_definition}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
@@ -569,10 +433,10 @@
|
||||
<StackPanel.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
|
||||
</StackPanel.Margin>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@@ -580,4 +444,4 @@
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -1,43 +1,24 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using System;
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoStudioView : UserControl {
|
||||
public const string BadgesInTreeViewVisibilityKey = nameof(BadgesInTreeViewVisibilityKey);
|
||||
public static readonly IValueConverter EditPaneVisualStateConverter =
|
||||
ValueConverter.create(obj => obj == null ? VisualStates.Inactive : VisualStates.Active);
|
||||
|
||||
IAlgoStudioViewController _controller;
|
||||
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
set_controller();
|
||||
// local methods
|
||||
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
|
||||
PopupViewController.attach(custom_badge_color_picker_popup, custom_badge_color_picker_button);
|
||||
this.ConnectDataContext<AlgoStudioVM>(vm => vm.request_view_focus_name += focus_name, vm => vm.request_view_focus_name -= focus_name);
|
||||
void focus_name(AlgoDefinitionVM target) =>
|
||||
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
|
||||
}
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
}
|
||||
|
||||
public void add_definition() => _controller.add_definition();
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) => _controller.add_definition_clone(src_definition);
|
||||
public void add_template(AlgoDefinitionVM definition) => _controller.add_template(definition);
|
||||
public void rename_definition(AlgoDefinitionVM definition) => _controller.rename_definition(definition);
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) => _controller.rename_template(template);
|
||||
public void select_definition(AlgoDefinitionVM definition) => _controller.select_definition(definition);
|
||||
public void select_template(AlgoDefinitionTemplateVM template) => _controller.select_template(template);
|
||||
public IDisposable preserve_selected_definition() => _controller.preserve_selected_definition();
|
||||
|
||||
void _end_edit_executed(object s, ExecutedRoutedEventArgs e) =>
|
||||
_controller.end_edit(e);
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class EditPane {
|
||||
public const string GroupName = nameof(EditPane);
|
||||
@@ -75,14 +56,6 @@ namespace mbts.ui.spark {
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class TreeViewBadge {
|
||||
public const string GroupName = nameof(TreeViewBadge);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
|
||||
public static class FavoriteToggleButton {
|
||||
public const string GroupName = nameof(FavoriteToggleButton);
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
interface IAlgoStudioViewController : IDisposable {
|
||||
void add_definition();
|
||||
void add_definition_clone(AlgoDefinitionVM src_definition);
|
||||
void add_template(AlgoDefinitionVM definition);
|
||||
void rename_definition(AlgoDefinitionVM definition);
|
||||
void rename_template(AlgoDefinitionTemplateVM template);
|
||||
void end_edit(ExecutedRoutedEventArgs e);
|
||||
void select_definition(AlgoDefinitionVM definition);
|
||||
void select_template(AlgoDefinitionTemplateVM template);
|
||||
IDisposable preserve_selected_definition();
|
||||
}
|
||||
|
||||
class AlgoStudioViewControllerFactory {
|
||||
static readonly IAlgoStudioViewController _empty = new EmptyController();
|
||||
|
||||
public static IAlgoStudioViewController create(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) =>
|
||||
vm == null || tree_view == null || name_text_box == null || color_picker_button == null || color_picker_popup == null ?
|
||||
_empty : new Controller(vm, tree_view, name_text_box, color_picker_button, color_picker_popup);
|
||||
|
||||
/// <summary>
|
||||
/// This wrapper class is returned by <see cref="create(AlgoStudioVM, TreeView)"/> when the vm or tree_view is null.
|
||||
/// This further separates the view from the controller-logic; it doesn't even need to check for null when accessing <see cref="IAlgoStudioViewController"/> methods.
|
||||
/// </summary>
|
||||
class EmptyController : IAlgoStudioViewController {
|
||||
public void add_definition() { }
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) { }
|
||||
public void add_template(AlgoDefinitionVM definition) { }
|
||||
public void rename_definition(AlgoDefinitionVM definition) { }
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) { }
|
||||
public void end_edit(ExecutedRoutedEventArgs e) { }
|
||||
public void select_definition(AlgoDefinitionVM definition) { }
|
||||
public void select_template(AlgoDefinitionTemplateVM template) { }
|
||||
public IDisposable preserve_selected_definition() => Disposable.empty;
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
class Controller : IAlgoStudioViewController {
|
||||
readonly AlgoStudioVM _vm;
|
||||
readonly TreeView _tree_view;
|
||||
readonly TextBox _name_text_box;
|
||||
IDisposable _dispose;
|
||||
|
||||
public Controller(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) {
|
||||
_vm = vm;
|
||||
_tree_view = tree_view;
|
||||
_name_text_box = name_text_box;
|
||||
_dispose = PopupViewController.attach(color_picker_popup, color_picker_button);
|
||||
}
|
||||
|
||||
public void add_definition() {
|
||||
_vm.add_new_definition();
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void add_definition_clone(AlgoDefinitionVM src_definition) {
|
||||
_vm.add_new_definition_clone(src_definition);
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void add_template(AlgoDefinitionVM def) {
|
||||
var new_tmp = def.template_factory();
|
||||
def.templates.Add(new_tmp);
|
||||
_begin_edit();
|
||||
}
|
||||
|
||||
public void end_edit(ExecutedRoutedEventArgs e) {
|
||||
if (e.OriginalSource is EditableTextBlock etb) {
|
||||
etb.end_edit();
|
||||
_get_container(etb.DataContext)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void rename_definition(AlgoDefinitionVM definition) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(definition) is TreeViewItem tvi) {
|
||||
var etb = tvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
|
||||
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void rename_template(AlgoDefinitionTemplateVM template) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem tvi) {
|
||||
if (tvi.ItemContainerGenerator.ContainerFromItem(template) is TreeViewItem ttvi) {
|
||||
var etb = ttvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
|
||||
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void select_definition(AlgoDefinitionVM definition) =>
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(definition)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
|
||||
public void select_template(AlgoDefinitionTemplateVM template) {
|
||||
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem def_view) {
|
||||
def_view.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
def_view.ItemContainerGenerator.ContainerFromItem(template)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable preserve_selected_definition() =>
|
||||
Disposable.Create(() => {
|
||||
var name = _vm.selected_definition.name;
|
||||
if (_vm.definitions.TryGetFirst(d => d.name == name, out AlgoDefinitionVM def))
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(def)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
|
||||
});
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
void _begin_edit() =>
|
||||
WPFHelper.push_frame_until(() => Keyboard.Focus(_name_text_box) == _name_text_box);
|
||||
|
||||
TreeViewItem _get_container(object obj) =>
|
||||
obj is AlgoDefinitionTemplateVM t ? _get_container(t) :
|
||||
obj is AlgoDefinitionVM d ? _get_container(d) : null;
|
||||
|
||||
TreeViewItem _get_container(AlgoDefinitionVM d) =>
|
||||
_tree_view.ItemContainerGenerator.ContainerFromItem(d) as TreeViewItem;
|
||||
|
||||
TreeViewItem _get_container(AlgoDefinitionTemplateVM t) =>
|
||||
_get_container(t.owner) is TreeViewItem dview ? dview.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,35 +62,19 @@ namespace mbts.spark {
|
||||
_vm = new AlgoStudioVM(_model);
|
||||
_vm.load_from(_state);
|
||||
_view = new AlgoStudioView { DataContext = _vm };
|
||||
_view.Resources[AlgoStudioView.BadgesInTreeViewVisibilityKey] = _ui_configuration.show_badges_in_tree_view ? Visibility.Visible : Visibility.Collapsed;
|
||||
_resources = new CompositeDisposable();
|
||||
// observable sources
|
||||
var content_src = _vm.observe_selected().Select(_ => _vm.content_source);
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, new DelegateCommand(_view.add_definition)));
|
||||
// add definition clone
|
||||
var add_definition_clone_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.DocumentCopy, d => $"Clone '{d?.name}'", d => d != null, _view.add_definition_clone);
|
||||
_commands.Add(add_definition_clone_spark_cmd);
|
||||
// add template
|
||||
var add_template_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.AddFile, d => $"Add '{d?.name}' Template", d => d != null, _view.add_template) {
|
||||
show_in_toolbar = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar) == bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar,
|
||||
show_in_context_menu = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu) == bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu
|
||||
};
|
||||
_commands.Add(add_template_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(add_definition_clone_spark_cmd, add_template_spark_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
|
||||
_commands.Add(clone_selected_definition_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
|
||||
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
|
||||
// save
|
||||
if (_ui_configuration.can_save_specific) {
|
||||
var content_is_dirty_src = _vm.observe_selected().SelectManyInSeries(t => {
|
||||
if (t.template != null) {
|
||||
return t.template.observe_is_dirty();
|
||||
} 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();
|
||||
else if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
|
||||
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
_vm.save_definition(d).ignore();
|
||||
}, content_is_dirty_src));
|
||||
}
|
||||
@@ -101,50 +85,39 @@ namespace mbts.spark {
|
||||
// undo/redo
|
||||
if (_ui_configuration.can_undo_redo) {
|
||||
// undo
|
||||
var undo_cmd = new ObservantCommand(() => _vm.content_source?.try_undo());
|
||||
var undo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_undo());
|
||||
var undo_spark_cmd = new SparkCommandCommand("Undo", Icons.UndoCircular, undo_cmd);
|
||||
var can_undo_source = _vm.observe_selected().SelectManyInSeries(tup => {
|
||||
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
|
||||
return content != null ?
|
||||
var can_undo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
|
||||
def != null ?
|
||||
Observable.Create<bool>(o => {
|
||||
content.can_undo_changed += notify;
|
||||
def.can_undo_changed += notify;
|
||||
notify();
|
||||
return Disposable.Create(() => content.can_undo_changed -= notify);
|
||||
void notify() => o.OnNext(content.can_undo);
|
||||
return Disposable.Create(() => def.can_undo_changed -= notify);
|
||||
void notify() => o.OnNext(def.can_undo);
|
||||
}) :
|
||||
Observable.Return(false);
|
||||
});
|
||||
Observable.Return(false));
|
||||
_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_cmd = new ObservantCommand(() => _vm.selected_definition?.try_redo());
|
||||
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
|
||||
var can_redo_source = _vm.observe_selected().SelectManyInSeries(tup => {
|
||||
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
|
||||
return content != null ?
|
||||
var can_redo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
|
||||
def != null ?
|
||||
Observable.Create<bool>(o => {
|
||||
content.can_redo_changed += notify;
|
||||
def.can_redo_changed += notify;
|
||||
notify();
|
||||
return Disposable.Create(() => content.can_redo_changed -= notify);
|
||||
void notify() => o.OnNext(content.can_redo);
|
||||
return Disposable.Create(() => def.can_redo_changed -= notify);
|
||||
void notify() => o.OnNext(def.can_redo);
|
||||
}) :
|
||||
Observable.Return(false);
|
||||
});
|
||||
Observable.Return(false));
|
||||
_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 => {
|
||||
if (value is AlgoDefinitionTemplateVM t) {
|
||||
_vm.remove_template(t).ignore();
|
||||
} else if (value is AlgoDefinitionVM d) {
|
||||
if (value is AlgoDefinitionVM d) {
|
||||
string prompt; MessageBoxImage icon;
|
||||
if (_model.algo_model.templates_enabled() && d.templates.Count > 0) {
|
||||
prompt = $"Deleting this definition ({d.name}) will also delete its templates ({d.templates.Count}).\n\nDo you wan to Continue?";
|
||||
icon = MessageBoxImage.Warning;
|
||||
} else if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
prompt = $"Are you sure you want to delete '{d.name}?'";
|
||||
icon = MessageBoxImage.Question;
|
||||
} else {
|
||||
@@ -164,7 +137,7 @@ namespace mbts.spark {
|
||||
}
|
||||
});
|
||||
_commands.Add(remove_spark_cmd);
|
||||
_resources.Add(content_src.OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
}
|
||||
|
||||
void _dispose_core() {
|
||||
|
||||
@@ -1,31 +1,57 @@
|
||||
using bts.api.orderman;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
struct AlgoData : IEquatable<AlgoData> {
|
||||
public readonly CustomOrderDefinitionImmutable value;
|
||||
public readonly string name;
|
||||
public readonly AlgoKey key;
|
||||
|
||||
public AlgoData(CustomOrderDefinitionImmutable value)
|
||||
: this(value, value.display_name(), OrderDataHelper.get_shallow_factory_key(value)) { }
|
||||
|
||||
public AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
|
||||
AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public AlgoData with(CustomOrderDefinitionImmutable? value = null, string name = null) =>
|
||||
new AlgoData(value ?? this.value, name ?? this.name, key);
|
||||
/// <summary>
|
||||
/// This is the only <see cref="AlgoData"/> factory method that allows callers to explicitly specify a name.
|
||||
/// It should only be used within the context of creating, or renaming, studio algos.
|
||||
/// </summary>
|
||||
public static AlgoData create(CustomOrderDefinitionImmutable def, string name, AlgoKey key) =>
|
||||
new AlgoData(def, name, key);
|
||||
|
||||
/// <summary>
|
||||
/// Reconstructs <see cref="AlgoData"/> from a <see cref="CustomOrderDefinitionImmutable"/>, with a special caveat in the case of <see cref="CompositeOrderDefinition"/>:
|
||||
/// a fallback/default name is used instead of the composite algo's true name, which must be obtained from the <see cref="IAlgoStudioModel.definitions"/> stream.
|
||||
/// </summary>
|
||||
public static AlgoData from_custom_def(CustomOrderDefinitionImmutable def) {
|
||||
switch (def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite:
|
||||
string name = $"({OrderDataHelper.abbreviate_algo_type(def.CustomCase)}){OrderDataHelper.abbreviate_algo_type(def.Composite.Definition.CustomCase)}";
|
||||
return new AlgoData(def.Composite.Definition, name, AlgoKey.from_composite_key(def.Composite.AlgoTemplateKey));
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
return new AlgoData(def,def.Dynamic.DynamicOrderType, AlgoKey.from_dynamic_type(def.Dynamic.DynamicOrderType));
|
||||
default:
|
||||
return new AlgoData(def, def.CustomCase.display_name(), AlgoKey.from_known_type(def.CustomCase));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData from_record(AlgoDefinitionRecordImmutable record) =>
|
||||
new AlgoData(record.Algo.Definition, record.Algo.Name, AlgoKey.from_composite_key(record.Key));
|
||||
|
||||
public static AlgoData create_unpersisted(CustomOrderDefinitionImmutable custom_def, string name) =>
|
||||
new AlgoData(custom_def, name, AlgoKey.create_unpersisted());
|
||||
|
||||
public bool Equals(AlgoData other) =>
|
||||
key == other.key &&
|
||||
name == other.name &&
|
||||
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.Equals(value, other.value);
|
||||
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer.Equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() =>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
[ProtoContract]
|
||||
struct AlgoID : IEquatable<AlgoID> {
|
||||
[ProtoMember(1)]
|
||||
@@ -14,6 +17,11 @@ namespace mbts.spark {
|
||||
[ProtoMember(3)]
|
||||
readonly uint? _persisted_key;
|
||||
|
||||
public static readonly AlgoID[] exchange_algo_keys = utilities.OrderDataHelper.outright_order_types
|
||||
.Concat(new CustomOrderDefinition.CustomOneofCase[] { CustomOrderDefinition.CustomOneofCase.Spreader })
|
||||
.Select(cooc => new AlgoID(cooc))
|
||||
.ToArray();
|
||||
|
||||
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
|
||||
_factory_key = factory_key;
|
||||
_dynamic_key = null;
|
||||
@@ -32,24 +40,31 @@ namespace mbts.spark {
|
||||
_persisted_key = persisted_key;
|
||||
}
|
||||
|
||||
public static AlgoID from_order_def(OrderDefinitionImmutable order_def) {
|
||||
switch (order_def.CustomDef.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(order_def.CustomDef.CustomCase);
|
||||
public static AlgoID from_custom_def(CustomOrderDefinitionImmutable custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(custom_def.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(custom_def.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(custom_def.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (key.factory.structure.structure.Count > 0)
|
||||
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
|
||||
break;
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
case AlgoKey.Mode.Factory: return new AlgoID(key.factory);
|
||||
case AlgoKey.Mode.Dynamic: return new AlgoID(key.dynamic_type);
|
||||
case AlgoKey.Mode.Persisted: return new AlgoID(key.persisted);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
public static AlgoKey to_algo_key(AlgoID id) {
|
||||
if (id._factory_key.HasValue)
|
||||
return AlgoKey.from_known_type(id._factory_key.Value);
|
||||
if (id._persisted_key.HasValue)
|
||||
return AlgoKey.from_composite_key(id._persisted_key.Value);
|
||||
if (id._dynamic_key != null)
|
||||
return AlgoKey.from_dynamic_type(id._dynamic_key);
|
||||
throw new ArgumentException("ID is empty.", nameof(id));
|
||||
}
|
||||
|
||||
public bool Equals(AlgoID other) =>
|
||||
@@ -58,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(
|
||||
@@ -70,55 +85,93 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
|
||||
struct AlgoKey : IEquatable<AlgoKey> {
|
||||
readonly AlgoPersistedKey? _persisted_key;
|
||||
readonly AlgoNameStructureKey? _factory_key;
|
||||
readonly uint? _composite_key;
|
||||
readonly Guid? _unpersisted;
|
||||
readonly string _dynamic_type;
|
||||
readonly CustomOrderDefinition.CustomOneofCase? _factory_type;
|
||||
|
||||
public AlgoKey(Guid id_guid) {
|
||||
_persisted_key = null;
|
||||
_factory_key = null;
|
||||
AlgoKey(Guid id_guid) {
|
||||
_composite_key = null;
|
||||
_unpersisted = id_guid;
|
||||
_dynamic_type = null;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_root_algo_type)
|
||||
: this(new AlgoNameStructureKey(factory_root_algo_type)) { }
|
||||
|
||||
public AlgoKey(AlgoNameStructureKey factory_key) {
|
||||
_persisted_key = null;
|
||||
_factory_key = factory_key;
|
||||
public AlgoKey(uint composite_key) {
|
||||
_composite_key = composite_key;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = null;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(AlgoPersistedKey persisted_key) {
|
||||
_persisted_key = persisted_key;
|
||||
_factory_key = null;
|
||||
public AlgoKey(string dynamic_type) {
|
||||
_composite_key = null;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = dynamic_type;
|
||||
_factory_type = null;
|
||||
}
|
||||
|
||||
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_type) {
|
||||
_composite_key = null;
|
||||
_unpersisted = null;
|
||||
_dynamic_type = null;
|
||||
_factory_type = factory_type;
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
None,
|
||||
Persisted,
|
||||
Unpersisted,
|
||||
Dynamic,
|
||||
Factory
|
||||
}
|
||||
|
||||
public bool is_persisted => mode == Mode.Persisted;
|
||||
|
||||
public AlgoPersistedKey persisted => _persisted_key.Value;
|
||||
public AlgoNameStructureKey factory => _factory_key ?? new AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase.None);
|
||||
public uint persisted => _composite_key.Value;
|
||||
public Guid unpersisted => _unpersisted.Value;
|
||||
public CustomOrderDefinition.CustomOneofCase factory => _factory_type.Value;
|
||||
public string dynamic_type => _dynamic_type;
|
||||
|
||||
public Mode mode {
|
||||
get {
|
||||
if (_persisted_key.HasValue) return Mode.Persisted;
|
||||
if (_factory_type.HasValue) return Mode.Factory;
|
||||
if (_composite_key.HasValue) return Mode.Persisted;
|
||||
if (_dynamic_type != null) return Mode.Dynamic;
|
||||
if (_unpersisted.HasValue) return Mode.Unpersisted;
|
||||
if (_factory_key.HasValue) return Mode.Factory;
|
||||
return Mode.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoKey create_unpersisted() =>
|
||||
new AlgoKey(Guid.NewGuid());
|
||||
|
||||
public static AlgoKey from_custom_def(CustomOrderDefinitionImmutable custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoKey(custom_def.Dynamic.DynamicOrderType);
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoKey(custom_def.Composite.AlgoTemplateKey);
|
||||
default: return new AlgoKey(custom_def.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoKey from_known_type(CustomOrderDefinition.CustomOneofCase known_type) {
|
||||
if (known_type == CustomOrderDefinition.CustomOneofCase.Dynamic)
|
||||
throw new ArgumentException(nameof(known_type), "Potential loss of data: no dynamic type name specified.");
|
||||
if (known_type == CustomOrderDefinition.CustomOneofCase.Composite)
|
||||
throw new ArgumentException(nameof(known_type), "Potential loss of data: no composite id specified.");
|
||||
return new AlgoKey(known_type);
|
||||
}
|
||||
|
||||
public static AlgoKey from_dynamic_type(string dynamic_type) {
|
||||
if (string.IsNullOrEmpty(dynamic_type))
|
||||
throw ExceptionBuilder.Argument.null_or_empty(nameof(dynamic_type));
|
||||
return new AlgoKey(dynamic_type);
|
||||
}
|
||||
|
||||
public static AlgoKey from_composite_key(uint composite_key) =>
|
||||
new AlgoKey(composite_key);
|
||||
|
||||
public bool Equals(AlgoKey other) {
|
||||
Mode m = mode;
|
||||
if (m == other.mode) {
|
||||
@@ -126,6 +179,7 @@ namespace mbts.spark {
|
||||
case Mode.Persisted: return persisted == other.persisted;
|
||||
case Mode.Unpersisted: return unpersisted == other.unpersisted;
|
||||
case Mode.Factory: return factory == other.factory;
|
||||
case Mode.Dynamic: return dynamic_type == other.dynamic_type;
|
||||
case Mode.None: return true;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +194,7 @@ namespace mbts.spark {
|
||||
case Mode.Persisted: hash = persisted.GetHashCode(); phase_mult = 2; break;
|
||||
case Mode.Unpersisted: hash = unpersisted.GetHashCode(); phase_mult = 4; break;
|
||||
case Mode.Factory: hash = factory.GetHashCode(); phase_mult = 3; break;
|
||||
case Mode.Dynamic: hash = dynamic_type.GetHashCode(); phase_mult = 5; break;
|
||||
default: return 0;
|
||||
}
|
||||
return EqualityComparer.phase_hash(hash, phase_mult);
|
||||
@@ -150,4 +205,4 @@ namespace mbts.spark {
|
||||
|
||||
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ namespace bts.persist.algo {
|
||||
public bool is_persisted => mode != Mode.None;
|
||||
|
||||
public mbts.spark.AlgoKey to_algo_key() =>
|
||||
new mbts.spark.AlgoKey(this);
|
||||
new mbts.spark.AlgoKey(_id_uint ?? 0);
|
||||
|
||||
public bool Equals(AlgoPersistedKey other) {
|
||||
Mode m = mode;
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
struct AlgoNameStructureKey : IEquatable<AlgoNameStructureKey> {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, AlgoStructureKey structure) {
|
||||
this.name = name;
|
||||
this.structure = structure;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoNameStructureKey other) => name == other.name && structure == other.structure;
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(EqualityComparer.safe_get_hash_code(name), structure.GetHashCode());
|
||||
public static bool operator ==(AlgoNameStructureKey a, AlgoNameStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) {
|
||||
if (_structure == null) {
|
||||
return other._structure == null;
|
||||
} else {
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
return other._structure != null && _structure.Length == other._structure.Length && _structure.SequenceEqual(other._structure);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoStructureKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_structure == null ? 0 : EqualityComparer.aggregate_hash(_structure);
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
|
||||
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-345
@@ -1,345 +0,0 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoStudioDefinitionTemplateModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
readonly ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata> _metadata_demuxer = new ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata>();
|
||||
readonly TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey> _definition_templates_demuxer;
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
public AlgoStudioDefinitionTemplateModel(AlgoService service, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
|
||||
_service = service;
|
||||
metadata_service = meta;
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
_definition_templates_demuxer = new TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey>(t => t.key, t => t.definition_key);
|
||||
definitions = _service.subscribei_algos()
|
||||
.Select(u => new Update<AlgoData, AlgoKey>(
|
||||
u.all.Select(_Utils.definition_data).ToList(),
|
||||
u.added.Select(_Utils.definition_data).ToList(),
|
||||
u.changes.Select(ch => new Change<AlgoData>(_Utils.definition_data(ch.Old), _Utils.definition_data(ch.New))).ToList(),
|
||||
u.removed.Select(d => _Utils.definition_data(d)).ToList(),
|
||||
def => def.key))
|
||||
.Table()
|
||||
.Cache();
|
||||
templates = _service.subscribei_algo_templates()
|
||||
.Select(u => new Update<AlgoTemplateData, AlgoKey>(
|
||||
u.all.Select(_Utils.template_data).ToList(),
|
||||
u.added.Select(_Utils.template_data).ToList(),
|
||||
u.changes.Select(ch => new Change<AlgoTemplateData>(_Utils.template_data(ch.Old), _Utils.template_data(ch.New))).ToList(),
|
||||
u.removed.Select(_Utils.template_data).ToList(),
|
||||
def => def.key))
|
||||
.Table()
|
||||
.Cache();
|
||||
_dispose = new CompositeDisposable(
|
||||
definitions.OnDispatcher().Subscribe(t => _algos = t.state),
|
||||
templates.OnDispatcher().Subscribe(t => _algo_templates = t.state),
|
||||
templates.Subscribe(_definition_templates_demuxer),
|
||||
metadata_service.subscribe_metadata().Subscribe(_metadata_demuxer));
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition) =>
|
||||
_definition_templates_demuxer.subscribe(definition);
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
if (definition_data.key.is_persisted) {
|
||||
// TODO: implement update op when backend ready
|
||||
} else {
|
||||
// create op
|
||||
return await _create_definition(new CompositeAlgoDefinitionImmutable(_Utils.to_definition(definition_data)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public async Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) {
|
||||
if (template_data.key.is_persisted) {
|
||||
// update op
|
||||
return await _update_template(_Utils.to_recordi(template_data));
|
||||
} else {
|
||||
// create op
|
||||
return await _create_template(_Utils.to_templatei(template_data));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The api is not used here (there are some unresolved dependency issues with definitions on the backend).
|
||||
/// The definition is marked as hidden in the AlgoMetadata stored on the server.
|
||||
/// <para/>
|
||||
/// For implementation details, see: <see cref="IAlgoDefinitionMetadataService"/>, <see cref="AlgoTemplateMetadataService"/>
|
||||
/// </summary>
|
||||
public async Task<bool> remove_definition(AlgoKey key) {
|
||||
if (_algos.TryGetValue(key, out AlgoData curr)) {
|
||||
if (_definition_templates_demuxer.try_get_values(key, out IReadOnlyDictionary<AlgoKey, AlgoTemplateData> tmps)) {
|
||||
foreach (var t in tmps.Values)
|
||||
await remove_template(t.key);
|
||||
}
|
||||
await metadata_service.hide_definition(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> remove_template(AlgoKey template_key) {
|
||||
var req = new AlgoServiceRemoveRequest();
|
||||
_Utils.set_key(ref req, template_key);
|
||||
var reply = await _safe_execute(() => _service.remove_template_async(req), ActionMetadata.remove_template);
|
||||
return reply.success && reply.value.Success;
|
||||
}
|
||||
|
||||
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<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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition, uint user_id) {
|
||||
(var result, var success) = await _error_reporter.safe_execute(() => _service.create_composite_algo_async(
|
||||
new CompositeAlgoCreateRequest {
|
||||
NewAlgo = definition.CloneToMutable(),
|
||||
UserId = user_id
|
||||
}), r => r.Success, r => r.ErrMsg, "create definition");
|
||||
return success ? _Utils.definition_data(new CompositeAlgoRecordImmutable(result.Record)) : (AlgoData?)null;
|
||||
}
|
||||
|
||||
async Task<Result<T>> _safe_execute<T>(Func<Task<Result<T>>> execute, IActionMetadata<T> am) {
|
||||
try {
|
||||
var result = await execute();
|
||||
if (!result.success) {
|
||||
_report_error(am.present_progressive_desc, result.error_message);
|
||||
} else if (!am.get_success(result.value)) {
|
||||
_report_error(am.present_progressive_desc, am.get_error_msg(result.value));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
_report_error(am.present_progressive_desc, ex.ToString());
|
||||
}
|
||||
return new Result<T>(bts.api.ApiError.UnexpectedResponse);
|
||||
}
|
||||
|
||||
void _report_error(string present_progressive_desc, string msg) =>
|
||||
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
|
||||
|
||||
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; }
|
||||
string present_progressive_desc { get; }
|
||||
}
|
||||
|
||||
interface IActionMetadata<TReply> : IActionMetadata {
|
||||
bool get_success(TReply reply);
|
||||
string get_error_msg(TReply reply);
|
||||
}
|
||||
|
||||
static class ActionMetadata {
|
||||
public const string
|
||||
CreateDefinition = "create definition",
|
||||
UpdateDefinition = "update definition",
|
||||
CreateTemplate = "create template",
|
||||
UpdateTemplate = "update template";
|
||||
public static readonly IActionMetadata<AlgoServiceReply> update_template = new Impl<AlgoServiceReply>(_update, _template, r => r.Success, r => r.ErrMsg.ToString());
|
||||
public static readonly IActionMetadata<AlgoServiceReply> remove_template = new Impl<AlgoServiceReply>(_delete, _template, r => r.Success, r => r.ErrMsg.ToString());
|
||||
public static readonly IActionMetadata<TemplateCreateResult> create_template = new Impl<TemplateCreateResult>(_create, _template, r => r.Success, r => r.ErrMsg);
|
||||
public static readonly IActionMetadata<CompositeAlgoCreateResult> create_definition = new Impl<CompositeAlgoCreateResult>(_create, _definition, r => r.Success, r => r.ErrMsg);
|
||||
|
||||
static readonly DescTuple _create = new DescTuple("create", "creating"), _update = new DescTuple("update", "updating"), _delete = new DescTuple("delete", "deleting");
|
||||
static readonly string _definition = "definition", _template = "template";
|
||||
|
||||
class Impl<T> : IActionMetadata<T> {
|
||||
readonly DescTuple _desc;
|
||||
readonly string _subject;
|
||||
readonly Func<T, bool> _get_success;
|
||||
readonly Func<T, string> _get_error_msg;
|
||||
|
||||
public Impl(DescTuple desc, string subject, Func<T, bool> get_success, Func<T, string> get_error_msg) {
|
||||
_desc = desc;
|
||||
_subject = subject;
|
||||
_get_success = get_success;
|
||||
_get_error_msg = get_error_msg;
|
||||
}
|
||||
public string desc => _desc.desc;
|
||||
public string present_progressive_desc => _desc.present_progressive_desc;
|
||||
public bool get_success(T reply) => _get_success(reply);
|
||||
public string get_error_msg(T reply) => _get_error_msg(reply);
|
||||
}
|
||||
|
||||
struct DescTuple {
|
||||
public readonly string desc, present_progressive_desc;
|
||||
public DescTuple(string desc, string present_progressive_desc) {
|
||||
this.desc = desc;
|
||||
this.present_progressive_desc = present_progressive_desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AlgoService protobuf types have been extracted out of the viewmodels (<see cref="AlgoStudioVM"/>, <see cref="OrderPropertiesEditorVM"/>, <see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>).
|
||||
/// The viewmodels instead use these types to represent the data: <see cref="AlgoData"/> is used in place of <see cref="CompositeAlgoRecordImmutable"/>, and <see cref="AlgoTemplateData"/> is used in place of <see cref="AlgoTemplateRecordImmutable"/>.
|
||||
/// This thin abstraction isolates the server-side from the client-side, so if one day the server side types change, we won't need to change any client-side code except the methods below which handle the conversion from server type to corresponding client type.
|
||||
/// There are a couple other methods in here as well (this is an extensions class afterall), but [John]I am going out of town soon (1/6/18) and did not get a chance to organize these methods beyond containing them in <see cref="Utils"/>.
|
||||
/// </summary>
|
||||
static class _Utils {
|
||||
// AlgoTemplateImmutable custom_order_definition(i)
|
||||
public static CustomOrderDefinitionImmutable custom_order_definitioni(AlgoTemplateImmutable templatei) {
|
||||
switch (templatei.AlgoParamsCase) {
|
||||
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return templatei.CompositeAlgoParams.Algo;
|
||||
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams: return templatei.SystemAlgoParams;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData definition_data(AlgoTemplateImmutable templatei) {
|
||||
switch (templatei.AlgoParamsCase) {
|
||||
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return definition_data(templatei.CompositeAlgoParams);
|
||||
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams:
|
||||
return new AlgoData(
|
||||
templatei.SystemAlgoParams,
|
||||
templatei.SystemAlgoParams.CustomCase.display_name(),
|
||||
get_key(templatei.SystemAlgoParams));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoData definition_data(CompositeAlgoRecordImmutable recordi) => new AlgoData(recordi.AlgoDefinition.Algo, recordi.AlgoDefinition.Name, get_key(recordi));
|
||||
|
||||
public static AlgoData definition_data(CompositeAlgoDefinitionImmutable definitioni) =>
|
||||
new AlgoData(definitioni.Algo, definitioni.Name, get_key(definitioni));
|
||||
|
||||
public static AlgoData? definition_data(Result<CompositeAlgoCreateResult> result) {
|
||||
if (result.success && result.value.Success) {
|
||||
var r = result.value.Record;
|
||||
return new AlgoData(new CustomOrderDefinitionImmutable(r.AlgoDefinition.Algo), r.AlgoDefinition.Name, new AlgoKey(new AlgoPersistedKey(r.Key)));
|
||||
} else return null;
|
||||
}
|
||||
|
||||
// AlgoTemplateRecord(Immutable) template data
|
||||
public static AlgoTemplateData template_data(AlgoTemplateRecord record) => template_data(new AlgoTemplateRecordImmutable(record));
|
||||
public static AlgoTemplateData template_data(AlgoTemplateRecordImmutable recordi) => new AlgoTemplateData(custom_order_definitioni(recordi.AlgoTemplate), recordi.AlgoTemplate.Name, get_key(recordi), definition_data(recordi.AlgoTemplate));
|
||||
|
||||
public static CompositeAlgoDefinition to_definition(AlgoData definition_data) =>
|
||||
new CompositeAlgoDefinition {
|
||||
Name = definition_data.name,
|
||||
Algo = definition_data.value.CloneToMutable()
|
||||
};
|
||||
|
||||
// AlgoTemplateData to_record(i)
|
||||
public static AlgoTemplateRecord to_record(AlgoTemplateData template) {
|
||||
var record = new AlgoTemplateRecord {
|
||||
AlgoTemplate = new AlgoTemplate {
|
||||
Name = template.name,
|
||||
CompositeAlgoParams = new CompositeAlgoDefinition {
|
||||
Name = template.definition.name,
|
||||
Algo = template.value.CloneToMutable()
|
||||
}
|
||||
}
|
||||
};
|
||||
set_key(ref record, template.key);
|
||||
return record;
|
||||
}
|
||||
public static AlgoTemplateRecordImmutable to_recordi(AlgoTemplateData template) => new AlgoTemplateRecordImmutable(to_record(template));
|
||||
|
||||
// AlgoTemplateData to_template(i)
|
||||
public static AlgoTemplate to_template(AlgoTemplateData template) =>
|
||||
new AlgoTemplate {
|
||||
Name = template.name,
|
||||
CompositeAlgoParams = new CompositeAlgoDefinition {
|
||||
Name = template.definition.name,
|
||||
Algo = template.value.CloneToMutable()
|
||||
}
|
||||
};
|
||||
public static AlgoTemplateImmutable to_templatei(AlgoTemplateData template) => new AlgoTemplateImmutable(to_template(template));
|
||||
|
||||
public static AlgoKey get_key(CompositeAlgoRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
public static AlgoKey get_key(AlgoTemplateRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
public static AlgoKey get_key(CompositeAlgoDefinitionImmutable definitioni) => new AlgoKey(new AlgoPersistedKey(definitioni.Name));
|
||||
public static AlgoKey get_key(CustomOrderDefinitionImmutable custom_order_definitioni) => new AlgoKey(custom_order_definitioni.CustomCase);
|
||||
|
||||
public static void set_key(ref AlgoTemplateRecord record, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
switch (key.persisted.mode) {
|
||||
case AlgoPersistedKey.Mode.String:
|
||||
record.Key = key.persisted.id_string; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
public static void set_key(ref AlgoServiceRemoveRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
switch (key.persisted.mode) {
|
||||
case AlgoPersistedKey.Mode.String:
|
||||
request.Key = key.persisted.id_string; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-32
@@ -1,18 +1,16 @@
|
||||
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.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export(typeof(IAlgoStudioModel))]
|
||||
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoDefinitionService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
@@ -21,33 +19,34 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
|
||||
[ImportingConstructor]
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
_service = service;
|
||||
this.metadata_service = metadata_service;
|
||||
_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; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -73,10 +72,10 @@ namespace mbts.spark {
|
||||
new AlgoDefinitionImmutable(_template(definition_data));
|
||||
|
||||
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
|
||||
new AlgoData(recordi.Algo.Definition, recordi.Algo.Name, _key(recordi));
|
||||
AlgoData.from_record(recordi);
|
||||
|
||||
static AlgoKey _key(AlgoDefinitionRecordImmutable recordi) =>
|
||||
new AlgoKey(new AlgoPersistedKey(recordi.Key));
|
||||
AlgoKey.from_composite_key(recordi.Key);
|
||||
|
||||
static AlgoDefinitionUpdateRequest _with_key(AlgoDefinitionUpdateRequest request, AlgoKey key) {
|
||||
_set_key(ref request, key);
|
||||
@@ -85,9 +84,10 @@ namespace mbts.spark {
|
||||
|
||||
static void _set_key(ref AlgoDefinitionUpdateRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Factory:
|
||||
case AlgoKey.Mode.Dynamic:
|
||||
case AlgoKey.Mode.None: request.Key = 0; break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
@@ -95,7 +95,7 @@ namespace mbts.spark {
|
||||
|
||||
static void _set_key(ref AlgoDefinitionRemoveRequest request, AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
|
||||
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
}
|
||||
@@ -105,16 +105,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -20,34 +17,13 @@ namespace mbts.spark {
|
||||
bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
}
|
||||
|
||||
class AlgoStudioModelExporter {
|
||||
[ImportingConstructor]
|
||||
AlgoStudioModelExporter(Spark.AlgoServiceWrapper service_wrapper, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
var ui_configuration = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
switch (service_wrapper.mode) {
|
||||
case Spark.AlgoServiceWrapper.Mode.Flat: model = new AlgoStudioFlatDefinitionModel(service_wrapper.flat, meta, admin_model, error_reporter, ui_configuration); break;
|
||||
case Spark.AlgoServiceWrapper.Mode.Hierarchical: model = new AlgoStudioDefinitionTemplateModel(service_wrapper.hierarhical, meta, admin_model, error_reporter, ui_configuration); break;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public IAlgoStudioModel model { get; }
|
||||
}
|
||||
|
||||
static class AlgoStudioModelUtils {
|
||||
public static bool templates_enabled(this IAlgoStudioModel model) =>
|
||||
get_templates_enabled(model.ui_configuration);
|
||||
|
||||
public static bool get_templates_enabled(bts.config.AlgoStudioOptions ui_configuration) =>
|
||||
ui_configuration.add_template_button != bts.config.AlgoStudioOptions.AddTemplateButton.None;
|
||||
|
||||
public static IReadOnlyDictionary<object, object> get_dynamic_resources(bts.config.AlgoStudioOptions ui_configuration) =>
|
||||
new Dictionary<object, object> {
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey, ui_configuration.can_modify_definition_name && ui_configuration.show_algo_toolbar_rename },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey, ui_configuration.can_modify_definition_values && ui_configuration.show_algo_toolbar_save },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey, ui_configuration.show_algo_toolbar_revert },
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo },
|
||||
{ AlgoStudioWindow.TemplatesEnabledKey, get_templates_enabled(ui_configuration) }
|
||||
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo }
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -35,9 +35,9 @@ namespace mbts.spark {
|
||||
internal AlgoDefinitionPersistedMetadata to_persisted() =>
|
||||
id.mode != AlgoKey.Mode.Persisted ?
|
||||
throw new InvalidOperationException("Metadata does not have a persisted key.") :
|
||||
new AlgoDefinitionPersistedMetadata(id.persisted,
|
||||
default_template.mode == AlgoKey.Mode.Persisted ? default_template.persisted : default,
|
||||
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => t.persisted).ToArray(),
|
||||
new AlgoDefinitionPersistedMetadata(new AlgoPersistedKey(id.persisted),
|
||||
default_template.mode == AlgoKey.Mode.Persisted ? new AlgoPersistedKey(default_template.persisted) : default,
|
||||
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => new AlgoPersistedKey(t.persisted)).ToArray(),
|
||||
is_favorite, is_hidden, abbreviation, new NullableColorProto(badge_color));
|
||||
|
||||
public AlgoDefinitionMetadata with(AlgoKey? id = null, AlgoKey? default_template = null, IEnumerable<AlgoKey> favorite_templates = null, bool? is_favorite = null, bool? is_hidden = null, string abbreviation = null, NullableColorProto? badge_color = null) =>
|
||||
|
||||
@@ -83,7 +83,8 @@ namespace mbts.spark {
|
||||
|
||||
public static AlgoDisplayGroup display_group(this AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory.structure.order_type());
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory);
|
||||
case AlgoKey.Mode.Dynamic: return AlgoDisplayGroup.API;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoDefinitionTemplateVM : DisposableViewModelBase, IAlgoStudioContentVM, IEquatable<AlgoDefinitionTemplateVM> {
|
||||
/// <see cref="CustomType"/> is referenced by an x:Static extension in OrderDetailsPopupControl.xaml <see cref="mbts.ui.spark.algo.OrderDetailsPopupControl"/>.
|
||||
/// In most cases, <see cref="mbts.ui.NestedExtension"/> works just like <see cref="System.Windows.Markup.TypeExtension"/>, but for nested types, such as <see cref="CustomTemplatePlaceholderVM"/>.
|
||||
/// However, when the former is used to denote the DataType of a DataTemplate, a strange XamlReader exception is thrown at runtime.
|
||||
/// After spending ~15 minutes investigating what was happening, [John]I decided it was more trouble than it was worth and opted for this workaround.
|
||||
public static readonly Type CustomType = typeof(CustomTemplatePlaceholderVM);
|
||||
readonly OrdersModel _model;
|
||||
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
|
||||
/// Note: the difference between <see cref="EditStack{T}"/> and <see cref="VersionContainer{T}"/> is that the former implements push/pop functionality so the user may undo/redo (like ctrl+z/ctrl+y in text editors),
|
||||
/// while the latter is a lazy delegate-based container for the most recent value (<see cref="VersionContainer{T}.increment_version"/>).
|
||||
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
|
||||
readonly VersionContainer<Snapshot> _version;
|
||||
readonly DeferrableActionWrapper _refresh_on_local_change;
|
||||
readonly Property<IAlgoVM> _algo_root;
|
||||
readonly DelegateProperty<bool> _can_undo, _can_redo, _is_dirty;
|
||||
readonly Property<bool> _is_default, _is_favorite, _is_selected;
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
// maintains the state of each algo-root, so the user may change the definition root back and forth without any loss of state amongst the templates
|
||||
Snapshot _committed;
|
||||
|
||||
public AlgoDefinitionTemplateVM(AlgoDefinitionVM owner, OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = AlgoEditingPermissions.AllowEditValues) {
|
||||
// init core data
|
||||
this.owner = owner;
|
||||
_model = model;
|
||||
key = data.key;
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
// maintain states for different algo roots, so they aren't lost when the user changes the root algo type
|
||||
var algo_map = new Dictionary<AlgoKey, IAlgoVM> {
|
||||
{ data.key, algo_vm_factory(data.value) }
|
||||
};
|
||||
// init backing props
|
||||
_name = backing(nameof(this.name), name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_is_default = backing(nameof(is_default), false);
|
||||
_is_favorite = backing(nameof(is_favorite), false);
|
||||
_is_selected = backing(nameof(is_selected), false);
|
||||
_algo_root = backing(nameof(algo_root), get_algo_root(owner.algo_root));
|
||||
_committed = _version.value;
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
_is_dirty = backing(nameof(is_dirty), () => !_committed.Equals(_version.value));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
set_as_default_cmd = new DelegateCommand(() => {
|
||||
if (!is_default && key.mode == AlgoKey.Mode.Persisted)
|
||||
_model.algo_model.metadata_service.set_as_default(owner.key, key).ignore();
|
||||
});
|
||||
// init visual states
|
||||
// TODO: implement these features in IAlgoMetadataService; until then, set the appropriate visual states.
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Abbreviation.GroupName, VisualStateNames.Abbreviation.NotSupportedForTemplates);
|
||||
_tree_view_vs_model.set_state(VisualStateNames.FavoriteToggleButton.GroupName, VisualStateNames.FavoriteToggleButton.Inactive);
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_edit_stack.push(_committed);
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active);
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active); break;
|
||||
case AlgoKey.Mode.Factory: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive); break;
|
||||
}
|
||||
// set hooks/subscriptions
|
||||
_refresh_on_local_change = new DeferrableActionWrapper(on_local_changed);
|
||||
_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()),
|
||||
Disposable.wrap_collection(algo_map.Values)) {
|
||||
() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}
|
||||
};
|
||||
// local methods
|
||||
IAlgoVM algo_vm_factory(CustomOrderDefinitionImmutable def) => OrderTypeVMFactory.create(new AlgoData(def, def.CustomCase.display_name(), OrderDataHelper.get_shallow_factory_key(def)), editing_permissions);
|
||||
IAlgoVM get_algo_root(IAlgoVM owner_root) {
|
||||
var key = new AlgoKey(owner_root.order_type);
|
||||
if (algo_map.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_structure(owner_root.definition);
|
||||
} else {
|
||||
vm = algo_map.GetOrAdd(key, ot => algo_vm_factory(owner_root.definition));
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
void on_local_changed() {
|
||||
_version.increment_version();
|
||||
_edit_stack.push_distinct(_version.value);
|
||||
_is_dirty.refresh();
|
||||
_refresh_undo_redo();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action can_redo_changed, can_undo_changed;
|
||||
public event PropertyChange<IAlgoVM> algo_root_changed { add => _algo_root.Changed += value; remove => _algo_root.Changed -= value; }
|
||||
|
||||
public AlgoDefinitionVM owner { get; }
|
||||
public AlgoKey owner_key => owner.key;
|
||||
public AlgoKey key { get; }
|
||||
public string name { get => _name.Value; set => _name.set(value); }
|
||||
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public bool is_default => _is_default.Value;
|
||||
public bool is_favorite { get => _is_favorite.Value; set => _is_favorite.set(value); }
|
||||
public IAlgoVM algo_root => _algo_root.Value;
|
||||
public virtual bool is_custom_placeholder => false;
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_expanded => false;
|
||||
public bool is_persisted => key.mode == AlgoKey.Mode.Persisted;
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
public ICommand set_as_default_cmd { get; }
|
||||
|
||||
public IObservable<bool> observe_is_dirty() => _is_dirty.observe_current(true);
|
||||
public void undo() => _restore(_edit_stack.try_undo);
|
||||
public void redo() => _restore(_edit_stack.try_redo);
|
||||
|
||||
public void on_meta(bool is_default, bool is_favorite) {
|
||||
_is_default.set(is_default);
|
||||
_is_favorite.set(is_favorite);
|
||||
}
|
||||
|
||||
public AlgoData create_def_data() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
|
||||
public AlgoTemplateData create() =>
|
||||
create(owner.create_algo_def());
|
||||
|
||||
public AlgoTemplateData create(AlgoData record) {
|
||||
if (!OrderDataHelper.EqualsHelpers.structure_equals(algo_root.definition, record.value))
|
||||
throw new InvalidOperationException("AlgoTemplate's algo structure does not match that of its parent.");
|
||||
return new AlgoTemplateData(algo_root.definition, name, key, record);
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM to_persisted(AlgoDefinitionVM persisted_owner, AlgoData template) =>
|
||||
new AlgoDefinitionTemplateVM(persisted_owner, _model, template);
|
||||
|
||||
public void load_committed() => _load_from(_committed.name, _committed.definition, true);
|
||||
|
||||
public void load_from(AlgoTemplateData value) {
|
||||
if (key.mode != AlgoKey.Mode.Persisted)
|
||||
throw new InvalidOperationException("AlgoStructure has not been saved yet.");
|
||||
else if (value.key != key)
|
||||
throw new InvalidOperationException("Value id does not match this id.");
|
||||
_load_from(value.name, value.value, true);
|
||||
}
|
||||
|
||||
public bool Equals(AlgoDefinitionTemplateVM other) => other != null && key == other.key;
|
||||
public override bool Equals(object obj) => Equals(obj as AlgoDefinitionTemplateVM);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
void _load_from(string new_name, CustomOrderDefinitionImmutable new_def, bool update_committed) {
|
||||
using (_refresh_on_local_change.defer_execution()) {
|
||||
name = new_name;
|
||||
algo_root.load_from(new_def);
|
||||
}
|
||||
if (update_committed) {
|
||||
_committed = _version.value;
|
||||
_is_dirty.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_undo_redo() {
|
||||
_can_redo.refresh();
|
||||
_can_undo.refresh();
|
||||
}
|
||||
|
||||
void _restore(TryGetValue<Snapshot> try_get_snapshot) {
|
||||
while (try_get_snapshot(out Snapshot next_state)) {
|
||||
if (!_version.value.Equals(next_state)) {
|
||||
_load_from(next_state.name, next_state.definition, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Snapshot _snapshot() =>
|
||||
new Snapshot(name, abbreviation, algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()), is_default, is_favorite);
|
||||
|
||||
struct Snapshot : IEquatable<Snapshot> {
|
||||
public readonly string name, abbreviation;
|
||||
public readonly CustomOrderDefinitionImmutable definition;
|
||||
public readonly bool is_default, is_favorite;
|
||||
|
||||
public Snapshot(string name, string abbreviation, CustomOrderDefinitionImmutable definition, bool is_default, bool is_favorite) {
|
||||
this.name = name;
|
||||
this.abbreviation = abbreviation;
|
||||
this.definition = definition;
|
||||
this.is_default = is_default;
|
||||
this.is_favorite = is_favorite;
|
||||
}
|
||||
|
||||
public bool Equals(Snapshot other) =>
|
||||
name == other.name &&
|
||||
abbreviation == other.abbreviation &&
|
||||
is_default == other.is_default &&
|
||||
is_favorite == other.is_favorite &&
|
||||
OrderDataHelper.EqualsHelpers.equals(definition, other.definition);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
EqualityComparer.safe_aggregate_hash(name),
|
||||
EqualityComparer.safe_aggregate_hash(abbreviation),
|
||||
is_default.GetHashCode(),
|
||||
is_favorite.GetHashCode(),
|
||||
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.GetHashCode(definition));
|
||||
|
||||
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
|
||||
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
public class CustomTemplatePlaceholderVM : AlgoDefinitionTemplateVM {
|
||||
const string _custom_name = "(Custom)";
|
||||
|
||||
public CustomTemplatePlaceholderVM(AlgoDefinitionVM owner, OrdersModel model, CustomOrderDefinitionImmutable definition)
|
||||
: base(owner, model, new AlgoData(definition, _custom_name, new AlgoKey(Guid.NewGuid())), AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues) {
|
||||
_edit_stack.reset(_snapshot());
|
||||
}
|
||||
|
||||
public override bool is_custom_placeholder => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts.api.core;
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
@@ -6,7 +6,6 @@ using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
@@ -18,7 +17,6 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
|
||||
|
||||
@@ -36,18 +34,14 @@ namespace mbts.spark {
|
||||
public static readonly CustomOrderDefinitionImmutable new_algo_def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg);
|
||||
readonly OrdersModel _model;
|
||||
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey> _templates = new ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey>(t => t.key);
|
||||
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
|
||||
readonly VersionContainer<Snapshot> _version;
|
||||
readonly Property<bool> _is_expanded, _is_selected;
|
||||
readonly Property<IAlgoVM> _algo_root, _visible_algo_root;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
|
||||
readonly DelegateProperty<bool> _is_favorite, _is_dirty, _can_undo, _can_redo;
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
|
||||
readonly ObservableAppendCollection<AlgoDefinitionTemplateVM> _templates_with_custom;
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
@@ -55,18 +49,12 @@ namespace mbts.spark {
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
AlgoDefinitionMetadata _metadata;
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
if (connect)
|
||||
rv.connect();
|
||||
return rv;
|
||||
}
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
|
||||
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
@@ -78,20 +66,8 @@ namespace mbts.spark {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
else if (model.algo_model.templates_enabled()) {
|
||||
resources.Add(_templates.Subscribe((s, e) => {
|
||||
if (_templates.Count == 0) {
|
||||
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
} else {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
}
|
||||
}));
|
||||
}
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
|
||||
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
|
||||
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
@@ -99,87 +75,80 @@ namespace mbts.spark {
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_selected_format = backing(nameof(selected_format), init_format);
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
|
||||
_is_expanded = backing(nameof(is_expanded), true);
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : AlgoData.from_custom_def(def_data.value);
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
// only composite/studio algos can change root
|
||||
if (key.mode == AlgoKey.Mode.Factory || key.mode == AlgoKey.Mode.Dynamic) {
|
||||
_algo_root.Changing += (oldval, newval) =>
|
||||
Log.assert(newval != null && newval.id == key);
|
||||
}
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
|
||||
// load initial state
|
||||
init_async();
|
||||
async void init_async() {
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
|
||||
if (_model.algo_model.templates_enabled()) {
|
||||
resources.Add(_selected_template.observe_current(true).Subscribe(t => _visible_algo_root.set(t?.algo_root)));
|
||||
} else {
|
||||
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
|
||||
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
|
||||
// set hooks/subscriptions
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
resources.AddRange(
|
||||
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}));
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_metadata = new AlgoDefinitionMetadata(key);
|
||||
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
// init tree view item visual state
|
||||
_tree_view_vs_model.set_state(VisualStateNames.TreeViewBadge.GroupName, conf.show_badges_in_tree_view ? VisualStateNames.TreeViewBadge.Active : VisualStateNames.TreeViewBadge.Inactive);
|
||||
// set hooks/subscriptions
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
resources.AddRange(
|
||||
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
ObservableAutoWrapper.connect_item_hooks(templates, t => new CompositeDisposable(_any_templates_dirty_monitor.join(t), t), true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
_can_undo.Changed -= on_can_undo_changed;
|
||||
}));
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
_metadata = new AlgoDefinitionMetadata(key);
|
||||
resources.Add(_template_update_buffer.OnDispatcher().Subscribe(_on_templates));
|
||||
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
|
||||
break;
|
||||
case AlgoKey.Mode.Factory:
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
algo_root.load_from(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
// local methods
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
@@ -206,7 +175,6 @@ namespace mbts.spark {
|
||||
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
|
||||
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
|
||||
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
|
||||
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
|
||||
public int? favorite_index {
|
||||
get => _favorite_index;
|
||||
set {
|
||||
@@ -219,34 +187,12 @@ namespace mbts.spark {
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_factory_definition => key.mode == AlgoKey.Mode.Factory;
|
||||
public AlgoDisplayGroup group {
|
||||
get {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (algo_root != null)
|
||||
return algo_root.group;
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
}
|
||||
return AlgoDisplayGroup.BTS;
|
||||
}
|
||||
}
|
||||
public AlgoDisplayGroup group => key.display_group();
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
||||
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
||||
public Brush badge_fill => _badge_fill;
|
||||
public ObservableCollection<AlgoDefinitionTemplateVM> templates => _templates;
|
||||
/// <see cref="templates_with_custom"/> has the same elements as <see cref="templates"/>, plus the custom-placeholder-item, which is always at index 0.
|
||||
/// The custom-placeholder supports legacy behavior in the ladder/ticket algo popup by allowing the user to manipulate the algo-structure (e.g. change an iceberg-limit order to iceberg-lean-cover-offset etc.).
|
||||
/// However, the custom-placeholder item cannot be used to create/save algo-templates.
|
||||
/// Algo-templates are just a set of values used to populate a concrete algo-structure; the 'structures' can be created in the AlgoStudio (<see cref="AlgoStudioWindow"/>).
|
||||
/// If we were to strictly follow this paradigm, the user would no longer be able to freely change the algo-structure in the ladder/ticket popup (they would be forced to open up a new <see cref="AlgoStudioWindow"/>, and we know how much they hate more clicks!).
|
||||
/// Some users might consider this a regression. The custom-placeholder-item addresses that concern.
|
||||
public ObservableAppendCollection<AlgoDefinitionTemplateVM> templates_with_custom => _templates_with_custom;
|
||||
public ListCollectionView templates_view { get; }
|
||||
// format
|
||||
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
|
||||
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
|
||||
@@ -321,24 +267,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// idempotent
|
||||
public void prepend_custom_template() {
|
||||
if (_templates_with_custom.appended_front.Count == 0)
|
||||
_templates_with_custom.insert_front(new AlgoDefinitionTemplateVM.CustomTemplatePlaceholderVM(this, _model, algo_root.definition));
|
||||
}
|
||||
|
||||
public AlgoDefinitionTemplateVM template_factory() =>
|
||||
new AlgoDefinitionTemplateVM(this, _model, algo_root.create_def_data().with(name: new_template_name)) {
|
||||
is_selected = true
|
||||
};
|
||||
|
||||
public IObservable<bool> subscribe_is_definition_dirty() => _is_dirty.observe_current(true);
|
||||
public IObservable<bool> subscribe_any_templates_dirty() => _any_templates_dirty_monitor;
|
||||
public IObservable<bool> subscribe_is_definition_or_any_templates_dirty() =>
|
||||
Observable.Merge(
|
||||
subscribe_is_definition_dirty().Select(v => v || _any_templates_dirty_monitor.is_above_threshold),
|
||||
subscribe_any_templates_dirty().Select(v => v || is_dirty))
|
||||
.DistinctUntilChanged();
|
||||
|
||||
public void undo() => _restore(_edit_stack.try_undo);
|
||||
public void redo() => _restore(_edit_stack.try_redo);
|
||||
@@ -347,20 +276,9 @@ namespace mbts.spark {
|
||||
_assign_from_data(_committed.Value);
|
||||
_on_committed_changed();
|
||||
}
|
||||
/// <summary>
|
||||
/// Subscribes to the templates source for this definition if it has been persisted (subscription requires a persistant id); does nothing if this definition has not been persisted, or if it is already subscribed (idempotent).
|
||||
/// </summary>
|
||||
public void connect() {
|
||||
if (!_connected) {
|
||||
_connected = true;
|
||||
concat_disposable(_model.algo_model.subscribe_definition_templates(key).OnDispatcher().Subscribe(_template_update_buffer.OnNext));
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
var key = AlgoKey.from_custom_def(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_from(def);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
@@ -384,7 +302,7 @@ namespace mbts.spark {
|
||||
case 1: load_def(def_list[0]); break;
|
||||
default:
|
||||
var def = def_list[0];
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
var key = AlgoKey.from_custom_def(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_many(def_list);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
@@ -414,7 +332,7 @@ namespace mbts.spark {
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
|
||||
|
||||
public AlgoDefinitionMetadata create_metadata() =>
|
||||
_metadata.with(abbreviation: abbreviation, badge_color: new NullableColorProto(custom_badge_color.get_nullable()));
|
||||
@@ -443,7 +361,7 @@ namespace mbts.spark {
|
||||
|
||||
void _assign_from_data(string name, CustomOrderDefinitionImmutable definition) {
|
||||
this.name = name;
|
||||
algo_root = _algo_roots.GetOrAdd(OrderDataHelper.get_shallow_factory_key(definition), _ => _mk_algo_root(definition));
|
||||
algo_root = _algo_roots.GetOrAdd(AlgoKey.from_custom_def(definition), _ => _mk_algo_root(definition));
|
||||
algo_root.load_from(definition);
|
||||
}
|
||||
|
||||
@@ -458,18 +376,12 @@ namespace mbts.spark {
|
||||
public Snapshot current_value => _version.value;
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
OrderTypeVMFactory.create(AlgoData.from_custom_def(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
_abbreviation.set(metadata.abbreviation);
|
||||
custom_badge_color.set_nullable(metadata.badge_color);
|
||||
foreach (var t in templates) {
|
||||
if (t.key.mode == AlgoKey.Mode.Persisted)
|
||||
t.on_meta(t.key == metadata.default_template, metadata.favorite_templates.Contains(t.key));
|
||||
else
|
||||
t.on_meta(false, false);
|
||||
}
|
||||
_version.increment_version();
|
||||
if (_edit_stack.size == 1 && _edit_stack.pos == 1) {
|
||||
// this is a hack to account for the metadata being part of the committed state,
|
||||
@@ -482,22 +394,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _on_templates(TableUpdate<AlgoTemplateData, AlgoKey> table) {
|
||||
foreach (var rec in table.removed)
|
||||
_templates.Remove(rec.key);
|
||||
foreach (var rec in table.changed)
|
||||
_templates[rec.key].load_from(rec);
|
||||
foreach (var rec in table.added) {
|
||||
if (_templates.TryGetValue(rec.key, out AlgoDefinitionTemplateVM tmp)) {
|
||||
tmp.load_from(rec);
|
||||
} else {
|
||||
tmp = new AlgoDefinitionTemplateVM(this, _model, new AlgoData(rec.value, rec.name, rec.key));
|
||||
tmp.on_meta(tmp.key == _metadata.default_template, _metadata.favorite_templates.Contains(tmp.key));
|
||||
_templates.Add(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_undo_redo() {
|
||||
_can_undo.refresh();
|
||||
_can_redo.refresh();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using bts;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -14,6 +13,7 @@ using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
@@ -37,29 +37,30 @@ namespace mbts.spark {
|
||||
});
|
||||
}
|
||||
|
||||
class AlgoStudioVM : DisposableViewModelBase, IHandleSelectedItemChanged {
|
||||
class AlgoStudioVM : DisposableViewModelBase {
|
||||
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<AlgoDefinitionVM> _selected_definition;
|
||||
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
|
||||
readonly Property<IAlgoVM> _editable_content;
|
||||
readonly DelegateProperty<string> _save_cmd_tooltip, _revert_cmd_tooltip;
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly ListCollectionView<AlgoDefinitionVM> _definitions_view;
|
||||
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_update_buffer;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_or_any_templates_dirty());
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_dirty());
|
||||
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _persisted_meta = new Dictionary<AlgoKey, AlgoDefinitionMetadata>();
|
||||
|
||||
public AlgoStudioVM(OrdersModel model) {
|
||||
_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), subscribe_definition().SelectManyInSeries(def => def?.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()
|
||||
};
|
||||
definitions_view.Filter = d => !(d.key.mode == AlgoKey.Mode.Persisted && _persisted_meta.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden);
|
||||
// add/clone
|
||||
add_definition_cmd = new DelegateCommand(add_definition);
|
||||
clone_selected_definition_cmd = new DelegateCommand(clone_selected_definition);
|
||||
// save
|
||||
var save_cmd = new ObservantAsyncCommand<IAlgoStudioContentVM>(save_async);
|
||||
this.save_cmd = save_cmd;
|
||||
@@ -71,13 +72,18 @@ namespace mbts.spark {
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, true, _studio_formats))),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
|
||||
var orig_selected_def = selected_definition;
|
||||
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
|
||||
if (selected_definition == null)
|
||||
selected_definition = definitions.FirstOrDefault();
|
||||
}),
|
||||
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
|
||||
_persisted_meta = meta;
|
||||
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()
|
||||
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
@@ -85,77 +91,64 @@ namespace mbts.spark {
|
||||
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
|
||||
// local methods
|
||||
string get_revert_tooltip() {
|
||||
if (content_source is AlgoDefinitionTemplateVM tmp) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Revert changes to '{tmp.name}'" : "No changes to revert";
|
||||
}
|
||||
} else if (content_source is AlgoDefinitionVM def) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Revert changes to '{def.name}' templates" : "No changes to revert";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
var def = selected_definition;
|
||||
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
|
||||
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
|
||||
null;
|
||||
}
|
||||
async Task save_async(IAlgoStudioContentVM content) {
|
||||
if (content is AlgoDefinitionTemplateVM tmp) {
|
||||
await save_template(tmp);
|
||||
} else if (content is AlgoDefinitionVM def) {
|
||||
if (content is AlgoDefinitionVM def)
|
||||
await save_definition(def);
|
||||
}
|
||||
}
|
||||
string get_save_tooltip() {
|
||||
if (content_source is AlgoDefinitionTemplateVM tmp) {
|
||||
switch (tmp.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return tmp.is_dirty ? $"Save '{tmp.name}'" : $"Cannot Save '{tmp.name}'";
|
||||
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Save changes to '{tmp.name}'" : "No changes to save";
|
||||
}
|
||||
} else if (content_source is AlgoDefinitionVM def) {
|
||||
var def = selected_definition;
|
||||
if (def != null) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void add_definition() =>
|
||||
add_definition_common(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
|
||||
|
||||
void clone_selected_definition() {
|
||||
if (selected_definition == null || selected_definition.algo_root == null) {
|
||||
add_definition();
|
||||
} else {
|
||||
add_definition_common(
|
||||
selected_definition.algo_root.definition,
|
||||
string.IsNullOrEmpty(selected_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{selected_definition.name} - Copy");
|
||||
}
|
||||
}
|
||||
|
||||
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
|
||||
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
|
||||
_definitions.Add(new_def);
|
||||
selected_definition = new_def;
|
||||
request_view_focus_name?.Invoke(new_def);
|
||||
}
|
||||
}
|
||||
|
||||
event Action<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> _selected_changed;
|
||||
public event Action<AlgoDefinitionVM> request_view_focus_name;
|
||||
|
||||
public ObservableCollection<AlgoDefinitionVM> definitions => _definitions;
|
||||
public ObservableCollection<string> content_visual_states { get; } = new ObservableCollection<string>();
|
||||
public AlgoDefinitionVM selected_definition { get => _selected_definition.Value; set => _selected_definition.set(value); }
|
||||
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
|
||||
public IAlgoStudioContentVM content_source => (IAlgoStudioContentVM)selected_template ?? selected_definition;
|
||||
public IAlgoVM editable_content => _editable_content.Value;
|
||||
public ICollectionView<AlgoDefinitionVM> definitions_view => _definitions_view;
|
||||
public ICommand add_definition_cmd { get; }
|
||||
public ICommand clone_selected_definition_cmd { get; }
|
||||
public ICommand save_cmd { get; }
|
||||
public ICommand revert_cmd { get; }
|
||||
public string save_cmd_tooltip => _save_cmd_tooltip.Value;
|
||||
public string revert_cmd_tooltip => _revert_cmd_tooltip.Value;
|
||||
|
||||
public void add_new_definition() =>
|
||||
_add_new_definition(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
|
||||
|
||||
public void add_new_definition_clone(AlgoDefinitionVM src_definition) {
|
||||
if (src_definition == null || src_definition.algo_root == null) {
|
||||
add_new_definition();
|
||||
} else {
|
||||
_add_new_definition(src_definition.algo_root.definition, string.IsNullOrEmpty(src_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{src_definition.name} - Copy");
|
||||
}
|
||||
}
|
||||
|
||||
void _add_new_definition(CustomOrderDefinitionImmutable def, string name) =>
|
||||
_definitions.Add(AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats));
|
||||
|
||||
public StartupData create() => new StartupData();
|
||||
public void load_from(StartupData state) { }
|
||||
public IObservable<AlgoDefinitionVM> subscribe_definition() => _selected_definition.observe_current(true);
|
||||
public IObservable<AlgoDefinitionTemplateVM> subscribe_template() => _selected_template.observe_current(true);
|
||||
public IObservable<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> observe_selected() =>
|
||||
Observable.Create<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)>(o => {
|
||||
_selected_changed += o.OnNext;
|
||||
return Disposable.Create(() => _selected_changed -= o.OnNext);
|
||||
});
|
||||
public IObservable<bool> subscribe_dirty() => _any_dirty_monitor;
|
||||
|
||||
public async Task remove_definition(AlgoDefinitionVM definition) {
|
||||
@@ -163,87 +156,43 @@ namespace mbts.spark {
|
||||
await _model.algo_model.remove_definition(definition.key);
|
||||
} else {
|
||||
// This is a new definition, which hasn't been saved/persisted yet. No need to call the server.
|
||||
definition.templates.Clear();
|
||||
definitions.Remove(definition);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task remove_template(AlgoDefinitionTemplateVM template) {
|
||||
if (template.key.mode == AlgoKey.Mode.Persisted) {
|
||||
await _model.algo_model.remove_template(template.key);
|
||||
} else {
|
||||
// This is a new template, which hasn't been saved/persisted yet. No need to call the server.
|
||||
template.owner.templates.Remove(template);
|
||||
}
|
||||
}
|
||||
public Task save_definition(AlgoDefinitionVM definition) =>
|
||||
_save(new[] { definition });
|
||||
|
||||
public async Task save_definition(AlgoDefinitionVM definition) {
|
||||
using (_save_op()) {
|
||||
var newly_persisted_keys = await AlgoPersistenceHelper.persist_definition(_model, _definitions, definition);
|
||||
AlgoKey pkey;
|
||||
switch (definition.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
if (!newly_persisted_keys.TryGetValue(definition.key, out pkey))
|
||||
return;
|
||||
break;
|
||||
case AlgoKey.Mode.Persisted:
|
||||
pkey = definition.key;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
await _model.algo_model.metadata_service.update_metadata(pkey, _ => definition.create_metadata().with(id: pkey));
|
||||
}
|
||||
}
|
||||
public Task save_all() =>
|
||||
_save(definitions);
|
||||
|
||||
public async Task save_template(AlgoDefinitionTemplateVM template) {
|
||||
using (_save_op())
|
||||
await AlgoPersistenceHelper.persist_template(_model, _definitions, template);
|
||||
}
|
||||
|
||||
public async Task save_all() =>
|
||||
await _save(definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
|
||||
|
||||
async Task _save(IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
|
||||
using (_save_op())
|
||||
await AlgoPersistenceHelper.persist(_model, definition_templates);
|
||||
}
|
||||
|
||||
IDisposable _save_op() {
|
||||
int? def_index = selected_definition == null ? (int?)null : _definitions.IndexOf(selected_definition);
|
||||
int? tmp_index = selected_template == null ? (int?)null : selected_definition.templates.IndexOf(selected_template);
|
||||
return new CompositeDisposable(_definitions_update_buffer.buffer_updates()) {
|
||||
() => {
|
||||
if (def_index.HasValue) {
|
||||
var def = _definitions[def_index.Value];
|
||||
if (tmp_index.HasValue) {
|
||||
var tmp = def.templates[tmp_index.Value];
|
||||
tmp.is_selected = true;
|
||||
} else def.is_selected = true;
|
||||
async Task _save(IEnumerable<AlgoDefinitionVM> definitions) {
|
||||
var orig_selected = selected_definition;
|
||||
var orig_selected_index = orig_selected == null ? -1 : this.definitions.IndexOf(orig_selected);
|
||||
using (_definitions_update_buffer.buffer_updates()) {
|
||||
var def_list = definitions as IList<AlgoDefinitionVM> ?? definitions.ToList();
|
||||
await Task.WhenAll(definitions.Select(async d => {
|
||||
var def = await _model.algo_model.save_definition(d.create_algo_def());
|
||||
if (def.HasValue) {
|
||||
// don't forget to save metadata
|
||||
await _model.algo_model.metadata_service.update_metadata(def.Value.key, _ => d.create_metadata().with(def.Value.key));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Despite the existence of these properties: <see cref="AlgoDefinitionVM.is_selected"/>, and <see cref="AlgoDefinitionTemplateVM.is_selected"/>,
|
||||
/// <see cref="AlgoStudioVM"/> still implements <see cref="IHandleSelectedItemChanged"/> to update the properties that depend on the <see cref="System.Windows.Controls.TreeView.SelectedItem"/>.
|
||||
/// [John]I am going to Uganda in 3 days (today is 1/3/2019), and decided against removing this legacy-dependency at the last minute.
|
||||
/// Fwiw, <see cref="AlgoDefinitionVM.is_selected"/> and <see cref="AlgoDefinitionTemplateVM.is_selected"/> are bound to <see cref="System.Windows.Controls.TreeViewItem.IsSelected"/>.
|
||||
/// They serve to prevent a quirky default behavior in TreeView: double clicking any TreeViewItem collapses the entire TreeView.
|
||||
/// This behavior wasn't just quirky either, it actually conflicted with the double-click-to-set-template-as-default behavior.
|
||||
/// (for details, <see cref="mbts.ui.spark.AlgoStudioView.EnableToggleDefaultOnDoubleClickProperty"/>)
|
||||
void IHandleSelectedItemChanged.on_selected_item_changed(object old_value, object new_value) {
|
||||
if (new_value is AlgoDefinitionVM def) {
|
||||
selected_definition = def;
|
||||
selected_template = null;
|
||||
} else if (new_value is AlgoDefinitionTemplateVM templ) {
|
||||
selected_definition = templ.owner;
|
||||
selected_template = templ;
|
||||
} else {
|
||||
selected_definition = null;
|
||||
selected_template = null;
|
||||
return def;
|
||||
})).ContinueOnDispatcher(t => {
|
||||
if (t.Status != TaskStatus.RanToCompletion) {
|
||||
Log.Ui.debug_fatal($"Algo Studio error saving definitions: unexpected Task.Status '{t.Status}'.");
|
||||
return;
|
||||
}
|
||||
var persisted_data = t.Result;
|
||||
Log.assert(persisted_data.Length == def_list.Count);
|
||||
for (int i = 0; i < persisted_data.Length; i++) {
|
||||
if (!def_list[i].key.is_persisted && persisted_data[i].HasValue)
|
||||
_definitions.Replace(def_list[i].key, def_list[i].to_persisted(persisted_data[i].Value));
|
||||
}
|
||||
if (selected_definition != orig_selected && orig_selected_index >= 0 && orig_selected_index < this.definitions.Count)
|
||||
selected_definition = this.definitions[orig_selected_index];
|
||||
});
|
||||
}
|
||||
_selected_changed?.Invoke((selected_definition, selected_template));
|
||||
RaisePropertyChanged(nameof(content_source));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,6 @@ namespace mbts.spark {
|
||||
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
|
||||
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
|
||||
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
|
||||
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
|
||||
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
|
||||
|
||||
IDisposable _set_hooks(AlgoDefinitionVM vm) {
|
||||
|
||||
+3
@@ -513,6 +513,9 @@
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
IsEnabled="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}, Converter={StaticResource NotConverter}}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
/>
|
||||
</AdornerDecorator>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -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;
|
||||
@@ -61,6 +60,14 @@ namespace mbts.spark {
|
||||
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
|
||||
comparer: Comparer<SecurityImmutable>.Default
|
||||
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
|
||||
// symbol
|
||||
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
|
||||
DiscreteValuesFilterVM.Create<FillTuple, string>(
|
||||
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ft => ft.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
DefaultColumnWidths.Symbol);
|
||||
// order type
|
||||
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
|
||||
add_col_expl(Columns.OrderType,
|
||||
@@ -121,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) =>
|
||||
@@ -173,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) {
|
||||
@@ -190,6 +196,7 @@ namespace mbts.spark {
|
||||
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
|
||||
case Columns.Side: return wrap_default(ft => ft.fill.Side);
|
||||
case Columns.Size: return wrap(ft => ft.fill.LastShares);
|
||||
case Columns.Symbol: return wrap(ft => ft.security?.Symbol ?? string.Empty);
|
||||
case Columns.TimeInForce: return wrap_default(ft => ft.order?.time_in_force() ?? TimeInForce.Undefined);
|
||||
case Columns.User: return wrap(ft => ft.order?.user() ?? string.Empty);
|
||||
default: goto case Columns.FillTime;
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:api="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:bts="clr-namespace:mbts.ui.controls"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -15,6 +19,10 @@
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<cv:ValueMap x:Key="SideToBrushConverter" Default="{u:Brush Transparent}">
|
||||
<cv:KeyValue Key="{u:Enum Buy, Type=api:Side}" Value="{x:Static semi_transparent_bid_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
|
||||
<cv:KeyValue Key="{u:Enum Sell, Type=api:Side}" Value="{x:Static semi_transparent_ask_brush, MemberType={u:Nested u:BTSColors.MarketData}}"/>
|
||||
</cv:ValueMap>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
@@ -41,10 +49,10 @@
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView/>
|
||||
</DataTemplate>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView Background="{Binding side, Converter={StaticResource SideToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
|
||||
@@ -7,7 +7,23 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for FillsView.xaml
|
||||
/// </summary>
|
||||
partial class FillsView : UserControl {
|
||||
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey }
|
||||
public enum Columns {
|
||||
FillTime,
|
||||
User,
|
||||
Account,
|
||||
Exchange,
|
||||
Security,
|
||||
Symbol,
|
||||
TimeInForce,
|
||||
OrderType,
|
||||
Side,
|
||||
Price,
|
||||
Size,
|
||||
FillKind,
|
||||
FillKey,
|
||||
OrderKey,
|
||||
SecurityKey
|
||||
}
|
||||
|
||||
public FillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -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;
|
||||
@@ -60,19 +61,18 @@ namespace mbts.spark {
|
||||
_commands = new List<SparkCommand> {
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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 SimpleGridSettingsDialog { 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;
|
||||
}))
|
||||
|
||||
@@ -66,6 +66,10 @@ namespace bts.shell.fills {
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
|
||||
[ProtoMember(15)]
|
||||
[DIYGridColumnID(nameof(Columns.Symbol))]
|
||||
public string[] symbol { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -86,6 +90,7 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
|
||||
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
|
||||
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
|
||||
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace bts.shell.fills {
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
|
||||
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
@@ -62,6 +63,7 @@ namespace bts.shell.fills {
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
|
||||
@@ -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!
|
||||
@@ -195,7 +196,7 @@ namespace mbts.spark {
|
||||
public readonly int column_count;
|
||||
|
||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count)
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => k.persisted), column_count) { }
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)), column_count) { }
|
||||
|
||||
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count) {
|
||||
_favorite_algos = favorite_algos;
|
||||
@@ -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 {
|
||||
@@ -286,8 +306,8 @@ namespace mbts.spark {
|
||||
volume_column = VolumeColumnSettings.Default,
|
||||
calculator_buttons = new[] { 1, 5, 10, 25, 50, 100 },
|
||||
scroll_bar_visible = false,
|
||||
default_order_type = CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
default_time_in_force = TimeInForce.Day,
|
||||
default_algo = new AlgoID(CustomOrderDefinition.CustomOneofCase.Limit),
|
||||
animate_recenter = true,
|
||||
badge_style = BadgeStyle.Normal,
|
||||
bid_break_line = new LadderMarketBreakLine { color = 0, show = true },
|
||||
@@ -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;
|
||||
@@ -327,7 +348,15 @@ namespace mbts.spark {
|
||||
|
||||
public enum BadgeStyle { Normal, Alternate }
|
||||
|
||||
public enum NetChangeTerms { None, Dollars, Ticks }
|
||||
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
|
||||
public enum NetChangeTerms {
|
||||
[Description("Standard Ticks")]
|
||||
None,
|
||||
[Description("Net Change Dollars")]
|
||||
Dollars,
|
||||
[Description("Net Change Ticks")]
|
||||
Ticks
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public double row_height { get; set; }
|
||||
@@ -354,6 +383,10 @@ namespace mbts.spark {
|
||||
public bool scroll_bar_visible { get; set; }
|
||||
// USED [ProtoMember(11)]
|
||||
// USED [ProtoMember(12)]
|
||||
[ProtoMember(51)]
|
||||
public uint default_account { get; set; }
|
||||
[ProtoMember(52)]
|
||||
public AlgoID default_algo { get; set; }
|
||||
[ProtoMember(13)]
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type { get; set; }
|
||||
[ProtoMember(14)]
|
||||
@@ -364,6 +397,8 @@ namespace mbts.spark {
|
||||
public bool animate_recenter { get; set; } = true;
|
||||
[ProtoMember(17)]
|
||||
public BadgeStyle badge_style { get; set; }
|
||||
[ProtoMember(53)]
|
||||
public bool auto_clear { get; set; }
|
||||
|
||||
[ProtoMember(18)]
|
||||
public LadderMarketBreakLine bid_break_line { get; set; }
|
||||
@@ -446,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 &&
|
||||
@@ -459,11 +535,13 @@ namespace mbts.spark {
|
||||
piq_column.safe_equals(other.piq_column) &&
|
||||
calculator_buttons.safe_equals(other.calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
|
||||
scroll_bar_visible == other.scroll_bar_visible &&
|
||||
default_order_type == other.default_order_type &&
|
||||
default_account == other.default_account &&
|
||||
default_algo == other.default_algo &&
|
||||
default_time_in_force == other.default_time_in_force &&
|
||||
size == other.size &&
|
||||
animate_recenter == other.animate_recenter &&
|
||||
badge_style == other.badge_style &&
|
||||
auto_clear == other.auto_clear &&
|
||||
bid_break_line.safe_equals(other.bid_break_line) &&
|
||||
ask_break_line.safe_equals(other.ask_break_line) &&
|
||||
mid_break_line.safe_equals(other.mid_break_line) &&
|
||||
@@ -495,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);
|
||||
|
||||
@@ -512,7 +592,6 @@ namespace mbts.spark {
|
||||
EqualityComparer.safe_get_hash_code(piq_column),
|
||||
EqualityComparer.safe_get_hash_code(calculator_buttons),
|
||||
scroll_bar_visible.GetHashCode(),
|
||||
default_order_type.GetHashCode(),
|
||||
default_time_in_force.GetHashCode(),
|
||||
size.GetHashCode(),
|
||||
animate_recenter.GetHashCode(),
|
||||
@@ -547,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;
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -52,63 +55,212 @@ namespace bts.shell {
|
||||
public async override Task<IShellWindow> load(LadderSaveState state) {
|
||||
await Wait.until_true(() => _securities.ContainsKey(state.security_key), _ladder_conf.security_timeout);
|
||||
if (_securities.TryGetValue(state.security_key, out SecurityMD security)) {
|
||||
return create_ladder_window(security, state.settings);
|
||||
return create_ladder_window(security, state.settings, state.settings_id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static LadderSaveState _get_state(LadderVM vm) =>
|
||||
new LadderSaveState { security_key = vm.security.Id, settings = vm.get_save_state() };
|
||||
LadderSaveState _get_state(LadderVM vm, uint id) {
|
||||
LadderSettings s = vm.get_save_state();
|
||||
if (id != 0) {
|
||||
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
|
||||
// profile was deleted
|
||||
id = 0;
|
||||
} else {
|
||||
if (!s.Equals(result.value)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
|
||||
}
|
||||
|
||||
int _ladder_count = 0;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
|
||||
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings last_value, 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 settings_manager = new WindowSettingsManager<LadderSettings>(
|
||||
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
|
||||
us => sanitize_ladder_settings(us.ladder),
|
||||
(us, v) => us.with(sanitize_ladder_settings(v)));
|
||||
var init = state_settings ?? settings_manager.default_settings;
|
||||
init.use_defaults |= state_settings == null;
|
||||
var vm = LadderVM.create(_bus, model, settings_manager, init);
|
||||
var profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
|
||||
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, 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()));
|
||||
last_value?.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;
|
||||
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
|
||||
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;
|
||||
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);
|
||||
};
|
||||
// view
|
||||
var lv = new LadderView(vm);
|
||||
foreach (var kv in AlgoStudioModelUtils.get_dynamic_resources(vm.model.order_gateway.algo_model.ui_configuration))
|
||||
lv.Resources[kv.Key] = kv.Value;
|
||||
return lv;
|
||||
// local methods
|
||||
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;
|
||||
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
if (!last_value.Equals(vm.get_save_state())) {
|
||||
settings_id = 0;
|
||||
} else {
|
||||
settings_subject.OnNext(last_value = profile.value);
|
||||
}
|
||||
} else {
|
||||
// profile was deleted; keep the current settings and switch to the 'Local' profile.
|
||||
settings_id = 0;
|
||||
}
|
||||
} else if (_settings_hub.initialized) {
|
||||
initialized = true;
|
||||
if (settings_id != 0) {
|
||||
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
last_value = profile.value;
|
||||
} else {
|
||||
settings_id = 0;
|
||||
}
|
||||
}
|
||||
if (settings_id == 0 && last_value == null) {
|
||||
foreach (var lp in _settings_hub.current.ladder_profiles) {
|
||||
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
|
||||
// In the meantime, simply use the first match.
|
||||
if (lp.security_criteria.includes(md.security)) {
|
||||
last_value = lp.value;
|
||||
settings_id = lp.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_value == null)
|
||||
last_value = _settings_hub.current.ladder;
|
||||
}
|
||||
settings_subject.OnNext(last_value);
|
||||
}
|
||||
}
|
||||
|
||||
void open_settings_dialog() {
|
||||
// dialog vm
|
||||
var dialog_vm = new ProfiledShellSettingsDialogVM<LadderSettings, LadderSettingsVM, LadderSettingsProfile>(
|
||||
profile_service,
|
||||
us => us.ladder ?? LadderSettings.Default,
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
|
||||
svm.load(s);
|
||||
return svm;
|
||||
},
|
||||
_ => _,
|
||||
vm.get_save_state(),
|
||||
UserSettings.FileExtensions.LADDER,
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
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;
|
||||
Disposable.replace(ref active_settings_dialog, Disposable.Create(destroy_all));
|
||||
dialog.move_to_mouse(-500, -500);
|
||||
dialog.Show();
|
||||
// local methods
|
||||
void apply(SettingsProfile<LadderSettings> profile) {
|
||||
settings_id = profile.id;
|
||||
last_value = profile.value;
|
||||
settings_subject.OnNext(profile.value);
|
||||
}
|
||||
// idempotent
|
||||
void destroy_all() {
|
||||
destroy_window();
|
||||
destroy_vm();
|
||||
}
|
||||
// idempotent
|
||||
void destroy_window() {
|
||||
if (dialog == null) return;
|
||||
try {
|
||||
var temp = dialog;
|
||||
dialog = null;
|
||||
temp.Closed -= on_closed;
|
||||
temp.Close();
|
||||
} catch (Exception ex) {
|
||||
on_error("closing window", ex);
|
||||
}
|
||||
}
|
||||
// idempotent
|
||||
void destroy_vm() {
|
||||
if (dialog_vm == null) return;
|
||||
try {
|
||||
var temp = dialog_vm;
|
||||
dialog_vm = null;
|
||||
temp.applied -= apply;
|
||||
temp.RequestClose -= on_request_close;
|
||||
temp.Dispose();
|
||||
} catch (Exception ex) {
|
||||
on_error("disposing vm", ex);
|
||||
}
|
||||
}
|
||||
void on_closed(object sender, EventArgs e) => destroy_all();
|
||||
void on_request_close(object sender, EventArgs e) => destroy_all();
|
||||
void on_error(string action_desc, Exception error) =>
|
||||
Log.Ui.warn($"LadderSettingsDialog error {action_desc}: {error}.");
|
||||
}
|
||||
});
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{id:00}" : "";
|
||||
return ShellWindow.create(
|
||||
string thread = _ladder_conf.ladders_on_threads ? $"Ladder{thread_id:00}" : "";
|
||||
return shell_window = ShellWindow.create(
|
||||
module, md.security.ToString(), _window_type, view,
|
||||
(v,s) => save(_get_state(((LadderView)v).vm), 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) {
|
||||
@@ -125,27 +277,6 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: whenever we re-implement the linked ladder, need to add some field to the LadderSaveState to indicate the owning window
|
||||
//void show_linked(IObservable<SecurityMD> keystream) {
|
||||
// var view = new LadderView();
|
||||
// IDisposable sub = null;
|
||||
// var window =
|
||||
// ShellWindow.create(module, "loading", view,
|
||||
// Disposable.Create(() => {
|
||||
// (view.DataContext as LadderVM)?.Dispose();
|
||||
// sub?.Dispose();
|
||||
// }), s => {
|
||||
// var vm = view.DataContext as LadderVM;
|
||||
// if (vm != null) save(_get_state(vm), s);
|
||||
// }, Icons.Ladder);
|
||||
// sub =
|
||||
// keystream
|
||||
// .Select(smd => new LadderVM(_bus, new LadderModel(smd, _orders_model),
|
||||
// Observable.Return(_orders_model.factory.default_ladder_settings(smd.security)))
|
||||
// ).Aggregate((old, vm) => { old.Dispose(); return vm; })
|
||||
// .Subscribe(vm => { view.DataContext = vm; window.Title = vm.security.ToString(); });
|
||||
//}
|
||||
|
||||
readonly struct LadderConfig {
|
||||
public readonly uint max_open_ladders;
|
||||
public readonly bool ladders_on_threads, open_algo_popup_on_select, use_algo_param_grid;
|
||||
@@ -167,5 +298,7 @@ namespace bts.shell {
|
||||
public UInt32 security_key;
|
||||
[ProtoMember(2)]
|
||||
public mbts.spark.LadderSettings settings;
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.orderbook;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
@@ -33,7 +35,7 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<double> _font_size, _grid_row_height;
|
||||
readonly Property<Brush> _foreground, _background, _hoverbackground, _border;
|
||||
readonly Property<bool> _has_multiple_orders_at_same_level, _is_hedge_order;
|
||||
readonly Property<CustomOrderDefinition.CustomOneofCase> _algo_type;
|
||||
@@ -62,6 +64,7 @@ namespace mbts.spark {
|
||||
_font_weight = backing(nameof(font_weight), owner.settings, get_font_weight, get_font_weight(fs));
|
||||
_font_size = backing(nameof(font_size), owner.settings, get_font_size, get_font_size(fs));
|
||||
_foreground = backing(nameof(foreground), owner.settings, get_foreground, get_foreground(fs));
|
||||
_grid_row_height = backing(nameof(grid_row_height), owner.settings, get_row_height, get_row_height(fs));
|
||||
_background = backing(nameof(background), _color_bg_brush);
|
||||
_hoverbackground = backing(nameof(hoverbackground), _color_bg_brush);
|
||||
_border = backing(nameof(border), _color_bg_brush);
|
||||
@@ -73,6 +76,7 @@ namespace mbts.spark {
|
||||
FontWeight get_font_weight(LadderSettings s) => ui.utility.text.TextUtils.from_open_type_weight_safe(s.orders_column.font_weight);
|
||||
double get_font_size(LadderSettings s) => s.orders_column.font_size;
|
||||
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
|
||||
double get_row_height(LadderSettings s) => s.row_height;
|
||||
//
|
||||
add_order(op);
|
||||
|
||||
@@ -244,6 +248,7 @@ namespace mbts.spark {
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public double font_size => _font_size.Value;
|
||||
public double grid_row_height => _grid_row_height.Value;
|
||||
public Brush foreground => _foreground.Value;
|
||||
public Brush background { get => _background.Value; private set => _background.Value = value; }
|
||||
public Brush hoverbackground { get => _hoverbackground.Value; private set => _hoverbackground.Value = value; }
|
||||
@@ -292,50 +297,29 @@ 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),
|
||||
price = price_filter,
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
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)));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -8,10 +11,14 @@ using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
@@ -19,19 +26,30 @@ using System.Windows.Media;
|
||||
using TextUtils = mbts.ui.utility.text.TextUtils;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderSettingsVM : ViewModelBase, IWindowSettingsVM<LadderSettings>, ITemplateSelectorKey, IDisposable {
|
||||
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
|
||||
readonly Property<LadderSettings.BadgeStyle> _badge_style;
|
||||
readonly Property<uint> _alternate_order_size;
|
||||
readonly DelegateProperty<double> _row_height;
|
||||
Func<double> _get_order_entry_panel_width, _get_zoom_factor;
|
||||
readonly UserProperty<AccountTuple?> _default_account;
|
||||
readonly UserProperty<AlgoDefinitionVM> _default_algo;
|
||||
readonly DeferrableActionWrapper _update;
|
||||
readonly ObservableKeyedCollection<AccountTuple?, uint> _accounts = new ObservableKeyedCollection<AccountTuple?, uint>(AccountTuple.nullable_key_fn);
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly OrdersModel _model;
|
||||
uint? _desired_account;
|
||||
AlgoID? _desired_algo;
|
||||
TimeInForce _default_time_in_force;
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear, _show_warn_on_cross;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
_bid = new LadderColumnSettingsVM("Bid"),
|
||||
_price = new LadderColumnSettingsVM("Price"),
|
||||
_ask = new LadderColumnSettingsVM("Ask"),
|
||||
_last = new LadderColumnSettingsVM("Last", true),
|
||||
_piq = new LadderColumnSettingsVM("PIQ", true);
|
||||
_last = new LadderColumnSettingsVM("Last", LadderSettingsView.LastColumnTemplateKey, true),
|
||||
_piq = new LadderColumnSettingsVM("PIQ", can_toggle_show: true);
|
||||
VolumeColumnVM _volume = new VolumeColumnVM();
|
||||
LadderMarketBreakLineVM
|
||||
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
|
||||
@@ -59,25 +77,18 @@ namespace mbts.spark {
|
||||
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
|
||||
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
|
||||
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
|
||||
readonly Property<bool>
|
||||
_show_in_net_change,
|
||||
_show_in_net_change_dollars,
|
||||
_show_in_net_change_ticks;
|
||||
|
||||
public LadderSettingsVM(OrdersModel model, bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
|
||||
this.is_synthetic = is_synthetic;
|
||||
favorite_algos_vm = new FavoriteAlgosVM(model);
|
||||
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
|
||||
_model = model;
|
||||
is_synthetic = security.ContractCase == Security.ContractOneofCase.Synthetic;
|
||||
favorite_algos_vm = new FavoriteAlgosVM(_algo_definitions);
|
||||
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
|
||||
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
|
||||
_badge_style = backing<LadderSettings.BadgeStyle>(nameof(badge_style));
|
||||
_badge_style.Changed += _badge_style_Changed;
|
||||
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
|
||||
_get_order_entry_panel_width = get_left_panel_width;
|
||||
_get_zoom_factor = get_zoom_factor;
|
||||
_show_in_net_change = backing<bool>(nameof(show_in_net_change));
|
||||
_show_in_net_change_dollars = backing<bool>(nameof(show_in_net_change_dollars));
|
||||
_show_in_net_change_ticks = backing<bool>(nameof(show_in_net_change_ticks));
|
||||
|
||||
_default_account = user_backing<AccountTuple?>(nameof(default_account));
|
||||
_default_algo = user_backing<AlgoDefinitionVM>(nameof(default_algo));
|
||||
// be sure to initialize backings before this measure_row_height call
|
||||
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
|
||||
_orders.font_size_changed += on_font_size_changed;
|
||||
@@ -86,10 +97,50 @@ namespace mbts.spark {
|
||||
_ask.font_size_changed += on_font_size_changed;
|
||||
_last.font_size_changed += on_font_size_changed;
|
||||
_piq.font_size_changed += on_font_size_changed;
|
||||
|
||||
// accounts
|
||||
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
|
||||
CustomSort = Comparer<AccountTuple>.Default.to_nullable().to_non_generic()
|
||||
};
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
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;
|
||||
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
|
||||
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
|
||||
_try_select_desired_account();
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, connect_favorite_algo),
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(update => {
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, algo_factory);
|
||||
_try_select_desired_algo();
|
||||
})
|
||||
) { dispose_resources };
|
||||
// local methods
|
||||
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) => _update.execute();
|
||||
void on_favorite_algo_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => _update.execute();
|
||||
IDisposable connect_favorite_algo(FavoriteAlgoItem item) {
|
||||
item.PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
void dispose_resources() {
|
||||
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)
|
||||
c.PropertyChanged -= on_prop_changed;
|
||||
}
|
||||
AccountTuple? account_factory(KeyValuePair<uint, string> kv) => new AccountTuple(kv.Key, kv.Value);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
private void _badge_style_Changed(LadderSettings.BadgeStyle oldval, LadderSettings.BadgeStyle newval) {
|
||||
if (newval == LadderSettings.BadgeStyle.Alternate && _orders.font_size == 10) {
|
||||
_orders.font_size = 7;
|
||||
@@ -110,6 +161,27 @@ 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>();
|
||||
// time in force
|
||||
public TimeInForce default_time_in_force { get => _default_time_in_force; set => Set(ref _default_time_in_force, value); }
|
||||
public IEnumerable<TimeInForce> time_in_force_options => EnumHelper.get_values_except(TimeInForce.Undefined);
|
||||
// account
|
||||
public AccountTuple? default_account { get => _default_account.Value; set => _default_account.Value = value; }
|
||||
public ICollectionView<AccountTuple?> accounts_view { get; }
|
||||
// algo
|
||||
public AlgoDefinitionVM default_algo { get => _default_algo.Value; set => _default_algo.Value = value; }
|
||||
public IReadOnlyObservableCollection<AlgoDefinitionVM> algo_definitions => _algo_definitions;
|
||||
// left-panel (components)
|
||||
public bool auto_clear { get => _auto_clear; set => Set(ref _auto_clear, value); }
|
||||
public double order_entry_panel_width { get => _order_entry_panel_width; set => Set(ref _order_entry_panel_width, value); }
|
||||
public double zoom_factor { get => _zoom_factor; set => Set(ref _zoom_factor, value); }
|
||||
// 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; }
|
||||
|
||||
public double row_height => _row_height.Value;
|
||||
@@ -175,88 +247,133 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public bool show_in_net_change {
|
||||
get => _show_in_net_change.Value;
|
||||
set {
|
||||
// when unchecking, clear out dollars and ticks
|
||||
if (!value) {
|
||||
show_in_net_change_dollars = false;
|
||||
show_in_net_change_ticks = false;
|
||||
}
|
||||
// when checking, by defalt check dollars
|
||||
if (value) {
|
||||
show_in_net_change_dollars = true;
|
||||
}
|
||||
_show_in_net_change.Value = value;
|
||||
}
|
||||
}
|
||||
public bool show_in_net_change_dollars {
|
||||
get => _show_in_net_change_dollars.Value;
|
||||
set => _show_in_net_change_dollars.Value = value;
|
||||
}
|
||||
|
||||
public bool show_in_net_change_ticks {
|
||||
get => _show_in_net_change_ticks.Value;
|
||||
set => _show_in_net_change_ticks.Value = value;
|
||||
}
|
||||
|
||||
|
||||
public string key => GridSettingsTemplateKeys.Ladder;
|
||||
|
||||
IEnumerable<INotifyPropertyChanged> _observable_components {
|
||||
get {
|
||||
// columns
|
||||
yield return _orders;
|
||||
yield return _bid;
|
||||
yield return _price;
|
||||
yield return _ask;
|
||||
yield return _last;
|
||||
yield return _piq;
|
||||
yield return _volume;
|
||||
yield return _volume.general;
|
||||
// market break lines
|
||||
yield return _bid_mkt_line;
|
||||
yield return _ask_mkt_line;
|
||||
yield return _mid_mkt_line;
|
||||
yield return _session_low_mkt_line;
|
||||
yield return _session_high_mkt_line;
|
||||
yield return _session_range_fill;
|
||||
// left panel
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _day_visibility;
|
||||
yield return _high_visibility;
|
||||
yield return _low_visibility;
|
||||
yield return _open_visibility;
|
||||
yield return _settle_visibility;
|
||||
yield return _volume_visibility;
|
||||
yield return _account_visibility;
|
||||
yield return _tif_visibility;
|
||||
yield return _order_type_visibility;
|
||||
yield return _account_position_visibility;
|
||||
yield return _day_position_visibility;
|
||||
yield return _size_preset_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
yield return _clear_visibility;
|
||||
yield return _cancel_buys_visibility;
|
||||
yield return _cancel_sells_visibility;
|
||||
yield return _cancel_all_visibility;
|
||||
// other
|
||||
yield return size_display;
|
||||
yield return favorite_algos_vm;
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(LadderSettings settings) {
|
||||
_current = settings;
|
||||
badge_style = settings.badge_style;
|
||||
alternate_order_size = settings.alternate_order_size;
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
_ask.assign_from(_current.ask_column);
|
||||
_price.assign_from(_current.price_column);
|
||||
_volume.assign_from(_current.volume_column);
|
||||
_last.assign_from(_current.last_column);
|
||||
_piq.assign_from(_current.piq_column);
|
||||
_bid_mkt_line.assign_from(_current.bid_break_line);
|
||||
_ask_mkt_line.assign_from(_current.ask_break_line);
|
||||
_mid_mkt_line.assign_from(_current.mid_break_line);
|
||||
_session_low_mkt_line.assign_from(_current.session_low_line);
|
||||
_session_high_mkt_line.assign_from(_current.session_high_line);
|
||||
_session_range_fill.assign_from(_current.session_range_fill);
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_day_visibility.show = settings.day_visible;
|
||||
_high_visibility.show = settings.high_visible;
|
||||
_low_visibility.show = settings.low_visible;
|
||||
_open_visibility.show = settings.open_visible;
|
||||
_settle_visibility.show = settings.settle_visible;
|
||||
_volume_visibility.show = settings.volume_visible;
|
||||
_account_visibility.show = settings.account_visible;
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_day_position_visibility.show = settings.day_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
_cancel_buys_visibility.show = settings.cancel_buys_visible;
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
// do not read the order entry panel width here from settings
|
||||
// because it could have changed from the UI
|
||||
// there might be a better way of doing this, like how
|
||||
// does the widths of the ladder grid column pull this off?
|
||||
show_in_net_change = settings.net_change != LadderSettings.NetChangeTerms.None;
|
||||
show_in_net_change_dollars = settings.net_change == LadderSettings.NetChangeTerms.Dollars;
|
||||
show_in_net_change_ticks = settings.net_change == LadderSettings.NetChangeTerms.Ticks;
|
||||
using (_update.defer_execution()) {
|
||||
_current = settings;
|
||||
badge_style = settings.badge_style;
|
||||
alternate_order_size = settings.alternate_order_size;
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
mini_ob.load_from(settings.mini_ob.value);
|
||||
// columns
|
||||
_orders.assign_from(_current.orders_column);
|
||||
_bid.assign_from(_current.bid_column);
|
||||
_ask.assign_from(_current.ask_column);
|
||||
_price.assign_from(_current.price_column);
|
||||
_volume.assign_from(_current.volume_column);
|
||||
_last.assign_from(_current.last_column);
|
||||
_piq.assign_from(_current.piq_column);
|
||||
// market break lines
|
||||
_bid_mkt_line.assign_from(_current.bid_break_line);
|
||||
_ask_mkt_line.assign_from(_current.ask_break_line);
|
||||
_mid_mkt_line.assign_from(_current.mid_break_line);
|
||||
_session_low_mkt_line.assign_from(_current.session_low_line);
|
||||
_session_high_mkt_line.assign_from(_current.session_high_line);
|
||||
_session_range_fill.assign_from(_current.session_range_fill);
|
||||
// left-panel (defaults)
|
||||
default_time_in_force = settings.default_time_in_force;
|
||||
// - account
|
||||
_desired_account = settings.default_account;
|
||||
_try_select_desired_account(true);
|
||||
// - algo
|
||||
_desired_algo = settings.default_algo;
|
||||
_try_select_desired_algo(true);
|
||||
// - components
|
||||
auto_clear = settings.auto_clear;
|
||||
order_entry_panel_width = settings.order_entry_panel_initial_width;
|
||||
zoom_factor = settings.zoom_factor;
|
||||
// left-panel (show/hide)
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_day_visibility.show = settings.day_visible;
|
||||
_high_visibility.show = settings.high_visible;
|
||||
_low_visibility.show = settings.low_visible;
|
||||
_open_visibility.show = settings.open_visible;
|
||||
_settle_visibility.show = settings.settle_visible;
|
||||
_volume_visibility.show = settings.volume_visible;
|
||||
_account_visibility.show = settings.account_visible;
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
_account_position_visibility.show = settings.account_position_visible;
|
||||
_day_position_visibility.show = settings.day_position_visible;
|
||||
_size_preset_visibility.show = settings.size_presets_visible;
|
||||
_clear_visibility.show = settings.clear_visible;
|
||||
_auto_clear_visibility.show = settings.auto_clear_visible;
|
||||
_cancel_buys_visibility.show = settings.cancel_buys_visible;
|
||||
_cancel_sells_visibility.show = settings.cancel_sells_visible;
|
||||
_cancel_all_visibility.show = settings.cancel_all_visible;
|
||||
price_format = settings.net_change;
|
||||
// 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();
|
||||
clone.size_display = size_display.create();
|
||||
clone.calculator_buttons = size_presets.Select(vm => (int)vm.value).ToArray();
|
||||
// columns, row height (the latter determined by columns font size)
|
||||
clone.orders_column = _orders.get_settings();
|
||||
clone.bid_column = _bid.get_settings();
|
||||
clone.price_column = _price.get_settings();
|
||||
@@ -264,13 +381,28 @@ namespace mbts.spark {
|
||||
clone.volume_column = _volume.get_settings();
|
||||
clone.last_column = _last.get_settings();
|
||||
clone.piq_column = _piq.get_settings();
|
||||
clone.row_height = LadderRender.measure_row_height(clone);
|
||||
// market break lines
|
||||
clone.bid_break_line = _bid_mkt_line.get_settings();
|
||||
clone.ask_break_line = _ask_mkt_line.get_settings();
|
||||
clone.mid_break_line = _mid_mkt_line.get_settings();
|
||||
clone.session_low_line = _session_low_mkt_line.get_settings();
|
||||
clone.session_high_line = _session_high_mkt_line.get_settings();
|
||||
clone.session_range_fill = _session_range_fill.get_settings();
|
||||
clone.row_height = LadderRender.measure_row_height(clone);
|
||||
// left-panel (defaults)
|
||||
clone.default_time_in_force = default_time_in_force;
|
||||
// - account
|
||||
if (default_account.HasValue)
|
||||
clone.default_account = default_account.Value.id;
|
||||
// - algo
|
||||
clone.default_algo = default_algo != null ?
|
||||
AlgoID.from_algo_key(default_algo.key) :
|
||||
(_desired_algo ?? LadderSettings.Default.default_algo);
|
||||
// - components
|
||||
clone.auto_clear = auto_clear;
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.day_visible = _day_visibility.show;
|
||||
clone.high_visible = _high_visibility.show;
|
||||
clone.low_visible = _low_visibility.show;
|
||||
@@ -288,25 +420,23 @@ namespace mbts.spark {
|
||||
clone.cancel_buys_visible = _cancel_buys_visibility.show;
|
||||
clone.cancel_sells_visible = _cancel_sells_visibility.show;
|
||||
clone.cancel_all_visible = _cancel_all_visibility.show;
|
||||
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
|
||||
clone.zoom_slider_visible = _zoom_slider_visbility.show;
|
||||
clone.zoom_factor = _get_zoom_factor();
|
||||
clone.net_change = _create_net_change_terms(show_in_net_change_dollars, show_in_net_change_ticks);
|
||||
clone.net_change = price_format;
|
||||
// safety checks
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
return clone;
|
||||
|
||||
LadderSettings.NetChangeTerms _create_net_change_terms(bool dollars, bool ticks) {
|
||||
if (dollars) {
|
||||
return LadderSettings.NetChangeTerms.Dollars;
|
||||
}
|
||||
if (ticks) {
|
||||
return LadderSettings.NetChangeTerms.Ticks;
|
||||
}
|
||||
return LadderSettings.NetChangeTerms.None;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Dispose() => favorite_algos_vm.Dispose();
|
||||
public IDisposable defer_updates() => _update.defer_execution();
|
||||
|
||||
IDisposable IObservable<LadderSettings>.Subscribe(IObserver<LadderSettings> observer) {
|
||||
result_changed += notify;
|
||||
return Disposable.Create(() => result_changed -= notify);
|
||||
void notify() => observer.OnNext(get_result());
|
||||
}
|
||||
|
||||
LadderSettings IValueFactory<LadderSettings>.create() => get_result();
|
||||
void IValueFactory<LadderSettings>.load_from(LadderSettings value) => load(value);
|
||||
|
||||
IMutableValueWrapperVM<uint> _create_size_preset_vm() => _create_size_preset_vm(0);
|
||||
IMutableValueWrapperVM<uint> _create_size_preset_vm(uint init) {
|
||||
@@ -314,6 +444,20 @@ namespace mbts.spark {
|
||||
vm.PropertyChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
|
||||
return vm;
|
||||
}
|
||||
|
||||
void _try_select_desired_account(bool bypass_setter = false) {
|
||||
if (_desired_account.HasValue && (!_default_account.setter_called || bypass_setter) && _accounts.TryGetValue(_desired_account.Value, out var acc_tup)) {
|
||||
_desired_account = null;
|
||||
_default_account.bypass_setter(acc_tup);
|
||||
}
|
||||
}
|
||||
|
||||
void _try_select_desired_algo(bool bypass_setter = false) {
|
||||
if (_desired_algo.HasValue && (!_default_algo.setter_called || bypass_setter) && _algo_definitions.TryGetValue(AlgoID.to_algo_key(_desired_algo.Value), out var vm)) {
|
||||
_desired_algo = null;
|
||||
_default_algo.bypass_setter(vm);
|
||||
}
|
||||
}
|
||||
|
||||
public class LadderColumnSettingsVM : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly Property<double> _font_size;
|
||||
@@ -323,11 +467,13 @@ namespace mbts.spark {
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
readonly DelegateProperty<string> _font_display_name;
|
||||
readonly Property<bool> _show;
|
||||
readonly bool _can_toggle_visibility;
|
||||
readonly string _template_key;
|
||||
uint _width;
|
||||
|
||||
public LadderColumnSettingsVM(string name, bool can_toggle_visibility = false) {
|
||||
public LadderColumnSettingsVM(string name, string template_key = null, bool can_toggle_show = false) {
|
||||
this.name = name;
|
||||
_can_toggle_visibility = can_toggle_visibility;
|
||||
_template_key = template_key;
|
||||
this.can_toggle_show = can_toggle_show;
|
||||
foreground_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
background_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
_show = backing(nameof(show), true);
|
||||
@@ -338,26 +484,35 @@ 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;
|
||||
RaisePropertyChanged(nameof(show_details));
|
||||
});
|
||||
}
|
||||
|
||||
public event Action<double, double> font_size_changed;
|
||||
|
||||
public string name { get; }
|
||||
public Typeface typeface { get => _typeface.Value; set => _typeface.Value = value; }
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public string font_display_name => _font_display_name.Value;
|
||||
public double font_size { get => _font_size.Value; set => _font_size.Value = value; }
|
||||
public uint width { get; set; }
|
||||
public uint width { get => _width; set => Set(ref _width, value); }
|
||||
public bool show { get => _show.Value; set => _show.Value = value; }
|
||||
public bool can_toggle_show { get; }
|
||||
public bool show_details { get; private set; }
|
||||
public Color foreground { get => _foreground.Value; set => _foreground.Value = value; }
|
||||
public Color background { get => _background.Value; set => _background.Value = value; }
|
||||
public ObservableCollection<ColorPaletteVM> foreground_palettes { get; }
|
||||
public ObservableCollection<ColorPaletteVM> background_palettes { get; }
|
||||
public event Action<double, double> font_size_changed;
|
||||
|
||||
public virtual string sample_text => "999-56789.01234";
|
||||
string ITemplateSelectorKey.key => _can_toggle_visibility ? ui.spark.LadderSettingsView.ColumnVisibilityToggleTemplateKey : null;
|
||||
|
||||
public ICommand toggle_show_details_cmd { get; }
|
||||
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
public void assign_from(LadderColumnSettings settings) {
|
||||
typeface = new Typeface(
|
||||
@@ -370,7 +525,6 @@ namespace mbts.spark {
|
||||
show = settings.show;
|
||||
foreground = settings.foreground.rgb_color();
|
||||
background = settings.background.rgb_color();
|
||||
RaisePropertyChanged(nameof(width), nameof(show));
|
||||
}
|
||||
|
||||
public LadderColumnSettings get_settings() {
|
||||
@@ -395,7 +549,7 @@ namespace mbts.spark {
|
||||
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
|
||||
public Brush badge_background { get; } = Brushes.White;
|
||||
|
||||
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnSampleTemplateKey;
|
||||
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnTemplateKey;
|
||||
}
|
||||
|
||||
public class LadderMarketBreakLineVM : ViewModelBase {
|
||||
@@ -463,7 +617,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", true);
|
||||
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", LadderSettingsView.VolumeColumnTemplateKey, true);
|
||||
public VolumeAtPriceAnimationVM buy { get; }
|
||||
public VolumeAtPriceAnimationVM sell { get; }
|
||||
public VolumeAtPriceAnimationVM passive { get; }
|
||||
@@ -566,17 +720,15 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FavoriteAlgosVM : DisposableViewModelBase, IValueFactoryVM<LadderFavoriteAlgosSettings> {
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
|
||||
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
|
||||
int _column_count = 2;
|
||||
|
||||
public FavoriteAlgosVM(OrdersModel model) {
|
||||
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
|
||||
_algo_definitions = algo_definitions;
|
||||
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
|
||||
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
|
||||
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
|
||||
_dispose = model.algo_model.definitions.OnDispatcher().Subscribe(update => {
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, d => AlgoDefinitionVM.create(model, d, false));
|
||||
});
|
||||
}
|
||||
|
||||
public ObservableCollection<FavoriteAlgoItem> items { get; }
|
||||
@@ -596,11 +748,17 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public LadderFavoriteAlgosSettings create() =>
|
||||
new LadderFavoriteAlgosSettings(_raw_items.Where(fai => fai.algo_definition != null).Select(fai => fai.algo_definition.key).ToArray(), column_count);
|
||||
new LadderFavoriteAlgosSettings(_raw_items
|
||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||
.Select(k => k.Value)
|
||||
.ToArray(),
|
||||
column_count);
|
||||
|
||||
FavoriteAlgoItem _item_factory() =>
|
||||
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic(),
|
||||
Filter = obj => obj is AlgoDefinitionVM vm && vm.key.is_persisted
|
||||
});
|
||||
}
|
||||
|
||||
@@ -611,7 +769,7 @@ namespace mbts.spark {
|
||||
|
||||
protected FavoriteAlgoItem() { }
|
||||
|
||||
public FavoriteAlgoItem(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
|
||||
public FavoriteAlgoItem(IObservableCollection<AlgoDefinitionVM> algo_definitions, ICollectionView<AlgoDefinitionVM> algo_definitions_view) {
|
||||
selector = new KeyedItemSelector<AlgoDefinitionVM, AlgoKey>(this, nameof(algo_definition), algo_definitions, d => d.key);
|
||||
this.algo_definitions_view = algo_definitions_view;
|
||||
}
|
||||
@@ -624,4 +782,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using bts.api;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -36,32 +34,28 @@ namespace mbts.spark {
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
double _position, _day_position;
|
||||
bool _show_account_position, _show_day_position;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
|
||||
public static LadderVM create(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
|
||||
var non_shared_wsm = settings_manager.as_non_shared();
|
||||
return new LadderVM(bus, model, settings_manager, non_shared_wsm, init, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
}
|
||||
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model) {
|
||||
var default_settings = LadderSettings.Default;
|
||||
return create_readonly(bus, model, new WindowSettingsManager<LadderSettings>.DisconnectedImpl(default_settings), default_settings);
|
||||
}
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, IObservable<LadderSettings> settings) =>
|
||||
create_readonly(bus, model, LadderSettings.Default, settings);
|
||||
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) =>
|
||||
new LadderVM(bus, model, settings_manager, settings_manager.as_non_shared(), init);
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings);
|
||||
|
||||
LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, IWindowSettingsManagerNonShared<LadderSettings> non_shared_wsm, LadderSettings init, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
LadderVM(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
current_settings = init;
|
||||
this.settings = settings;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
_settings_manager = non_shared_wsm;
|
||||
this.order_properties_vm = order_properties_vm;
|
||||
_high = backing(nameof(high), double.NaN);
|
||||
_low = backing(nameof(low), double.NaN);
|
||||
@@ -81,12 +75,14 @@ namespace mbts.spark {
|
||||
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
|
||||
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), false);
|
||||
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
|
||||
_status = backing(nameof(status_message), "");
|
||||
_status_brush = backing(nameof(status_background), _transparent_status_brush);
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
/// first things first: assign our <see cref="current_settings"/>
|
||||
(s => current_settings = s),
|
||||
// brush props
|
||||
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
|
||||
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
|
||||
@@ -113,6 +109,19 @@ namespace mbts.spark {
|
||||
if (Set(ref _show_account_position, s.account_position_visible, nameof(show_account_position)) | Set(ref _show_day_position, s.day_position_visible, nameof(show_day_position))) {
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}
|
||||
size_calculator.buttons = s.calculator_buttons;
|
||||
_auto_clear.set(s.auto_clear);
|
||||
// column widths
|
||||
_orders_column_width.set(s.orders_column.width);
|
||||
_bid_column_width.set(s.bid_column.width);
|
||||
_price_column_width.set(s.price_column.width);
|
||||
_ask_column_width.set(s.ask_column.width);
|
||||
_last_column_width.set(s.last_column.width);
|
||||
_piq_column_width.set(s.piq_column.width);
|
||||
_volume_column_width.set(s.volume_column.general.width);
|
||||
// left panel/zoom
|
||||
_left_panel_width.set(s.order_entry_panel_initial_width);
|
||||
_zoom_factor.set(s.zoom_factor);
|
||||
}
|
||||
};
|
||||
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
@@ -128,30 +137,13 @@ namespace mbts.spark {
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
show_book_command = new DelegateCommand(_show_book);
|
||||
show_ticket_command = new DelegateCommand(_show_ticket);
|
||||
settings_command = new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<LadderSettings, LadderSettingsVM>(
|
||||
settings_manager, s => s.ladder, UserSettings.FileExtensions.LADDER,
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(model.order_gateway, security.ContractCase == Security.ContractOneofCase.Synthetic, () => left_panel_width, () => zoom_factor);
|
||||
svm.load(s);
|
||||
return svm;
|
||||
}, s => s.with_window_specifics(get_save_state().get_window_specifics()));
|
||||
var dialog = new ui.spark.SimpleGridSettingsDialog {
|
||||
DataContext = vm,
|
||||
Title = $"Ladder Settings - '{security}'"
|
||||
};
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}, -500, -500);
|
||||
settings_command = new DelegateCommand(() => request_settings_dialog?.Invoke());
|
||||
|
||||
orders = new ObservableCollection<LadderOrderVM>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(settings_observer),
|
||||
settings_command,
|
||||
non_shared_wsm,
|
||||
settings.OnDispatcher().Subscribe(settings_observer),
|
||||
get_order_properties_editor_hooks(),
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
// we need to hook this up to model.orders which is filtered for just the security
|
||||
model.orders.OnDispatcher().Subscribe(working => {
|
||||
@@ -176,7 +168,7 @@ namespace mbts.spark {
|
||||
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
||||
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
||||
} else {
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, settings.Select(s => s.badge_style).OnDispatcher());
|
||||
_slot_price_vs_orders[slot_price_target] = slot_vm;
|
||||
}
|
||||
|
||||
@@ -243,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;
|
||||
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
|
||||
@@ -285,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;
|
||||
@@ -294,7 +286,7 @@ namespace mbts.spark {
|
||||
public double day_position => _day_position;
|
||||
public ulong volume => _volume.Value;
|
||||
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
|
||||
/// Important: <see cref="order_properties_vm"/> can be null.
|
||||
public OrderPropertiesEditorVM order_properties_vm { get; }
|
||||
public ObservableCollection<LadderOrderVM> orders { get; }
|
||||
@@ -302,11 +294,11 @@ namespace mbts.spark {
|
||||
public ICommand send_order_command { get; }
|
||||
public ICommand show_book_command { get; }
|
||||
public ICommand show_ticket_command { get; }
|
||||
public ICommand settings_command { get; }
|
||||
public Brush bid_background => _bid_background.view;
|
||||
public Brush bid_foreground => _bid_foreground.view;
|
||||
public Brush ask_background => _ask_background.view;
|
||||
public Brush ask_foreground => _ask_foreground.view;
|
||||
public DialogCommand settings_command { get; }
|
||||
public bool show_day => _show_day.view;
|
||||
public bool show_high => _show_high.view;
|
||||
public bool show_low => _show_low.view;
|
||||
@@ -346,8 +338,10 @@ 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 => _settings_manager.subscribe_current();
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
var order = op.order;
|
||||
double p = order.price();
|
||||
@@ -366,10 +360,26 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public LadderSettings get_save_state() {
|
||||
var state = _settings_manager.current_settings();
|
||||
state = state.with_window_specifics(new LadderSettings.WindowSpecifics(left_panel_width, orders_column_width, bid_column_width, price_column_width, ask_column_width, last_column_width, volume_column_width, zoom_factor, piq_column_width));
|
||||
state.use_defaults = _settings_manager.instance_settings == null;
|
||||
return state;
|
||||
var rv = current_settings.with_window_specifics(
|
||||
new LadderSettings.WindowSpecifics(
|
||||
left_panel_width,
|
||||
orders_column_width,
|
||||
bid_column_width,
|
||||
price_column_width,
|
||||
ask_column_width,
|
||||
last_column_width,
|
||||
volume_column_width,
|
||||
zoom_factor,
|
||||
piq_column_width));
|
||||
rv.auto_clear = auto_clear;
|
||||
if (order_properties_vm != null) {
|
||||
rv.default_time_in_force = order_properties_vm.selected_time_in_force;
|
||||
if (order_properties_vm.selected_account.HasValue)
|
||||
rv.default_account = order_properties_vm.selected_account.Value.id;
|
||||
if (order_properties_vm.selected_algo_definition != null)
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void key_down(KeyEventArgs e) {
|
||||
@@ -384,20 +394,24 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void send_order((double price, Side? side, MouseButton buton) args) {
|
||||
if (args.buton == MouseButton.Middle) return;
|
||||
var size_from_alternate = args.buton == MouseButton.Right;
|
||||
if (order_properties_vm == null)
|
||||
public void 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 s = _settings_manager.current_settings();
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
|
||||
return;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -337,13 +338,28 @@ namespace mbts.spark {
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
if (bs.HasValue) {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
return;
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
show_mbo_popup(point).ignore();
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
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);
|
||||
@@ -678,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();
|
||||
}
|
||||
}
|
||||
@@ -846,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 {
|
||||
@@ -139,6 +137,7 @@ namespace mbts.spark {
|
||||
// properly Dispose of it later when the popup is closed.
|
||||
DataContext = obvm,
|
||||
Child = view,
|
||||
// StaysOpen true for debugging
|
||||
StaysOpen = false,
|
||||
PlacementTarget = popup_placement_target,
|
||||
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom,
|
||||
@@ -155,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);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -10,10 +10,15 @@ namespace mbts.ui.spark {
|
||||
/// </summary>
|
||||
partial class LadderSettingsView : UserControl {
|
||||
public const string
|
||||
OrdersColumnSampleTemplateKey = nameof(OrdersColumnSampleTemplateKey),
|
||||
ColumnVisibilityToggleTemplateKey = nameof(ColumnVisibilityToggleTemplateKey),
|
||||
OrdersColumnTemplateKey = nameof(OrdersColumnTemplateKey),
|
||||
VolumeColumnTemplateKey = nameof(VolumeColumnTemplateKey),
|
||||
LastColumnTemplateKey = nameof(LastColumnTemplateKey),
|
||||
FavoriteAlgoAddButtonTemplateKey = nameof(FavoriteAlgoAddButtonTemplateKey);
|
||||
|
||||
public static readonly IMultiValueConverter LadderColumnToggleShowToolTipConverter =
|
||||
ValueConverter.create<bool, bool, string, string>((can_toggle_show, show, col_name) =>
|
||||
can_toggle_show ? $"{(show ? "Hide" : "Show")} {col_name}" : $"{col_name} cannot be {(show ? "hidden" : "shown")}.");
|
||||
|
||||
public LadderSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
<converters:ResponsiveTextConverter x:Key="OpenTextConverter" LongText="Open:" ShortText="O:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="SettleTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="VolumeTextConverter" LongText="Volume:" ShortText="V:" Threshold="99" />
|
||||
<converters:DoubleSubtractionConverter x:Key="DoubleSubtractionConverter" Value="4" />
|
||||
<converters:DoubleSubtractionConverter x:Key="HeightDoubleSubtractionConverter" Value="6" />
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
<Setter Property="Background" Value="{mbts:BTSColor header}"/>
|
||||
<Setter Property="Foreground" Value="{mbts:BTSColor header_text}"/>
|
||||
@@ -288,8 +290,11 @@
|
||||
/>
|
||||
</Grid.LayoutTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="60" MaxWidth="150"/>
|
||||
<ColumnDefinition
|
||||
Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="{x:Static spark:LadderView.MinLeftPanelWidth}"
|
||||
MaxWidth="{x:Static spark:LadderView.MaxLeftPanelWidth}"
|
||||
/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -309,11 +314,14 @@
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<StackPanel x:Name="_slider_stack_panel" Margin="5,5,5,0">
|
||||
<Slider x:Name="_ui_scaler" Value="{Binding zoom_factor}"
|
||||
Minimum="{StaticResource MinZoomFactor}"
|
||||
Maximum="{StaticResource MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Slider
|
||||
x:Name="_ui_scaler"
|
||||
Value="{Binding zoom_factor}"
|
||||
Minimum="{x:Static spark:LadderView.MinZoomFactor}"
|
||||
Maximum="{x:Static spark:LadderView.MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</StackPanel>
|
||||
<Grid Margin="2,4,2,0">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -573,7 +581,9 @@
|
||||
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||
<Setter Property="Foreground" Value="{Binding foreground}"/>
|
||||
<Setter Property="TextLines" Value="{Binding text_lines}" />
|
||||
<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>
|
||||
<!--<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>-->
|
||||
<Setter Property="Width" Value="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type spark:LadderOrdersHost}}, Converter={StaticResource DoubleSubtractionConverter}}" />
|
||||
<Setter Property="Height" Value="{Binding grid_row_height, Converter={StaticResource HeightDoubleSubtractionConverter}}" />
|
||||
<Setter Property="HasMultipleOrdersAtLevel" Value="{Binding has_multiple_orders_at_same_level}" />
|
||||
<Setter Property="IsHedgeOrder" Value="{Binding is_hedge_order}" />
|
||||
<Setter Property="AlgoType" Value="{Binding algo_type}" />
|
||||
|
||||
@@ -11,6 +11,12 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderView : UserControl {
|
||||
public const double
|
||||
MinLeftPanelWidth = 60,
|
||||
MaxLeftPanelWidth = 150,
|
||||
MinZoomFactor = 0.5,
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", total_pos_text = "total";
|
||||
@@ -40,8 +46,6 @@ namespace mbts.spark {
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
readonly double _max_zoom, _min_zoom;
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
InitializeComponent();
|
||||
_column_headers_presenter.Columns = new GridViewColumnCollection();
|
||||
@@ -50,19 +54,6 @@ namespace mbts.spark {
|
||||
this.vm = vm;
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
if (TryFindResource("MaxZoomFactor") is double found_max) {
|
||||
_max_zoom = found_max;
|
||||
} else {
|
||||
// is there a better way of not specifying the constant twice ?
|
||||
// once in the resource dictionary here and again in the xaml, seems like there has to be a better way
|
||||
_max_zoom = 1.5;
|
||||
}
|
||||
// ditto
|
||||
if (TryFindResource("MinZoomFactor") is double found_min) {
|
||||
_min_zoom = found_min;
|
||||
} else {
|
||||
_min_zoom = 0.5;
|
||||
}
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
|
||||
this.PreviewMouseDown += LadderView_PreviewMouseDown;
|
||||
@@ -78,7 +69,7 @@ namespace mbts.spark {
|
||||
var delta = (e.Delta > 0) ? 0.1 : -0.1;
|
||||
var proposed_scaler_value = _ui_scaler.Value + delta;
|
||||
// Here we enforce the same contraints that are on the gui slider regarding min and max
|
||||
if (_min_zoom < proposed_scaler_value && proposed_scaler_value < _max_zoom) {
|
||||
if (MinZoomFactor < proposed_scaler_value && proposed_scaler_value < MaxZoomFactor) {
|
||||
_ui_scaler.Value = proposed_scaler_value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +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}"/>
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
|
||||
@@ -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}">
|
||||
@@ -99,7 +98,16 @@
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
|
||||
<!-- We added this IsSelected after seeing some strangeness when bringing up the Mini OB,
|
||||
then clicking the attach button, then performing a click to modify a param in the popup, the grid
|
||||
would lose the appearance of its selected row. I think this was actually a result of a bad path
|
||||
in the SelectionHighlight expression below, but that leaves me questioning how if it was bad (and
|
||||
it was, I confirmed in Snoop) did the Grid successfully show a highlighted row in the first place.
|
||||
|
||||
At any rate, I have a strong suspicion this Setter is no longer necessary, but I am too exhausted
|
||||
and scared to remove it at this point.
|
||||
-->
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
<DataTemplate x:Key="{x:Static sp:MiniOrderBookView.RowTemplateKey}">
|
||||
<sp:MiniOrderBookRowView/>
|
||||
@@ -121,10 +129,9 @@
|
||||
Rows="{Binding rows}"
|
||||
RowStyle="{StaticResource {x:Static sp:MiniOrderBookView.RowStyleKey}}"
|
||||
RowTemplate="{StaticResource {x:Static sp:MiniOrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
RowHeight="20"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurity> filter { get; }
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ namespace mbts.spark {
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
Add(security_column = new SecurityColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, SecurityImmutable, SecurityCriteria>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, SecurityImmutable, SecurityCriteria>(
|
||||
order_securities,
|
||||
ort => ort.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
ort => ort.order.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
nameof(OrderBookRowVM.security),
|
||||
@@ -85,9 +85,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Symbol,
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ort => ort.security?.Symbol ?? string.Empty,
|
||||
ort => ort.order.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)
|
||||
),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
@@ -98,8 +98,8 @@ namespace mbts.spark {
|
||||
"Time",
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.created(),
|
||||
new TimeFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.created(),
|
||||
nameof(OrderBookRowVM.time_created)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.time_created)));
|
||||
@@ -109,8 +109,8 @@ namespace mbts.spark {
|
||||
"Last Change",
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_change(),
|
||||
new TimeFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_change(),
|
||||
nameof(OrderBookRowVM.last_change)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.last_change)));
|
||||
@@ -120,9 +120,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.User,
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
ort => ort.order.user(),
|
||||
ort => ort.order.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
_wrap_comparer(r => r.user)));
|
||||
@@ -132,9 +132,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Account,
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string, string[]>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
ort => ort.order.account(),
|
||||
ort => ort.order.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
nameof(OrderBookRowVM.account)),
|
||||
@@ -146,9 +146,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Exchange,
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Venue>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, Venue>(
|
||||
EnumHelper.get_values_except(Venue.Unset).ToList(),
|
||||
ort => ort.security.exchange_or_default(),
|
||||
ort => ort.order.security.exchange_or_default(),
|
||||
nameof(OrderBookRowVM.exchange)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.exchange)));
|
||||
@@ -158,9 +158,9 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, TimeInForce>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, TimeInForce>(
|
||||
EnumHelper.get_values<TimeInForce>().ToList(),
|
||||
ort => ort.order.time_in_force(),
|
||||
ort => ort.order.order.time_in_force(),
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
@@ -174,8 +174,8 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.badge_fill),
|
||||
nameof(OrderBookRowVM.display_name),
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityRootPair, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.order.algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
@@ -185,9 +185,9 @@ namespace mbts.spark {
|
||||
Columns.RootAlgoType,
|
||||
nameof(OrderBookRowVM.root_algo_badge_fill),
|
||||
nameof(OrderBookRowVM.root_algo_display_name),
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
_wrap_comparer(r => r.order_tuple.root.algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityRootPair, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root.algo),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
@@ -199,36 +199,36 @@ namespace mbts.spark {
|
||||
"Sub Type",
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderSubType>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
ort => ort.order.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
OrderBookGridView.SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Side>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, Side>(
|
||||
new[] { Side.Buy, Side.Sell },
|
||||
ort => ort.order.side(),
|
||||
ort => ort.order.order.side(),
|
||||
nameof(OrderBookRowVM.side))));
|
||||
// quantity
|
||||
Add(qty_column = new SizeColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// edit quantity column
|
||||
// not usually part of the main order book, but it's useful to add it
|
||||
// rather than debugging the popup window
|
||||
//Add(edit_qty_column = new EditableQtyColumn());
|
||||
// filled quantity
|
||||
Add(filled_qty_column = new FilledColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.price(),
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.price(),
|
||||
nameof(OrderBookRowVM.price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.price)));
|
||||
@@ -243,8 +243,8 @@ namespace mbts.spark {
|
||||
"Avg Price",
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.avg_price(),
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.avg_price(),
|
||||
nameof(OrderBookRowVM.avg_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.avg_price)));
|
||||
@@ -254,30 +254,30 @@ namespace mbts.spark {
|
||||
"Last Filled",
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.last_price(),
|
||||
new PriceMinMax<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_price(),
|
||||
nameof(OrderBookRowVM.last_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.last_price)));
|
||||
// piq
|
||||
Add(piq_column = new PiqColumn(
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
new UIntMinMax<OrderSecurityRootPair>(ort => ort.order.piq ?? uint.MaxValue, nameof(OrderBookRowVM.piq))));
|
||||
// status
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderStatus>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityRootPair, OrderStatus>(
|
||||
EnumHelper.get_values<OrderStatus>().ToList(),
|
||||
ort => ort.order.status(),
|
||||
ort => ort.order.order.status(),
|
||||
nameof(OrderBookRowVM.status)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.status)));
|
||||
// last message
|
||||
Add(last_message_column = new OrderBookLastMessageColumn(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_msg(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.last_msg(),
|
||||
nameof(OrderBookRowVM.last_msg))));
|
||||
// security key
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
@@ -285,8 +285,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.security_key().ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.security_key().ToString(),
|
||||
nameof(OrderBookRowVM.security_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.security_key)));
|
||||
@@ -296,8 +296,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.Key.ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.Key.ToString(),
|
||||
nameof(OrderBookRowVM.order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order_key)));
|
||||
@@ -307,8 +307,8 @@ namespace mbts.spark {
|
||||
"Parent Key",
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.ParentKey.ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.ParentKey.ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
@@ -318,8 +318,8 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
new RegexFilterVM<OrderSecurityRootPair>(
|
||||
ort => ort.order.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order.attached_target_order_key())));
|
||||
@@ -428,9 +428,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public Func<OrderSecurity, bool> get_filter() {
|
||||
public Func<OrderSecurityRootPair, bool> get_filter() {
|
||||
var active_filters = this.OfType<ICVOrderBookColumn>().Where(c => c.filter.active.GetValueOrDefault()).Select(c => c.filter).ToArray();
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurity, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurityRootPair, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
}
|
||||
|
||||
public OrderBookSortingData get_sorting_data() {
|
||||
@@ -531,7 +531,7 @@ namespace mbts.spark {
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
IDataGridColumnPersistentFilter<OrderSecurityRootPair, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
@@ -548,10 +548,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, T> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityRootPair, T> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -565,7 +565,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
IDataGridColumnPersistentFilter<OrderSecurityRootPair, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -575,7 +575,7 @@ namespace mbts.spark {
|
||||
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurity, Side[]> filter)
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -584,7 +584,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -593,7 +593,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -605,7 +605,7 @@ namespace mbts.spark {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurityRootPair, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -615,11 +615,11 @@ namespace mbts.spark {
|
||||
this.comparer = row_comparer;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, AlgoID[]> filter { get; }
|
||||
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -637,7 +637,7 @@ namespace mbts.spark {
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurity, string> filter)
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -646,10 +646,10 @@ namespace mbts.spark {
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, string> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityRootPair, string> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
@@ -659,7 +659,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class PiqColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurity, UIntMinMaxState> filter)
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, UIntMinMaxState> filter)
|
||||
: base(Columns.Piq, "PIQ", r => r.piq_text, DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.piq)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
@@ -674,7 +674,7 @@ namespace mbts.spark {
|
||||
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityRootPair, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
@@ -683,10 +683,10 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityRootPair, SecurityCriteria> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
|
||||
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurityRootPair> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -724,6 +724,33 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public class CheckColumn : FrameworkElementColumnBase<Columns> {
|
||||
readonly BindingBase _checked_binding;
|
||||
public CheckColumn(string checked_property) : base(Columns.Check) {
|
||||
id = key.ToString();
|
||||
Width = 30;
|
||||
_checked_binding = new Binding(checked_property);
|
||||
|
||||
var header_cb = new CheckBox();
|
||||
header_cb.ToolTip = "Select / Unselect All";
|
||||
header_cb.SetBinding(CheckBox.IsCheckedProperty, new Binding(nameof(OrderBookVM.all_rows_selected)));
|
||||
this.Header = header_cb;
|
||||
}
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var grid = new Grid() {
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
var cb = new CheckBox();
|
||||
cb.Name = "check_col";
|
||||
cb.SetBinding(CheckBox.IsCheckedProperty, _checked_binding);
|
||||
|
||||
grid.Children.Add(cb);
|
||||
return grid;
|
||||
}
|
||||
}
|
||||
|
||||
public class CancelColumn : FrameworkElementColumnBase<Columns> {
|
||||
readonly BindingBase _command_binding;
|
||||
|
||||
@@ -812,7 +839,10 @@ namespace mbts.spark {
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
attach_grid.Children.Add(attach_button);
|
||||
attach_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
|
||||
attach_button.Click += (s, e) => {
|
||||
popup.Child = create_popup_child();
|
||||
popup.IsOpen = !popup.IsOpen;
|
||||
};
|
||||
|
||||
var orderbook_vm_datacontext_binding = new Binding("DataContext");
|
||||
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
|
||||
@@ -829,84 +859,9 @@ namespace mbts.spark {
|
||||
|
||||
var row_vm_attach_command_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.attach_command)));
|
||||
row_vm_attach_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
|
||||
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
|
||||
attach_button.SetBinding(Button.CommandProperty, row_vm_attach_command_binding);
|
||||
|
||||
#region OrderParamsPanel
|
||||
// finally, set the order_params DataContext to that of the ContextMenu
|
||||
var order_params = AlgoEditorGridView.create_attach_view();
|
||||
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
|
||||
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
|
||||
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
|
||||
#endregion
|
||||
|
||||
#region Dialog Skirt
|
||||
var dialog_skirt_button_style =
|
||||
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
|
||||
Application.Current.TryFindResource("CommonButton") as Style ??
|
||||
Application.Current.TryFindResource(typeof(Button)) as Style;
|
||||
|
||||
// go find the OBVM on the ContextMenu to get the command
|
||||
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
|
||||
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
|
||||
// and Click event on a Button appropriately, but since the Button that will perform this
|
||||
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
|
||||
// we make use of this addition trick from John that allows for both of these things to happen.
|
||||
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
|
||||
// pure genious!
|
||||
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
|
||||
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
|
||||
};
|
||||
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
|
||||
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
|
||||
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
|
||||
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
|
||||
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
|
||||
// create a KeyedStyleSelector here I guess.
|
||||
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
|
||||
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
|
||||
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
|
||||
var awesome_command_style_key = "SendAttachButtonStyleKey";
|
||||
var cancel_style_key = "CancelButtonStyleKey";
|
||||
|
||||
var dialog_skirt = new DialogSkirt {
|
||||
RightItems = new[] {
|
||||
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
|
||||
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
|
||||
}};
|
||||
|
||||
var kss = new KeyedStyleSelector();
|
||||
kss.Map.Add(new StyleSelectorKeyValue {
|
||||
Key = awesome_command_style_key,
|
||||
Value = send_attached_button_style
|
||||
});
|
||||
kss.Map.Add(
|
||||
new StyleSelectorKeyValue {
|
||||
Key = cancel_style_key,
|
||||
Value = new Style(typeof(Button), dialog_skirt_button_style)
|
||||
});
|
||||
dialog_skirt.RightItemContainerStyleSelector = kss;
|
||||
|
||||
Grid.SetRow(dialog_skirt, 1);
|
||||
var grid = new Grid();
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.Children.Add(order_params);
|
||||
grid.Children.Add(dialog_skirt);
|
||||
|
||||
var border = new Border() {
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Name = "attach_border",
|
||||
Child = grid,
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
popup.Child = border;
|
||||
// set to true for debugging
|
||||
popup.StaysOpen = false;
|
||||
popup.PlacementTarget = attach_button;
|
||||
popup.Placement = PlacementMode.Bottom;
|
||||
@@ -914,7 +869,6 @@ namespace mbts.spark {
|
||||
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Detach Button
|
||||
@@ -950,6 +904,83 @@ namespace mbts.spark {
|
||||
the_whole_thing.Children.Add(popup);
|
||||
|
||||
return the_whole_thing;
|
||||
|
||||
UIElement create_popup_child() {
|
||||
#region OrderParamsPanel
|
||||
// finally, set the order_params DataContext to that of the ContextMenu
|
||||
var order_params = AlgoEditorGridView.create_attach_view();
|
||||
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.attach_vm)));
|
||||
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
|
||||
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
|
||||
#endregion
|
||||
|
||||
var dialog_skirt_button_style =
|
||||
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
|
||||
Application.Current.TryFindResource("CommonButton") as Style ??
|
||||
Application.Current.TryFindResource(typeof(Button)) as Style;
|
||||
|
||||
// go find the OBVM on the ContextMenu to get the command
|
||||
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
|
||||
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
|
||||
// and Click event on a Button appropriately, but since the Button that will perform this
|
||||
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
|
||||
// we make use of this addition trick from John that allows for both of these things to happen.
|
||||
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
|
||||
// pure genious!
|
||||
var send_attach_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.send_attached_orders_cmd))) {
|
||||
// set to true for debugging
|
||||
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
|
||||
};
|
||||
send_attach_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
|
||||
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
|
||||
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
|
||||
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
|
||||
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
|
||||
// create a KeyedStyleSelector here I guess.
|
||||
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
|
||||
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
|
||||
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, send_attach_button_binding));
|
||||
var awesome_command_style_key = "SendAttachButtonStyleKey";
|
||||
var cancel_style_key = "CancelButtonStyleKey";
|
||||
|
||||
var dialog_skirt = new DialogSkirt {
|
||||
RightItems = new[] {
|
||||
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
|
||||
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
|
||||
}
|
||||
};
|
||||
|
||||
var kss = new KeyedStyleSelector();
|
||||
kss.Map.Add(new StyleSelectorKeyValue {
|
||||
Key = awesome_command_style_key,
|
||||
Value = send_attached_button_style
|
||||
});
|
||||
kss.Map.Add(
|
||||
new StyleSelectorKeyValue {
|
||||
Key = cancel_style_key,
|
||||
Value = new Style(typeof(Button), dialog_skirt_button_style)
|
||||
});
|
||||
dialog_skirt.RightItemContainerStyleSelector = kss;
|
||||
|
||||
Grid.SetRow(dialog_skirt, 1);
|
||||
var grid = new Grid();
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.Children.Add(order_params);
|
||||
grid.Children.Add(dialog_skirt);
|
||||
|
||||
var border = new Border() {
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Name = "attach_border",
|
||||
Child = grid,
|
||||
};
|
||||
return border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,17 +1001,21 @@ namespace mbts.spark {
|
||||
public override FrameworkElement view_factory() {
|
||||
var popup = new System.Windows.Controls.Primitives.Popup();
|
||||
|
||||
#region Attach Button
|
||||
#region Update Button
|
||||
|
||||
// create the attach button and assign the menu to it
|
||||
// create the update button and assign the menu to it
|
||||
var update_button = new Button() {
|
||||
Name = "update_button",
|
||||
Content = "···",
|
||||
Margin = new Thickness(2),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Focusable = false
|
||||
Focusable = false,
|
||||
ToolTip = "Update"
|
||||
};
|
||||
update_button.Click += (s, e) => {
|
||||
popup.Child = create_popup_child();
|
||||
popup.IsOpen = !popup.IsOpen;
|
||||
};
|
||||
update_button.Click += (s, e) => { popup.IsOpen = !popup.IsOpen; };
|
||||
|
||||
var orderbook_vm_datacontext_binding = new Binding("DataContext");
|
||||
orderbook_vm_datacontext_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, _mini_or_full_ob_type, 1);
|
||||
@@ -995,89 +1030,14 @@ namespace mbts.spark {
|
||||
row_vm_update_command_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
|
||||
update_button.SetBinding(Button.CommandProperty, row_vm_update_command_binding);
|
||||
|
||||
// update button is only visible for orders under the control of an algo
|
||||
var update_button_visibility_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookRowVM.root_algo_order_type))) {
|
||||
Converter = ValueConverter.create<CustomOrderDefinition.CustomOneofCase, Visibility>(ord_type => utilities.OrderDataHelper.outright_order_types.Contains(ord_type) ? Visibility.Hidden : Visibility.Visible)
|
||||
};
|
||||
update_button_visibility_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(MiniOrderBookRowView), 1);
|
||||
update_button.SetBinding(Button.VisibilityProperty, update_button_visibility_binding);
|
||||
#endregion
|
||||
|
||||
#region OrderParamsPanel
|
||||
// finally, set the order_params DataContext to that of the ContextMenu
|
||||
var order_params = new AlgoEditorGridView {
|
||||
Margin = new Thickness(6),
|
||||
CanUserChangeAlgoRoot = false,
|
||||
};
|
||||
;
|
||||
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
|
||||
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
|
||||
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
|
||||
#endregion
|
||||
|
||||
#region Dialog Skirt
|
||||
var dialog_skirt_button_style =
|
||||
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
|
||||
Application.Current.TryFindResource("CommonButton") as Style ??
|
||||
Application.Current.TryFindResource(typeof(Button)) as Style;
|
||||
|
||||
// go find the OBVM on the ContextMenu to get the command
|
||||
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
|
||||
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
|
||||
// and Click event on a Button appropriately, but since the Button that will perform this
|
||||
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
|
||||
// we make use of this addition trick from John that allows for both of these things to happen.
|
||||
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
|
||||
// pure genious!
|
||||
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
|
||||
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
|
||||
};
|
||||
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
|
||||
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
|
||||
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
|
||||
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
|
||||
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
|
||||
// create a KeyedStyleSelector here I guess.
|
||||
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
|
||||
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
|
||||
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
|
||||
var awesome_command_style_key = "SendAttachButtonStyleKey";
|
||||
var cancel_style_key = "CancelButtonStyleKey";
|
||||
|
||||
var dialog_skirt = new DialogSkirt {
|
||||
RightItems = new[] {
|
||||
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
|
||||
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
|
||||
}
|
||||
};
|
||||
|
||||
var kss = new KeyedStyleSelector();
|
||||
kss.Map.Add(new StyleSelectorKeyValue {
|
||||
Key = awesome_command_style_key,
|
||||
Value = send_attached_button_style
|
||||
});
|
||||
kss.Map.Add(
|
||||
new StyleSelectorKeyValue {
|
||||
Key = cancel_style_key,
|
||||
Value = new Style(typeof(Button), dialog_skirt_button_style)
|
||||
});
|
||||
dialog_skirt.RightItemContainerStyleSelector = kss;
|
||||
|
||||
Grid.SetRow(dialog_skirt, 1);
|
||||
var grid = new Grid();
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.Children.Add(order_params);
|
||||
grid.Children.Add(dialog_skirt);
|
||||
|
||||
var border = new Border() {
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Name = "update_border",
|
||||
Child = grid,
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
popup.Child = border;
|
||||
popup.StaysOpen = false;
|
||||
popup.PlacementTarget = update_button;
|
||||
popup.Placement = PlacementMode.Bottom;
|
||||
@@ -1086,8 +1046,9 @@ namespace mbts.spark {
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
|
||||
});
|
||||
popup.Closed += (s, e) => {
|
||||
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm)
|
||||
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm) {
|
||||
ob_vm.is_attaching_orders = false;
|
||||
}
|
||||
};
|
||||
|
||||
var the_whole_thing = new Grid();
|
||||
@@ -1095,6 +1056,83 @@ namespace mbts.spark {
|
||||
the_whole_thing.Children.Add(popup);
|
||||
|
||||
return the_whole_thing;
|
||||
|
||||
UIElement create_popup_child() {
|
||||
var order_params = new AlgoEditorGridView {
|
||||
Margin = new Thickness(6),
|
||||
CanUserChangeAlgoRoot = false,
|
||||
};
|
||||
var order_params_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.order_mod_vm)));
|
||||
order_params_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
order_params.SetBinding(FrameworkElement.DataContextProperty, order_params_binding);
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoRootsProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_roots))));
|
||||
order_params.SetBinding(AlgoEditorGridView.SelectedAlgoRootProperty, new Binding(WPFHelper.join_binding_paths(nameof(OrderParamsVM.algo_vm), nameof(AlgoDefinitionVM.algo_root))));
|
||||
order_params.SetBinding(AlgoEditorGridView.AlgoParamRowsProperty, new Binding(nameof(OrderParamsVM.param_rows)));
|
||||
|
||||
var dialog_skirt_button_style =
|
||||
Application.Current.TryFindResource(DialogSkirt.DialogSkirtButtonStyleKey) as Style ??
|
||||
Application.Current.TryFindResource("CommonButton") as Style ??
|
||||
Application.Current.TryFindResource(typeof(Button)) as Style;
|
||||
|
||||
// go find the OBVM on the ContextMenu to get the command
|
||||
// The button for which this will server as a Binding to needs to both perform the send_attached_orders
|
||||
// command *AND* close the algo param popup. Previously this was accomplished by setting both the Command
|
||||
// and Click event on a Button appropriately, but since the Button that will perform this
|
||||
// ultimately lives inside of the DialogSkirt, we don't have direct access to it. Ergo,
|
||||
// we make use of this addition trick from John that allows for both of these things to happen.
|
||||
// We take our original ICommand (send_attached) and wrap it in another Command by way of a Converter on the binding,
|
||||
// pure genious!
|
||||
var modify_selected_button_binding = new Binding(WPFHelper.join_binding_paths("DataContext", nameof(OrderBookVM.modify_selected_orders_cmd))) {
|
||||
// set to true for debugging
|
||||
Converter = CommandWithActionWrapper.mk_wrap_converter(() => popup.IsOpen = false)
|
||||
};
|
||||
modify_selected_button_binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Popup), 1);
|
||||
|
||||
// Our ultimate goal is to have the Ok button (DialogSkirtItem) run the send attached orders command.
|
||||
// Since we're only dealing with binding paths in here and not actual objects, we don't have access
|
||||
// to an actual Command, which is what the DialogSkirtItem expects. John came up with a sneaky
|
||||
// way of setting the Button.CommandProperty via a Style backdoor, but it's also necessary to
|
||||
// create a KeyedStyleSelector here I guess.
|
||||
// Also, the DialogSkirt Ok button seems to magically close the popup, cool!
|
||||
var send_attached_button_style = new Style(typeof(Button), dialog_skirt_button_style);
|
||||
send_attached_button_style.Setters.Add(new Setter(Button.CommandProperty, modify_selected_button_binding));
|
||||
var awesome_command_style_key = "SendAttachButtonStyleKey";
|
||||
var cancel_style_key = "CancelButtonStyleKey";
|
||||
|
||||
var dialog_skirt = new DialogSkirt {
|
||||
RightItems = new[] {
|
||||
new DialogSkirtItem("OK", null, awesome_command_style_key) { is_default = true },
|
||||
new DialogSkirtItem("Cancel", new DelegateCommand(() => popup.IsOpen = false), cancel_style_key) { is_cancel = true }
|
||||
}
|
||||
};
|
||||
|
||||
var kss = new KeyedStyleSelector();
|
||||
kss.Map.Add(new StyleSelectorKeyValue {
|
||||
Key = awesome_command_style_key,
|
||||
Value = send_attached_button_style
|
||||
});
|
||||
kss.Map.Add(
|
||||
new StyleSelectorKeyValue {
|
||||
Key = cancel_style_key,
|
||||
Value = new Style(typeof(Button), dialog_skirt_button_style)
|
||||
});
|
||||
dialog_skirt.RightItemContainerStyleSelector = kss;
|
||||
|
||||
Grid.SetRow(dialog_skirt, 1);
|
||||
var grid = new Grid();
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.Children.Add(order_params);
|
||||
grid.Children.Add(dialog_skirt);
|
||||
|
||||
var border = new Border() {
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderThickness = new Thickness(1),
|
||||
Name = "update_border",
|
||||
Child = grid,
|
||||
};
|
||||
return border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1139,7 +1177,6 @@ namespace mbts.spark {
|
||||
public class CommitColumn : FrameworkElementColumnBase<Columns> {
|
||||
public CommitColumn() : base(Columns.Commit) {
|
||||
id = key.ToString();
|
||||
Header = "";
|
||||
Width = 20;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -14,7 +16,9 @@ using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurity>, INotifyPropertyChanged {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityRootPair>, INotifyPropertyChanged {
|
||||
public static readonly IComparer<OrderBookRowVM> key_comparer = Comparer<uint>.Default.wrap<uint, OrderBookRowVM>(r => r.order_key).to_safe();
|
||||
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -23,18 +27,20 @@ namespace mbts.spark {
|
||||
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
|
||||
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
|
||||
int _depth_level;
|
||||
SecurityImmutable? _security => order_tuple.security;
|
||||
SecurityImmutable? _security => order_tuple.order.security;
|
||||
// for mini-ob
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
Action<bool> _on_selection_changed;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded) {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityRootPair order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
_is_expanded = is_expanded;
|
||||
prospective_quantity = this.quantity;
|
||||
prospective_price = this.price;
|
||||
_on_selection_changed = on_selection_changed;
|
||||
}
|
||||
|
||||
//// This takes care of the case where the OB has all rows visible
|
||||
@@ -43,22 +49,22 @@ namespace mbts.spark {
|
||||
//// when the OrderBookVM.selected_order_changes, this emulates the selected
|
||||
//// order changing since the current_row setter invokes the selected_order_changed
|
||||
//// handler
|
||||
public IObservable<OrderSecurity> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurity>(
|
||||
public IObservable<OrderSecurityRootPair> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityRootPair>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurity oldval, OrderSecurity newval)> updated;
|
||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// Core
|
||||
public OrderSecurity order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order;
|
||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order.order;
|
||||
public OrderMetadata order_metadata => _owner.get_metadata(this);
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.order.meta, order_metadata));
|
||||
public uint order_key => order.Key;
|
||||
public uint parent_order_key => order.ParentKey;
|
||||
public uint security_key => order.security_key();
|
||||
@@ -69,7 +75,7 @@ namespace mbts.spark {
|
||||
public string symbol => _security.symbol_or_default();
|
||||
// Def
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => order.order_type();
|
||||
public string display_name => order_tuple.display_name();
|
||||
public string display_name => order_tuple.order.display_name();
|
||||
public OrderSubType order_sub_type => order.sub_type();
|
||||
public string order_sub_type_text => _fmt_order_sub_type(order_sub_type);
|
||||
public Side side => order.side();
|
||||
@@ -91,7 +97,7 @@ namespace mbts.spark {
|
||||
public uint quantity => order.quantity();
|
||||
public uint filled_quantity => order.filled();
|
||||
public string last_msg => order.last_msg();
|
||||
public uint piq => order_tuple.piq ?? uint.MaxValue;
|
||||
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
|
||||
public string piq_text => piq == uint.MaxValue ? string.Empty : piq.ToString();
|
||||
public bool is_non_terminal => order.is_non_terminal();
|
||||
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
|
||||
@@ -99,10 +105,10 @@ namespace mbts.spark {
|
||||
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
|
||||
|
||||
// Root Algo
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root_algo.value.CustomCase;
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root.algo.value.CustomCase;
|
||||
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
|
||||
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
|
||||
public string root_algo_display_name => order_tuple.root_algo.name;
|
||||
public string root_algo_display_name => order_tuple.root.algo.name;
|
||||
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
@@ -118,7 +124,11 @@ namespace mbts.spark {
|
||||
public int depth_level { get => _depth_level; set => Set(ref _depth_level, value); }
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set => Set(ref _is_selected, value);
|
||||
set {
|
||||
if (Set(ref _is_selected, value)) {
|
||||
_on_selection_changed(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public uint prospective_quantity {
|
||||
get => _prospective_quantity;
|
||||
@@ -190,20 +200,24 @@ namespace mbts.spark {
|
||||
public ICommand attach_command => _attach_command ?? (
|
||||
_attach_command = new DelegateCommand(() => {
|
||||
// the _select me will trigger the OBVM.attach_vm to get primed
|
||||
_select_me();
|
||||
if (!is_selected) {
|
||||
_select_me();
|
||||
}
|
||||
_owner.begin_attach_algos();
|
||||
})
|
||||
);
|
||||
|
||||
public ICommand update_command => _update_command ?? (
|
||||
_update_command = new DelegateCommand(() => {
|
||||
_select_me();
|
||||
if (!is_selected) {
|
||||
_select_me();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// private props
|
||||
Color _root_algo_badge_color {
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata)).Value;
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root.meta, root_algo_order_metadata)).Value;
|
||||
set {
|
||||
if (_root_algo_badge_color_backing != value) {
|
||||
_root_algo_badge_color_backing = value;
|
||||
@@ -216,7 +230,7 @@ namespace mbts.spark {
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
int IHierarchicalRow.depth_level => depth_level;
|
||||
OrderSecurity IHierarchicalRow<OrderSecurity>.value => order_tuple;
|
||||
OrderSecurityRootPair IHierarchicalRow<OrderSecurityRootPair>.value => order_tuple;
|
||||
|
||||
void IHierarchicalRow.on_is_parent_changed(bool is_parent) => this.is_parent = is_parent;
|
||||
/// Do NOT actually call the <see cref="is_expanded"/> setter (it has side effects) - the row collection calls this method when the row is expanded/collapsed by some **external means, outside of this class.
|
||||
@@ -228,17 +242,15 @@ namespace mbts.spark {
|
||||
/// <see cref="OperableHierarchicalCollection{TRow, T, TKey}.collapse_row(TKey)"/>
|
||||
void IHierarchicalRow.on_is_expanded_changed(bool is_expanded) => Set(ref _is_expanded, is_expanded, nameof(is_expanded));
|
||||
void IHierarchicalRow.on_depth_level_changed(int depth_level) => this.depth_level = depth_level;
|
||||
void IHierarchicalRow<OrderSecurity>.on_value_changed(OrderSecurity value) => update(value);
|
||||
void IHierarchicalRow<OrderSecurityRootPair>.on_value_changed(OrderSecurityRootPair value) => update(value);
|
||||
#endregion
|
||||
|
||||
private void _unselect_me() {
|
||||
this.is_selected = false;
|
||||
_owner.current_row = null;
|
||||
}
|
||||
|
||||
private void _select_me() {
|
||||
this.is_selected = true;
|
||||
_owner.current_row = this;
|
||||
}
|
||||
|
||||
// These fns present a way for the orderbook to avoid noisy, intermittent collection changes during "Expand/Collapse All" ops,
|
||||
@@ -246,8 +258,8 @@ namespace mbts.spark {
|
||||
internal void expand_internal() => Set(ref _is_expanded, true, nameof(is_expanded));
|
||||
internal void collapse_internal() => Set(ref _is_expanded, false, nameof(is_expanded));
|
||||
|
||||
public void update(OrderSecurity order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
public void update(OrderSecurityRootPair order_tuple) {
|
||||
Log.assert(order_tuple.order.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
|
||||
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
|
||||
var old_order_tuple = this.order_tuple;
|
||||
@@ -273,23 +285,27 @@ namespace mbts.spark {
|
||||
if ((Columns.Security & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(security), nameof(security_text));
|
||||
// badge fill
|
||||
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
|
||||
// invalidate by setting to null (will be re-instantiated in the public getter)
|
||||
_badge_fill = null;
|
||||
RaisePropertyChanged(nameof(badge_fill));
|
||||
}
|
||||
// display name
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
if (old_order_tuple.order.algo.name != order_tuple.order.algo.name)
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
// root badge fill
|
||||
if (_root_algo_badge_color_backing.HasValue) {
|
||||
// only need to invalidate it if it had a value (it is lazily instantiated)
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata);
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root.meta, root_algo_order_metadata);
|
||||
RaisePropertyChanged(nameof(root_algo_badge_fill));
|
||||
}
|
||||
// root display name
|
||||
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
|
||||
if (old_order_tuple.root.algo.name != order_tuple.root.algo.name)
|
||||
RaisePropertyChanged(nameof(root_algo_display_name));
|
||||
// root order type
|
||||
if (old_order_tuple.root.algo.value.CustomCase != order_tuple.root.algo.value.CustomCase) {
|
||||
RaisePropertyChanged(nameof(root_algo_order_type));
|
||||
}
|
||||
// status
|
||||
if ((Columns.Status & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(status));
|
||||
@@ -305,7 +321,7 @@ namespace mbts.spark {
|
||||
// always raise updated
|
||||
updated?.Invoke((old_order_tuple, order_tuple));
|
||||
// terminal state
|
||||
if (old_order_tuple.order.is_non_terminal() != order_tuple.order.is_non_terminal()) {
|
||||
if (old_order_tuple.order.order.is_non_terminal() != order_tuple.order.order.is_non_terminal()) {
|
||||
// while we call refresh_is_at/detachable in the OrderBookRowCollection.update
|
||||
// on all the children of a modified row, we used to not call it on the row istelf,
|
||||
// which should of course be done here. This manifested via the very top parent in any
|
||||
@@ -386,35 +402,35 @@ namespace mbts.spark {
|
||||
Columns.AvgFilled |
|
||||
Columns.Status;
|
||||
|
||||
public static Columns get_dirty_cells(OrderSecurity oldval, OrderSecurity newval) {
|
||||
public static Columns get_dirty_cells(OrderSecurityRootPair oldval, OrderSecurityRootPair newval) {
|
||||
var dirty = (Columns)0;
|
||||
if (oldval.security != newval.security)
|
||||
if (oldval.order.security != newval.order.security)
|
||||
dirty |= Columns.Security;
|
||||
if (oldval.order.last_change() != newval.order.last_change())
|
||||
if (oldval.order.order.last_change() != newval.order.order.last_change())
|
||||
dirty |= Columns.LastChange;
|
||||
if (oldval.order.filled() != newval.order.filled())
|
||||
if (oldval.order.order.filled() != newval.order.order.filled())
|
||||
dirty |= Columns.Filled;
|
||||
if (oldval.order.last_price() != newval.order.last_price())
|
||||
if (oldval.order.order.last_price() != newval.order.order.last_price())
|
||||
dirty |= Columns.LastPrice;
|
||||
if (oldval.order.avg_price() != newval.order.avg_price())
|
||||
if (oldval.order.order.avg_price() != newval.order.order.avg_price())
|
||||
dirty |= Columns.AvgFilled;
|
||||
if (oldval.order.price() != newval.order.price())
|
||||
if (oldval.order.order.price() != newval.order.order.price())
|
||||
dirty |= Columns.Price;
|
||||
if (oldval.order.quantity() != newval.order.quantity())
|
||||
if (oldval.order.order.quantity() != newval.order.order.quantity())
|
||||
dirty |= Columns.Size;
|
||||
if (oldval.order.ParentKey != newval.order.ParentKey)
|
||||
if (oldval.order.order.ParentKey != newval.order.order.ParentKey)
|
||||
dirty |= Columns.ParentKey;
|
||||
if (oldval.order.attached_target_order_key() != newval.order.attached_target_order_key())
|
||||
if (oldval.order.order.attached_target_order_key() != newval.order.order.attached_target_order_key())
|
||||
dirty |= Columns.AttachedTargetOrderKey;
|
||||
if (oldval.order.status() != newval.order.status())
|
||||
if (oldval.order.order.status() != newval.order.order.status())
|
||||
dirty |= Columns.Status;
|
||||
if (oldval.order.last_msg() != newval.order.last_msg())
|
||||
if (oldval.order.order.last_msg() != newval.order.order.last_msg())
|
||||
dirty |= Columns.LastMessage;
|
||||
if (oldval.piq != newval.piq) {
|
||||
if (oldval.order.piq != newval.order.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
@@ -31,21 +31,25 @@ using System.Windows.Input;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection, IHandleGridSorting, IRequestRowSelection {
|
||||
const double
|
||||
_min_right_panel_width = 200,
|
||||
_min_order_params_height = 300,
|
||||
_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;
|
||||
readonly ObservableSortedCollection<OrderBookRowVM> _selected_rows = new ObservableSortedCollection<OrderBookRowVM>(OrderBookRowVM.key_comparer);
|
||||
readonly DelegateProperty<bool> _show_order_params, _show_ladder, _show_fills, _can_attach_selected_orders, _can_detach_selected_orders;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityRootPair, uint> _row_collection;
|
||||
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
|
||||
@@ -56,8 +60,9 @@ namespace mbts.spark {
|
||||
bool
|
||||
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
|
||||
_is_attaching_orders,
|
||||
_is_row_collection_populated;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
_is_row_collection_populated,
|
||||
_all_rows_selected;
|
||||
IReadOnlyDictionary<uint, OrderSecurityRootPair> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityRootPair>();
|
||||
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
|
||||
HashSet<uint>
|
||||
_attachable_selected_orders = new HashSet<uint>(),
|
||||
@@ -65,7 +70,6 @@ namespace mbts.spark {
|
||||
FilterData _cached_filter_data;
|
||||
|
||||
public OrderBookVM(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
@@ -74,8 +78,9 @@ namespace mbts.spark {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle, k => _cached_orders.TryGetValue(k, out var o) ? o.order : (OrderSecurityAlgo?)null);
|
||||
ladder_vm = new LadderVMContainer(bus, model);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoFormats.Exchange, AlgoEditingPermissions.AllowEditValues);
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
_show_order_params = backing(nameof(show_order_params), () => (_right_panel.visible_components & OrderBookRightPanelSettings.Components.Params) != 0);
|
||||
@@ -88,22 +93,28 @@ namespace mbts.spark {
|
||||
};
|
||||
_can_attach_selected_orders = backing(nameof(can_attach_selected_orders), () => _attachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
|
||||
_can_detach_selected_orders = backing(nameof(can_detach_selected_orders), () => _detachable_selected_orders.Count == selected_rows.Count && selected_rows.Count > 0);
|
||||
attach_vm = new OrderParamsVM(this,
|
||||
AlgoDefinitionVM.create(_model,
|
||||
OrderDataHelper.Defaults.AlgoData(CustomOrderDefinition.CustomOneofCase.Iceberg),
|
||||
init_format: AlgoFormats.Attachable,
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues));
|
||||
attach_vm = new OrderParamsVM(this, AlgoFormats.Attachable, AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues);
|
||||
_settings_manager = settings_manager;
|
||||
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(x => (x.added.Values, x.removed.Values)));
|
||||
// we use an ObservableAppendCollection b/c the mini-ob needs a cancel column at the front
|
||||
columns = new ObservableAppendCollection<DIYGrid.ColumnBase>(_columns);
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityRootPair, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded, (row_is_selected) => {
|
||||
// all are selected and one is checked off
|
||||
if (all_rows_selected && !row_is_selected) {
|
||||
_all_rows_selected = false;
|
||||
RaisePropertyChanged(nameof(this.all_rows_selected));
|
||||
// not all selected and now the last one gets turned on
|
||||
} else if (!all_rows_selected && _row_collection.All(r => r.is_selected)) {
|
||||
_all_rows_selected = true;
|
||||
RaisePropertyChanged(nameof(this.all_rows_selected));
|
||||
}
|
||||
}) {
|
||||
is_attachable = _cached_attachable.TryGetValue(o.order.order.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.order.Key, out bool id)
|
||||
},
|
||||
r => r.order.Key,
|
||||
r => r.order.ParentKey,
|
||||
r => r.order.order.Key,
|
||||
r => r.order.order.ParentKey,
|
||||
_columns.get_sorting_data().comparer);
|
||||
_row_collection.CollectionChanged += _row_collection_CollectionChanged;
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
@@ -125,23 +136,22 @@ namespace mbts.spark {
|
||||
toggle_show_fills_cmd = new DelegateCommand(() => show_fills = !show_fills);
|
||||
launch_fills_settings_cmd = concated_disposable(new DialogCommand(() => {
|
||||
const string title = "Order Book 'Mini' Fills Grid";
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_fills_settings_manager,
|
||||
us => us.fills_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
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
|
||||
var dialog = new SimpleGridSettingsDialog { Title = title, DataContext = vm };
|
||||
var dialog = new ShellSettingsDialog { Title = title, DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
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;
|
||||
@@ -164,14 +174,35 @@ 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;
|
||||
RaisePropertyChanged(nameof(security_plurality));
|
||||
}),
|
||||
orders_source.OnDispatcher().Subscribe(t => {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
|
||||
if (_selected_rows.Count == 0) {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(OrderSecurityHelper.root_pair_key_fn));
|
||||
} else {
|
||||
using (_selected_params_mon.defer_updates())
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(OrderSecurityHelper.root_pair_key_fn));
|
||||
// Remove, then re-add, all selected roots from _selected_params_mon
|
||||
// to capture algo param changes at any level (this keeps the update panel in sync).
|
||||
_selected_params_mon.Clear();
|
||||
_selected_security_mon.clear();
|
||||
foreach (var r in _selected_rows) {
|
||||
_selected_params_mon.Add(r.order_tuple.root);
|
||||
_selected_security_mon.add(r.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
model.on_orders_can_attach.OnDispatcher().Subscribe(table => {
|
||||
_cached_attachable = new MappedDictionary<uint, KeyValuePair<uint, bool>, bool>(table.state, kv => kv.Value);
|
||||
@@ -205,7 +236,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 => {
|
||||
@@ -253,11 +283,11 @@ namespace mbts.spark {
|
||||
try {
|
||||
double price = attach_vm.price;
|
||||
uint? size = attach_vm.size;
|
||||
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted.id_uint : 0u;
|
||||
uint algo_template_key = attach_vm.algo_vm.algo_root.id.is_persisted ? attach_vm.algo_vm.algo_root.id.persisted : 0u;
|
||||
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
|
||||
var mod_algo_vm = attach_vm.algo_vm.algo_root;
|
||||
await Task.WhenAll(order_tups.Select(t => {
|
||||
var algo = t.order.Def.CustomDef.actual().CloneToMutable();
|
||||
var algo = t.order.order.Def.CustomDef.actual().CloneToMutable();
|
||||
mod_algo_vm.assign_to(algo);
|
||||
if (algo_template_key != 0) {
|
||||
algo = new CustomOrderDefinition {
|
||||
@@ -268,12 +298,12 @@ namespace mbts.spark {
|
||||
};
|
||||
}
|
||||
return _model.send_order(
|
||||
t.order.Def.with(
|
||||
attach_target_order_key: t.order.Key,
|
||||
t.order.order.Def.with(
|
||||
attach_target_order_key: t.order.order.Key,
|
||||
custom_def: algo
|
||||
),
|
||||
double.IsNaN(price) ? t.order.price() : price,
|
||||
size ?? t.order.quantity());
|
||||
double.IsNaN(price) ? t.order.order.price() : price,
|
||||
size ?? t.order.order.quantity());
|
||||
}));
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Error sending attached orders: {ex}.");
|
||||
@@ -300,21 +330,20 @@ namespace mbts.spark {
|
||||
}),
|
||||
// custom templates (created by user)
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_factory(true)))));
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_algo_def_vm))));
|
||||
// vm factory
|
||||
Func<AlgoData, AlgoDefinitionVM> mk_factory(bool connect) =>
|
||||
fd => {
|
||||
var vm = AlgoDefinitionVM.create(_model, fd, connect, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
|
||||
return vm;
|
||||
};
|
||||
AlgoDefinitionVM mk_algo_def_vm(AlgoData fd) {
|
||||
var vm = AlgoDefinitionVM.create(_model, fd, OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase), AlgoEditingPermissions.AllowEditValues);
|
||||
vm.selected_format = OrderDataHelper.get_algo_format(_selected_params_mon.security?.ContractCase);
|
||||
return vm;
|
||||
}
|
||||
}
|
||||
void on_end_attaching() => Disposable.dispose(ref attach_vm_hooks);
|
||||
|
||||
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();
|
||||
}),
|
||||
@@ -322,10 +351,11 @@ 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) {
|
||||
@@ -334,6 +364,7 @@ namespace mbts.spark {
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
public event Action begin_attaching, end_attaching;
|
||||
public event Action<(IEnumerable, bool)> request_selection;
|
||||
|
||||
public ObservableAppendCollection<DIYGrid.ColumnBase> columns { get; }
|
||||
public ObservableCollection<DIYGrid.ColumnBase> core_columns => _columns;
|
||||
@@ -356,7 +387,7 @@ namespace mbts.spark {
|
||||
public OrderParamsVM order_mod_vm { get; }
|
||||
public FillsVMContainer fills_vm { get; } = new FillsVMContainer();
|
||||
public OrderParamsVM attach_vm { get; }
|
||||
public ObservableCollection<OrderBookRowVM> selected_rows { get; } = new ObservableCollection<OrderBookRowVM>();
|
||||
public IReadOnlyObservableCollection<OrderBookRowVM> selected_rows => _selected_rows;
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => selected_rows.Select(r => r.order);
|
||||
public IEnumerable<OrderBookRowVM> selected_hierarchical_rows => selected_rows.SelectMany(r => new[] { r }.Concat(_row_collection.get_descendent_rows(r.order_key)));
|
||||
public OrderBookGridSettingsManager grid_settings_manager { get; }
|
||||
@@ -419,6 +450,14 @@ namespace mbts.spark {
|
||||
public bool can_attach_selected_orders => _can_attach_selected_orders.Value;
|
||||
public bool can_detach_selected_orders => _can_detach_selected_orders.Value;
|
||||
public Plurality security_plurality { get; private set; }
|
||||
public bool all_rows_selected {
|
||||
get => _all_rows_selected;
|
||||
set {
|
||||
Set(ref _all_rows_selected, value);
|
||||
request_selection?.Invoke((_row_collection, _all_rows_selected));
|
||||
}
|
||||
}
|
||||
public 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);
|
||||
@@ -426,31 +465,33 @@ 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_row_unselected(row_vm);
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
foreach (var row in e.RemovedItems.OfType<OrderBookRowVM>()) {
|
||||
row.is_selected = false;
|
||||
_selected_rows.Remove(row);
|
||||
_selected_params_mon.Remove(row.order_tuple.root);
|
||||
_selected_security_mon.remove(row.security);
|
||||
_attachable_selected_orders.Remove(row.order_key);
|
||||
_detachable_selected_orders.Remove(row.order_key);
|
||||
}
|
||||
}
|
||||
if (e.AddedItems != null) {
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
row_vm.is_selected = true;
|
||||
_selected_params_mon.on_row_selected(row_vm);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
_detachable_selected_orders.Add(row_vm.order_key);
|
||||
foreach (var row in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
row.is_selected = true;
|
||||
_selected_rows.Add(row);
|
||||
_selected_params_mon.Add(row.order_tuple.root);
|
||||
_selected_security_mon.add(row.security);
|
||||
if (row.is_attachable) _attachable_selected_orders.Add(row.order_key);
|
||||
if (row.is_detachable) _detachable_selected_orders.Add(row.order_key);
|
||||
}
|
||||
}
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
_can_attach_selected_orders.refresh();
|
||||
_can_detach_selected_orders.refresh();
|
||||
selected_order_changed?.Invoke(current_order);
|
||||
} catch (Exception ex) {
|
||||
// [John]I observed a strange null-reference exception in this callback when the launcher was shutting down (see https://bluetradesys.atlassian.net/browse/SPARK-2089)
|
||||
Log.Ui.error($"[{nameof(OrderBookVM)}] Error handling selection change: {ex}");
|
||||
}
|
||||
@@ -544,35 +585,28 @@ 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);
|
||||
|
||||
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
|
||||
while (r.ParentKey != 0 && _cached_orders.TryGetValue(r.ParentKey, out var parent_osam)) {
|
||||
r = parent_osam.order;
|
||||
internal void detach_orders(OrderBookRowVM row) {
|
||||
if (row.is_selected) {
|
||||
_detach_orders_async(selected_rows).ignore();
|
||||
} else {
|
||||
_detach_orders_async(new[] { row }).ignore();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
internal void detach_orders(OrderBookRowVM order) =>
|
||||
_detach_orders_async(new[] { order }).ignore();
|
||||
|
||||
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);
|
||||
|
||||
internal void cancel_order(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
|
||||
internal void cancel_order(OrderBookRowVM row) {
|
||||
if (row.is_selected) {
|
||||
_bus.post(SparkTags.CancelOrders, selected_rows.Select(r => r.order_key).ToList());
|
||||
} else {
|
||||
_bus.post(SparkTags.CancelOrders, new List<uint> { row.order_key });
|
||||
}
|
||||
}
|
||||
|
||||
internal void modify_order_quantity(OrderBookRowVM row, uint size) =>
|
||||
_bus.post(SparkTags.UpdateOrder, (row.order_key, row.price, size));
|
||||
@@ -611,6 +645,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// Note: modifications are performed on the selected orders' ROOT orders.
|
||||
async Task _modify_selected_orders_async() {
|
||||
double? price = DoubleHelper.nan_nullable(order_mod_vm.price);
|
||||
uint? size = order_mod_vm.size;
|
||||
@@ -623,22 +658,22 @@ namespace mbts.spark {
|
||||
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
|
||||
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
|
||||
// ----
|
||||
var update_params = selected_rows.Select(update_params_factory).ToList();
|
||||
var update_params = selected_rows.Select(r => update_params_factory(r.order_tuple.root)).ToList();
|
||||
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
|
||||
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurityAlgo order_tuple) {
|
||||
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
|
||||
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
|
||||
} else {
|
||||
mod_algo_vm.assign_to(mod_algo);
|
||||
}
|
||||
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
|
||||
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
return (order_tuple.order, OrderDataHelper.EqualsHelpers.equals(order_tuple.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
}
|
||||
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
|
||||
}
|
||||
|
||||
IObservable<ObservableCollection<OrderBookRowVM>> _subscribe_throttled_selected_rows() =>
|
||||
IObservable<IReadOnlyList<OrderBookRowVM>> _subscribe_throttled_selected_rows() =>
|
||||
selected_rows.Observe().BufferLatest(TimeSpan.FromSeconds(0.2)).Select(_ => selected_rows);
|
||||
|
||||
static OrderBookFillsGridSettings _get_fills_settings(FillsVM fills_vm) =>
|
||||
@@ -676,52 +711,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;
|
||||
@@ -732,18 +721,17 @@ 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)
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoFormats init_format, AlgoEditingPermissions init_permissions)
|
||||
: this(owner,
|
||||
AlgoDefinitionVM.create(
|
||||
owner._model,
|
||||
new AlgoData(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg)),
|
||||
editing_permissions: AlgoEditingPermissions.AllowEditValues)) { }
|
||||
AlgoData.create_unpersisted(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg), CustomOrderDefinition.CustomOneofCase.Iceberg.display_name()),
|
||||
init_format: init_format,
|
||||
editing_permissions: init_permissions)) { }
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
OrderParamsVM(OrderBookVM owner, AlgoDefinitionVM algo_vm) {
|
||||
_owner = owner;
|
||||
this.algo_vm = algo_vm;
|
||||
_accounts = backing(
|
||||
@@ -784,13 +772,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);
|
||||
@@ -818,8 +800,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();
|
||||
@@ -843,7 +824,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;
|
||||
@@ -878,18 +858,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)))) :
|
||||
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;
|
||||
@@ -908,7 +876,7 @@ namespace mbts.spark {
|
||||
this.column_filters = column_filters;
|
||||
}
|
||||
|
||||
public Func<OrderSecurity, bool> build_predicate() {
|
||||
public Func<OrderSecurityRootPair, bool> build_predicate() {
|
||||
var pred = column_filters.build_predicate();
|
||||
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
|
||||
if (pred == null) {
|
||||
@@ -917,10 +885,10 @@ namespace mbts.spark {
|
||||
var col_pred = pred;
|
||||
pred = o => tree_disp_pred(o) && col_pred(o);
|
||||
}
|
||||
bool tree_disp_pred(OrderSecurity o) =>
|
||||
!o.order.has_parent() ||
|
||||
o.order.is_exchange_order() ||
|
||||
o.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
bool tree_disp_pred(OrderSecurityRootPair o) =>
|
||||
!o.order.order.has_parent() ||
|
||||
o.order.order.is_exchange_order() ||
|
||||
o.order.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
}
|
||||
return pred;
|
||||
}
|
||||
@@ -936,132 +904,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_row_selected(OrderBookRowVM row) {
|
||||
_group_mon.add(row.order_tuple);
|
||||
row.updated += _on_row_updated;
|
||||
}
|
||||
|
||||
public void on_row_unselected(OrderBookRowVM row) {
|
||||
_group_mon.remove(row.order_tuple);
|
||||
row.updated -= _on_row_updated;
|
||||
}
|
||||
|
||||
public void on_security_md_map(IReadOnlyDictionary<uint, SecurityMD> security_md_map) {
|
||||
_security_md_map = security_md_map;
|
||||
_security_md_map_changed?.Invoke();
|
||||
}
|
||||
|
||||
void _on_row_updated((OrderSecurity oldval, OrderSecurity newval) ch) {
|
||||
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;
|
||||
@@ -1124,11 +966,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);
|
||||
|
||||
@@ -161,7 +159,7 @@ namespace mbts.spark {
|
||||
toggle_expand_all_command.group = CommandGroups.Auxiliary;
|
||||
|
||||
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
|
||||
var gs_vm = new ShellSettingsDialogVM<OrderBookGridSettings, OrderBookSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -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
|
||||
var dialog = new SimpleGridSettingsDialog { 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.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => gs_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = gs_vm };
|
||||
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
@@ -1,154 +1,155 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public string order_key_regex;
|
||||
[ProtoMember(3)]
|
||||
public TimeFilterTuple time_created;
|
||||
[ProtoMember(4)]
|
||||
public string[] users;
|
||||
[ProtoMember(5)]
|
||||
public string[] accounts;
|
||||
[ProtoMember(6)]
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public UIntMinMaxState qty;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
public UIntMinMaxState qty_filled;
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState avg_price;
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(15)]
|
||||
public OrderStatus[] statuses;
|
||||
[ProtoMember(16)]
|
||||
public string exchange_message_regex;
|
||||
[ProtoMember(17)]
|
||||
public TimeFilterTuple last_change;
|
||||
//[ProtoMember(18)]
|
||||
//public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public string order_key_regex;
|
||||
[ProtoMember(3)]
|
||||
public TimeFilterTuple time_created;
|
||||
[ProtoMember(4)]
|
||||
public string[] users;
|
||||
[ProtoMember(5)]
|
||||
public string[] accounts;
|
||||
[ProtoMember(6)]
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public UIntMinMaxState qty;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
public UIntMinMaxState qty_filled;
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState avg_price;
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(15)]
|
||||
public OrderStatus[] statuses;
|
||||
[ProtoMember(16)]
|
||||
public string exchange_message_regex;
|
||||
[ProtoMember(17)]
|
||||
public TimeFilterTuple last_change;
|
||||
//[ProtoMember(18)]
|
||||
//public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurityRootPair, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurityRootPair>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo.key));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.order.price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -108,4 +108,9 @@
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.RowStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay}" />
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
</c:DIYGrid>
|
||||
@@ -50,7 +50,8 @@ namespace mbts.spark {
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
|
||||
@@ -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;
|
||||
@@ -67,29 +71,19 @@ namespace mbts.spark.utilities {
|
||||
public IObservable<IReadOnlyList<AccountTuple>> subscribe_account_tuples() => _admin_model.on_account_tuples;
|
||||
|
||||
#region Defaults
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type() =>
|
||||
CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child = false) {
|
||||
if (is_child) return CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security) {
|
||||
switch (security.ContractCase) {
|
||||
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
default: return CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
}
|
||||
}
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_order_type(security.Value, is_child) : default_order_type();
|
||||
|
||||
public TimeInForce default_time_in_force() =>
|
||||
TimeInForce.Day;
|
||||
|
||||
public TimeInForce default_time_in_force(SecurityImmutable security) =>
|
||||
default_time_in_force();
|
||||
|
||||
public TimeInForce default_time_in_force(SecurityImmutable? security) =>
|
||||
security.HasValue ? default_time_in_force(security.Value) : default_time_in_force();
|
||||
|
||||
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types() =>
|
||||
OrderDataHelper.exchange_order_types;
|
||||
|
||||
@@ -141,15 +135,15 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public Dictionary<AlgoKey, AlgoData> default_algos(IEnumerable<CustomOrderDefinition.CustomOneofCase> types) {
|
||||
var def_map = new Dictionary<AlgoKey, AlgoData>();
|
||||
foreach (var ot in types.Except(new[] { CustomOrderDefinition.CustomOneofCase.Dynamic })) {
|
||||
var key = new AlgoKey(new AlgoNameStructureKey(ot));
|
||||
def_map[key] = new AlgoData(custom_orderi(ot), ot.display_name(), key);
|
||||
foreach (var ot in types.Except(new[] { CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite })) {
|
||||
var data = AlgoData.from_custom_def(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(ot));
|
||||
def_map[data.key] = data;
|
||||
}
|
||||
var dynamic_defs = new List<CustomOrderDefinitionImmutable>();
|
||||
_dynamic_helper.add_dynamic_order_defs(dynamic_defs, custom_order);
|
||||
foreach (var dd in dynamic_defs) {
|
||||
var key = new AlgoKey(new AlgoNameStructureKey(dd.Dynamic.DynamicOrderType, new AlgoStructureKey(CustomOrderDefinition.CustomOneofCase.Dynamic)));
|
||||
def_map[key] = new AlgoData(dd, dd.Dynamic.DynamicOrderType, key);
|
||||
var data = AlgoData.from_custom_def(dd);
|
||||
def_map[data.key] = data;
|
||||
}
|
||||
return def_map;
|
||||
}
|
||||
@@ -157,6 +151,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public OrderDefinitionImmutable order(SecurityImmutable security) =>
|
||||
new OrderDefinitionImmutable(order(security, _admin_model.get_default_account()));
|
||||
|
||||
public OrderDefinition order(SecurityImmutable security, AccountTuple? account) {
|
||||
var rv = new OrderDefinition {
|
||||
SecurityKey = security.Id,
|
||||
@@ -168,17 +163,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 +207,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 +238,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 +269,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() };
|
||||
@@ -306,6 +304,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public static OneofDescriptor oneof_descriptor => _oneof_desc;
|
||||
|
||||
const string _primary_child_field = "child";
|
||||
// algo-type-case maps
|
||||
// Note: obv this will break if we change the name of the 'CustomOneofCase' field
|
||||
static readonly OneofDescriptor _oneof_desc = CustomOrderDefinition.Descriptor.Oneofs.FirstOrDefault(fd => fd.Name == "custom");
|
||||
@@ -349,35 +348,69 @@ namespace mbts.spark.utilities {
|
||||
f.FieldType == FieldType.Message && f.MessageType.ClrType == typeof(CustomOrderDefinition);
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_children(CustomOrderDefinition algo) {
|
||||
switch (algo.CustomCase) {
|
||||
#region Algo Structure/Hierarchy
|
||||
public static IEnumerable<(CustomOrderDefinition.CustomOneofCase type, string dyn, uint studio, uint spreader_sec)> enumerate_algo_structure(CustomOrderDefinitionImmutable custom_defi) {
|
||||
string dyn = null;
|
||||
uint studio = 0, spreader_sec = 0;
|
||||
foreach (var cd in enumerate_algo_hierarchy(custom_defi.CloneToMutable())) {
|
||||
switch (cd.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: studio = cd.Composite.AlgoTemplateKey; break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: dyn = cd.Dynamic.DynamicOrderType; break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: spreader_sec = cd.Spreader.Security.Id; break;
|
||||
}
|
||||
yield return (cd.CustomCase, dyn, studio, spreader_sec);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates through every algo member immediately defined <paramref name="custom_def"/> (i.e., no recursion, just the first level of children).
|
||||
/// </summary>
|
||||
/// <param name="custom_defi">The root algo, from which enumeration begins.</param>
|
||||
/// <returns>An IEnumerable of each algo member contained in <paramref name="custom_def"/></returns>
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_children(CustomOrderDefinition custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
foreach (var child in enumerate_dynamic_algo_children(algo.Dynamic))
|
||||
yield return child;
|
||||
foreach (var kv in custom_def.Dynamic.OrderParams) {
|
||||
if (kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
yield return kv.Value.AsOrderDef ?? new CustomOrderDefinition();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
foreach (var cad in get_child_algo_data(algo)) {
|
||||
if (cad.get_child(algo) is CustomOrderDefinition child)
|
||||
yield return child;
|
||||
var child_algo_data = get_child_algo_data(custom_def);
|
||||
if (child_algo_data.Count > 0) {
|
||||
// need to convert to underlying proto type to use the [reflection-based]generated child selectors
|
||||
for (int i = 0; i < child_algo_data.Count; i++) {
|
||||
// don't skip undefined children (this normalizes the structure).
|
||||
// each algo type should always yield the same number of children.
|
||||
yield return child_algo_data[i].get_child(custom_def) ?? new CustomOrderDefinition();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_dynamic_algo_children(DynamicOrderDefinition dynamic_algo) {
|
||||
foreach (var kv in dynamic_algo.OrderParams) {
|
||||
if (kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
yield return kv.Value.AsOrderDef;
|
||||
/// <summary>
|
||||
/// Enumerates through every algo member contained in <paramref name="custom_def"/>, excluding <paramref name="custom_def"/> itself.
|
||||
/// </summary>
|
||||
/// <param name="custom_defi">The root algo, from which enumeration begins.</param>
|
||||
/// <returns>An IEnumerable of each algo member contained in <paramref name="custom_def"/></returns>
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_descendents(CustomOrderDefinition custom_def) {
|
||||
foreach (var child in enumerate_algo_children(custom_def)) {
|
||||
yield return child;
|
||||
foreach (var grand_child in enumerate_algo_descendents(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_hierarchy(CustomOrderDefinition algo) {
|
||||
yield return algo;
|
||||
foreach (var child in enumerate_algo_children(algo)) {
|
||||
yield return child;
|
||||
foreach (var grand_child in enumerate_algo_hierarchy(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
/// <summary>
|
||||
/// Enumerates through every algo member contained in <paramref name="custom_def"/>, including <paramref name="custom_def"/> itself.
|
||||
/// </summary>
|
||||
/// <param name="custom_def">The root algo, from which enumeration begins.</param>
|
||||
/// <returns>An IEnumerable of each algo member contained in <paramref name="custom_def"/></returns>
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_hierarchy(CustomOrderDefinition custom_def) {
|
||||
yield return custom_def;
|
||||
foreach (var d in enumerate_algo_descendents(custom_def))
|
||||
yield return d;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinitionImmutable defi) =>
|
||||
@@ -405,6 +438,12 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public static ChildAlgoData? get_primary_child_algo_data(CustomOrderDefinition algo) {
|
||||
var child_data = get_child_algo_data(algo);
|
||||
return child_data.TryGetFirst(cad => cad.name == _primary_child_field, out ChildAlgoData best_match) ? best_match :
|
||||
child_data.TryGetFirst(out best_match) ? best_match : (ChildAlgoData?)null;
|
||||
}
|
||||
|
||||
public static CustomOrderDefinition create_algo(CustomOrderDefinition.CustomOneofCase algo_type) {
|
||||
var algo = new CustomOrderDefinition();
|
||||
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData desc))
|
||||
@@ -412,8 +451,18 @@ namespace mbts.spark.utilities {
|
||||
return algo;
|
||||
}
|
||||
|
||||
public static bool append_algo_node(CustomOrderDefinition target, CustomOrderDefinition.CustomOneofCase algo_type) {
|
||||
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out var desc)) {
|
||||
var node = ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor));
|
||||
desc.type_case_descriptor.Accessor.SetValue(target, node);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition.CustomOneofCase algo_type) =>
|
||||
_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData d) ? d.child_algo_data : Array.Empty<ChildAlgoData>();
|
||||
#endregion
|
||||
|
||||
public static OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) {
|
||||
switch (order_type) {
|
||||
@@ -459,6 +508,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return "TG";
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: return "JN";
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return "SR";
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return "COMP";
|
||||
default: return algo_type.ToString();
|
||||
}
|
||||
}
|
||||
@@ -496,7 +546,7 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public static IReadOnlyDictionary<AlgoKey, AlgoData> get_format_algo_data(AlgoFormats f) {
|
||||
var algo_types = get_format_algo_types(f);
|
||||
var algo_dict = algo_types.Select(at => new AlgoData(Defaults.CustomOrderDefinitionImmutable(at), at.display_name(), new AlgoKey(at))).ToDictionaryCrushed(a => a.key);
|
||||
var algo_dict = algo_types.Select(at => AlgoData.from_custom_def(Defaults.CustomOrderDefinitionImmutable(at))).ToDictionaryCrushed(a => a.key);
|
||||
return algo_dict;
|
||||
}
|
||||
|
||||
@@ -516,7 +566,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 +592,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];
|
||||
@@ -580,49 +631,25 @@ namespace mbts.spark.utilities {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a key that is uniquely identified by <see cref="CustomOrderDefinition.CustomOneofCase"/>,
|
||||
/// and potentially <see cref="DynamicOrderDefinition.DynamicOrderType"/> (if it is a <see cref="DynamicOrderDefinition"/>).
|
||||
/// </summary>
|
||||
public static AlgoKey get_shallow_factory_key(CustomOrderDefinitionImmutable defi) =>
|
||||
new AlgoKey(new AlgoNameStructureKey(
|
||||
defi.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic ? defi.Dynamic.DynamicOrderType : defi.CustomCase.type_id(),
|
||||
new AlgoStructureKey(defi.CustomCase)));
|
||||
|
||||
static CustomOrderDefinition.CustomOneofCase[] algo_config(
|
||||
Func<bts.config.AlgoPermissions, List<string>> selector)
|
||||
{
|
||||
var conf = mbts.config.Configuration.get<bts.config.AlgoPermissions>();
|
||||
return selector(conf).Select(x =>
|
||||
{
|
||||
return (CustomOrderDefinition.CustomOneofCase)System.Enum.Parse(typeof(CustomOrderDefinition.CustomOneofCase), x, true);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
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 = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
},
|
||||
// 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
|
||||
},
|
||||
attachable_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Iceberg,
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
};
|
||||
|
||||
// exchange_order_types are the types which can be submitted on an exchange security
|
||||
exchange_order_types = algo_config(x => x.exchange_order_types),
|
||||
spreader_order_types = algo_config(x => x.spreader_order_types),
|
||||
attachable_order_types = algo_config(x => x.attachable_order_types),
|
||||
outright_order_types = algo_config(x => x.outright_order_types);
|
||||
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
|
||||
|
||||
public static void copy_values(CustomOrderDefinitionImmutable source, ref CustomOrderDefinition target) =>
|
||||
@@ -680,17 +707,11 @@ namespace mbts.spark.utilities {
|
||||
// That causes problems in some usages (e.g., AlgoStudio, and the Ladder/Ticket algo popup), bc double.NaN != double.NaN.
|
||||
// These equality comparers should be user in such cases, as they use the actual double.Equals method, not the == operator.
|
||||
// https://github.com/protocolbuffers/protobuf/issues/3725
|
||||
public static readonly IEqualityComparer<CustomOrderDefinitionImmutable> custom_order_def_compareri;
|
||||
public static readonly IEqualityComparer<CompositeAlgoDefinitionImmutable> algo_def_compareri;
|
||||
public static readonly IEqualityComparer<CustomOrderDefinitionImmutable>
|
||||
custom_order_defi_comparer = new CustomDefinitionComparer(),
|
||||
custom_order_defi_structure_comparer = new CustomDefinitionStructureComparer();
|
||||
|
||||
static EqualsHelpers() {
|
||||
int mod = MathUtils.Primes.P_1087;
|
||||
custom_order_def_compareri = new _Compareri(mod);
|
||||
algo_def_compareri = new CompositeAlgoDefinitionImmutableComparer(mod, custom_order_def_compareri);
|
||||
}
|
||||
|
||||
public static bool equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) => custom_order_def_compareri.Equals(a, b);
|
||||
public static bool equals(CompositeAlgoDefinitionImmutable a, CompositeAlgoDefinitionImmutable b) => algo_def_compareri.Equals(a, b);
|
||||
public static bool equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) => custom_order_defi_comparer.Equals(a, b);
|
||||
|
||||
public static bool structure_equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
|
||||
return impl(a.CloneToMutable(), b.CloneToMutable());
|
||||
@@ -710,7 +731,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) &&
|
||||
@@ -835,11 +856,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
class _Compareri : IEqualityComparer<CustomOrderDefinitionImmutable> {
|
||||
readonly int _mod;
|
||||
|
||||
public _Compareri(int mod) => _mod = mod;
|
||||
|
||||
class CustomDefinitionComparer : IEqualityComparer<CustomOrderDefinitionImmutable> {
|
||||
public bool Equals(CustomOrderDefinitionImmutable a, CustomOrderDefinitionImmutable b) {
|
||||
if (a.CustomCase != b.CustomCase) return false;
|
||||
switch (a.CustomCase) {
|
||||
@@ -901,7 +918,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;
|
||||
@@ -923,29 +940,17 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
|
||||
void _add_component(ref int hash, ref int phase_mult, int component_hash) =>
|
||||
hash += EqualityComparer.phase_hash(component_hash, phase_mult++, _mod);
|
||||
hash += EqualityComparer.phase_hash(component_hash, phase_mult++, MathUtils.Primes.P_1087);
|
||||
|
||||
int _get_hash(CustomOrderDefinitionImmutable? a) => a.HasValue ? GetHashCode(a.Value) : 0;
|
||||
}
|
||||
|
||||
class CompositeAlgoDefinitionImmutableComparer : IEqualityComparer<CompositeAlgoDefinitionImmutable> {
|
||||
readonly int _mod;
|
||||
readonly IEqualityComparer<CustomOrderDefinitionImmutable> _def_comparer;
|
||||
class CustomDefinitionStructureComparer : IEqualityComparer<CustomOrderDefinitionImmutable> {
|
||||
public bool Equals(CustomOrderDefinitionImmutable def1, CustomOrderDefinitionImmutable def2) =>
|
||||
enumerate_algo_structure(def1).SequenceEqual(enumerate_algo_structure(def2));
|
||||
|
||||
public CompositeAlgoDefinitionImmutableComparer(int mod, IEqualityComparer<CustomOrderDefinitionImmutable> def_comparer) {
|
||||
_mod = mod;
|
||||
_def_comparer = def_comparer;
|
||||
}
|
||||
|
||||
public bool Equals(CompositeAlgoDefinitionImmutable x, CompositeAlgoDefinitionImmutable y) =>
|
||||
x.Name == y.Name &&
|
||||
x.HasAlgo ?
|
||||
y.HasAlgo && _def_comparer.Equals(x.Algo, y.Algo) :
|
||||
!y.HasAlgo;
|
||||
|
||||
public int GetHashCode(CompositeAlgoDefinitionImmutable algo_def) =>
|
||||
EqualityComparer.safe_get_hash_code(algo_def.Name) % _mod +
|
||||
EqualityComparer.phase_hash(algo_def.HasAlgo ? _def_comparer.GetHashCode(algo_def.Algo) : 0, 1, _mod);
|
||||
public int GetHashCode(CustomOrderDefinitionImmutable def) =>
|
||||
EqualityComparer.auto_phase(enumerate_algo_structure(def));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,8 +969,8 @@ namespace mbts.spark.utilities {
|
||||
// param types
|
||||
public static QuantityMeasure QuantityMeasure => new QuantityMeasure { Absolute = 1 };
|
||||
// core data types
|
||||
public static AlgoData AlgoData() => new AlgoData(CustomOrderDefinitionImmutable());
|
||||
public static AlgoData AlgoData(CustomOrderDefinition.CustomOneofCase algo_type) => new AlgoData(CustomOrderDefinitionImmutable(algo_type));
|
||||
public static AlgoData AlgoData() => spark.AlgoData.from_custom_def(CustomOrderDefinitionImmutable());
|
||||
public static AlgoData AlgoData(CustomOrderDefinition.CustomOneofCase algo_type) => spark.AlgoData.from_custom_def(CustomOrderDefinitionImmutable(algo_type));
|
||||
|
||||
public static OrderDefinition OrderDefinition => new OrderDefinition { CustomDef = new CustomOrderDefinition() };
|
||||
public static OrderRecord OrderRecord => new OrderRecord {
|
||||
@@ -1000,6 +1005,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;
|
||||
}
|
||||
@@ -1025,6 +1033,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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 455 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user