Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0fdf74a73 | ||
|
|
ebedd17129 | ||
|
|
0f8ed5dacb | ||
|
|
8553229119 | ||
|
|
4903c12533 | ||
|
|
88f23530c6 | ||
|
|
14f4452599 | ||
|
|
22002c43cf | ||
|
|
7551e23877 | ||
|
|
22c92c3e17 | ||
|
|
613823e7c5 | ||
|
|
f0a9a2415d | ||
|
|
708e63e36e | ||
|
|
76ff36e514 | ||
|
|
740c14c261 | ||
|
|
53990297e8 | ||
|
|
9570be8bfa | ||
|
|
0b15570d00 | ||
|
|
33a71f5f47 | ||
|
|
2940601f4b | ||
|
|
1f7939f49a | ||
|
|
48e273a607 | ||
|
|
e35634020d | ||
|
|
1ede053f59 | ||
|
|
93824786c7 | ||
|
|
25b8c0a728 | ||
|
|
0e4fe1b47e | ||
|
|
cd744963ff | ||
|
|
fb9d877938 | ||
|
|
087b001510 | ||
|
|
02a7c0ddca | ||
|
|
6afa1d581d | ||
|
|
5e04abe573 | ||
|
|
3422a44c87 | ||
|
|
d5fdcc6796 | ||
|
|
f12217d76c | ||
|
|
d85a914046 | ||
|
|
4fdc890cb5 | ||
|
|
3ed0bd9052 | ||
|
|
dbdc915ff2 | ||
|
|
10476c6662 | ||
|
|
5fbc1397b5 | ||
|
|
05e09cb977 | ||
|
|
38bd2ef321 | ||
|
|
dab4f5acbb | ||
|
|
a20c53b23d | ||
|
|
8206f457b5 | ||
|
|
d4e6d6293d | ||
|
|
d1a2197ba4 | ||
|
|
369fb55e90 | ||
|
|
37dacff74d | ||
|
|
d297b703c7 | ||
|
|
59c00203a9 | ||
|
|
33f533af9b | ||
|
|
085d6402f4 | ||
|
|
f4395ef833 | ||
|
|
e7a9412124 | ||
|
|
da4ee38abd | ||
|
|
706e5bc73b | ||
|
|
af39fac368 | ||
|
|
4f997505f3 | ||
|
|
fcbb66361d | ||
|
|
fa16321691 | ||
|
|
ceb3542e15 | ||
|
|
32e55d07ee | ||
|
|
e079acf91f | ||
|
|
fecedb203a | ||
|
|
6ac05a47e1 | ||
|
|
36ed80dc87 | ||
|
|
6cf395199e | ||
|
|
1707c3268f | ||
|
|
f5bbc4245f | ||
|
|
96fc54ff0f | ||
|
|
25b69e4f7d | ||
|
|
d871ffca6d | ||
|
|
213567bcdb | ||
|
|
b7e5c3a374 | ||
|
|
ae4a8bbf46 | ||
|
|
850386c1d4 | ||
|
|
2d4e94a636 | ||
|
|
9b73ad2486 | ||
|
|
e447096733 | ||
|
|
cd68b95a3e | ||
|
|
205eebb7da | ||
|
|
ef4dc1fc65 | ||
|
|
ebad5b389f | ||
|
|
160c49514a | ||
|
|
4f7fcc5634 | ||
|
|
500e5b5fe4 | ||
|
|
9d924fa4ba | ||
|
|
092e63e300 | ||
|
|
4654cd4739 | ||
|
|
f2f06d76de | ||
|
|
6bc293a58e | ||
|
|
fdb462eda4 | ||
|
|
cc579034b2 | ||
|
|
4b76e91757 | ||
|
|
8d34279ab0 | ||
|
|
7973b927a2 | ||
|
|
6997aed9bd | ||
|
|
b5d7d98098 | ||
|
|
8f22a326ab | ||
|
|
b2c3488c5e | ||
|
|
23f10eac70 | ||
|
|
cb31269a70 | ||
|
|
5b75b8718a | ||
|
|
5a256d4634 | ||
|
|
db2de3e8c4 | ||
|
|
b9dcad9549 | ||
|
|
ce104479e1 | ||
|
|
2d56eedf3e | ||
|
|
049381908c | ||
|
|
0067e52155 | ||
|
|
8ed6d297a9 | ||
|
|
474401cddc | ||
|
|
a06df5099a | ||
|
|
8ddd5bdbb5 | ||
|
|
5cbe00beab | ||
|
|
3ebcf9a270 | ||
|
|
40cd826260 | ||
|
|
8bd63f11b1 | ||
|
|
2198a8d6ed | ||
|
|
8023dc7aa8 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
bef46390be | ||
|
|
be5d037ca9 | ||
|
|
4edd804b3c | ||
|
|
86733747f1 | ||
|
|
5202300fb1 | ||
|
|
21f5fd919d | ||
|
|
7aae61decd | ||
|
|
64f78e6077 | ||
|
|
318ea5909c | ||
|
|
83190b437a | ||
|
|
5a54b37af7 | ||
|
|
12dd3909bf | ||
|
|
64fd8addb2 | ||
|
|
5ddb0f92ea | ||
|
|
69f3f1f467 | ||
|
|
be42f9e583 | ||
|
|
6d069f5174 | ||
|
|
7a31adcc1e | ||
|
|
512175966d | ||
|
|
67de7c5a33 | ||
|
|
8add53a559 | ||
|
|
3e7e572763 | ||
|
|
159e8fba23 | ||
|
|
0d020e9821 | ||
|
|
4490ac303e | ||
|
|
fb1d70b08c | ||
|
|
c2f6b01567 | ||
|
|
4a758c7554 | ||
|
|
5c79a08f0e | ||
|
|
4b7adbe0c9 | ||
|
|
164c88fa60 | ||
|
|
b57d6cce85 | ||
|
|
e47706eb1b | ||
|
|
c2e91b03f6 | ||
|
|
114c9f3c27 | ||
|
|
40805be11e | ||
|
|
b21cd840c2 | ||
|
|
4854a06eda | ||
|
|
44e7cfb4a5 | ||
|
|
a7da930e69 | ||
|
|
d64df27188 | ||
|
|
93534eb801 | ||
|
|
d1b2dbd688 | ||
|
|
bebe0c2478 | ||
|
|
61126fb3d5 | ||
|
|
0af5b78318 | ||
|
|
c652032ddf | ||
|
|
7bd868b753 | ||
|
|
1bc4510862 | ||
|
|
e452413b92 | ||
|
|
b0886f6b3d | ||
|
|
62c7886807 | ||
|
|
b1fb964648 | ||
|
|
ee19f7c7da | ||
|
|
a0bf7779f5 | ||
|
|
cd41632b53 | ||
|
|
95a5791867 | ||
|
|
ecd384501e | ||
|
|
0483460ac9 | ||
|
|
666bd07411 | ||
|
|
0bc24fa7b7 | ||
|
|
af365177d0 | ||
|
|
ac60a1447e | ||
|
|
e8b5d4f8fd | ||
|
|
0ff4624778 | ||
|
|
ab04b4afab | ||
|
|
ac226ddd3b | ||
|
|
0223e43aa2 | ||
|
|
73e5cfdada | ||
|
|
33b4b88af1 | ||
|
|
12f4b4b25b | ||
|
|
7fd8eeb1ff | ||
|
|
96cacb8bb9 | ||
|
|
300a1ed17f | ||
|
|
1a0836ca6d | ||
|
|
cb157dbce1 | ||
|
|
88908543e1 | ||
|
|
80c9b6a534 | ||
|
|
f6282c0676 | ||
|
|
370b4bcde9 | ||
|
|
6756b23104 | ||
|
|
c5f7ee0ed3 | ||
|
|
02fe83f20b | ||
|
|
84d666c29c | ||
|
|
16eeddec7d | ||
|
|
147e43b464 | ||
|
|
b909a6062e | ||
|
|
421b3af6a7 | ||
|
|
46a340d493 | ||
|
|
3cc7d76a1b | ||
|
|
5123b928f2 | ||
|
|
c2631e3257 | ||
|
|
445bbb10f5 | ||
|
|
70c006f797 | ||
|
|
685181ae28 | ||
|
|
929492c2f0 | ||
|
|
be023ff6cb | ||
|
|
307d3d9466 | ||
|
|
3a5810d8ac | ||
|
|
9ba2ba1d75 | ||
|
|
29c772cbb6 | ||
|
|
0b03180c4d | ||
|
|
1518906b7f | ||
|
|
31fe708907 | ||
|
|
4b2f39c536 | ||
|
|
040960d33b | ||
|
|
a4d63a1421 | ||
|
|
a28d5883f7 | ||
|
|
8a1d49733e | ||
|
|
0da6b2cc3e | ||
|
|
2fd8c39888 | ||
|
|
eb97fd045a | ||
|
|
a07bc152d1 | ||
|
|
aef8e2dbb1 | ||
|
|
3f245cd886 | ||
|
|
570f85377a | ||
|
|
8afb35e564 | ||
|
|
a38a6057e0 | ||
|
|
dcd8984b33 | ||
|
|
6647b28a9f | ||
|
|
baac6a893d | ||
|
|
df681ca209 | ||
|
|
21b34434ef | ||
|
|
0c17dbb55b | ||
|
|
fd6c6a3055 | ||
|
|
259355d629 | ||
|
|
c6c2d3448d | ||
|
|
f860649996 | ||
|
|
05c3c97713 | ||
|
|
f3be277484 | ||
|
|
fb314f0eb1 | ||
|
|
0ba311788c | ||
|
|
341eda5962 | ||
|
|
3398bb5b86 | ||
|
|
dc7cacc126 | ||
|
|
6d3d4e1f56 | ||
|
|
433b1abecd | ||
|
|
4544180c5f | ||
|
|
5722fcffc3 | ||
|
|
f1880615b1 | ||
|
|
e60f5678ec | ||
|
|
e9a70e51e4 | ||
|
|
e98d18a5aa | ||
|
|
513cc248b8 | ||
|
|
c024095f75 | ||
|
|
3e5eb80a7c | ||
|
|
942dc1640e | ||
|
|
b5c3abb782 | ||
|
|
3478d1ad52 | ||
|
|
d54e9233d1 | ||
|
|
e94759cd89 | ||
|
|
b58da4a20e | ||
|
|
8426399a23 | ||
|
|
8a965d41ff | ||
|
|
9b2f41c805 | ||
|
|
15f96dda3e | ||
|
|
a9d5c17d63 | ||
|
|
715b629778 | ||
|
|
dd82d127ea | ||
|
|
f97e22d6eb | ||
|
|
f9e04ee28c | ||
|
|
1ce00ca13d | ||
|
|
3b9c2f5d70 | ||
|
|
5cd2dfec18 | ||
|
|
31b2ba9165 | ||
|
|
2b1b1f1681 | ||
|
|
4427b02d8d | ||
|
|
dcd3921379 | ||
|
|
bd156ecbed | ||
|
|
02f5731e1d | ||
|
|
7e75a71898 | ||
|
|
798c4dcd49 | ||
|
|
7e47d7f8a5 | ||
|
|
260ca06cde | ||
|
|
7d75d87e6b | ||
|
|
f13a3325bc | ||
|
|
8615adb564 | ||
|
|
ab15d9d348 | ||
|
|
9540a15585 | ||
|
|
e8a467d5f1 | ||
|
|
567c18f325 | ||
|
|
270ab9c3f1 | ||
|
|
9a9024c2c0 | ||
|
|
45a5049a2d | ||
|
|
8b17911da0 | ||
|
|
61556eb534 | ||
|
|
b691dbae80 | ||
|
|
f6be20ce8d | ||
|
|
68428ab89d | ||
|
|
12d54fdb31 | ||
|
|
366e13ef05 | ||
|
|
e94ef6a602 | ||
|
|
5c22b43eb5 | ||
|
|
3e7f145290 | ||
|
|
748a99d4ef | ||
|
|
35b8c1e53f | ||
|
|
123b690c6c | ||
|
|
38a65bd0e3 | ||
|
|
1cb8c54225 | ||
|
|
414833c0c5 | ||
|
|
6cc4064cd3 | ||
|
|
0fcdc9c493 | ||
|
|
0f967ae4cd | ||
|
|
a232d738a0 | ||
|
|
3d3ac4e49f | ||
|
|
15a5abf1ad | ||
|
|
76218fd35a | ||
|
|
d0370c5dc7 | ||
|
|
b07700cdc7 | ||
|
|
15de3bbfe9 | ||
|
|
8d2fa40dcc | ||
|
|
3758ac6101 | ||
|
|
ac45e9bdb2 | ||
|
|
769bad89a7 | ||
|
|
7dc272506a | ||
|
|
86c787efaf | ||
|
|
f917f581c3 | ||
|
|
bb0171a2e4 | ||
|
|
88bf448e68 | ||
|
|
05a615038d | ||
|
|
51cfd0e5bb | ||
|
|
d2ee575f19 | ||
|
|
0b76ffb4f3 | ||
|
|
0f9b93115a | ||
|
|
8cfc6a7591 | ||
|
|
9de2dd30bc | ||
|
|
38005e3266 | ||
|
|
26ae70f432 | ||
|
|
e7cea797ba | ||
|
|
f16545dfaa | ||
|
|
dcd62409f1 | ||
|
|
11252b4300 | ||
|
|
90526a8bfd | ||
|
|
e815dade3a | ||
|
|
576be7e961 | ||
|
|
407de320f6 | ||
|
|
bc0234a5e9 | ||
|
|
e1229186de | ||
|
|
a3e00217a1 | ||
|
|
16f0beb3d8 | ||
|
|
d244c62c26 | ||
|
|
44730204a5 | ||
|
|
bd47816d16 | ||
|
|
2f12f2609a | ||
|
|
9c489c03c6 | ||
|
|
5ae8a66558 | ||
|
|
03882718a0 | ||
|
|
0f21d9783b | ||
|
|
2c547f37fb | ||
|
|
a7f4944bd6 | ||
|
|
a3a33ffb6a | ||
|
|
b3db0fc4ca | ||
|
|
0f066fdeb9 | ||
|
|
22f89490a3 | ||
|
|
2137d37cbd | ||
|
|
082a241bb9 | ||
|
|
0057f8f080 | ||
|
|
4768c1cd87 | ||
|
|
57b70684ea | ||
|
|
af64592f52 | ||
|
|
a1b89ccb34 | ||
|
|
44f2a9daf7 | ||
|
|
0e5833b275 | ||
|
|
bded6d334c | ||
|
|
893c6702a2 | ||
|
|
627b8742c4 | ||
|
|
1f7e0a672e | ||
|
|
29e8fd1050 | ||
|
|
6ce741bcaf | ||
|
|
8dd070961a | ||
|
|
f6a3a276d9 | ||
|
|
68c8828d4f | ||
|
|
0d68ebbbea | ||
|
|
cc04e79f8b | ||
|
|
ac76462f7e | ||
|
|
d20b30557f | ||
|
|
a6fa5f9852 | ||
|
|
0db165bf76 | ||
|
|
705fd293ba | ||
|
|
2172f007a8 | ||
|
|
9d762e0777 | ||
|
|
ef53c70c7e | ||
|
|
8cf4e37310 | ||
|
|
04e7180b69 | ||
|
|
d40d3615b2 | ||
|
|
4efec939aa | ||
|
|
a0f24d8227 | ||
|
|
a2278423f6 | ||
|
|
7321d5d8f7 | ||
|
|
464426537e | ||
|
|
ebb2d5618c | ||
|
|
7b9c10f47c | ||
|
|
478640e808 | ||
|
|
ab993423a6 | ||
|
|
ab8802c3f1 | ||
|
|
42906f5a37 | ||
|
|
931ed76673 | ||
|
|
ebc4e202fe | ||
|
|
a7dda587ba | ||
|
|
1ace2b7a3f | ||
|
|
af2875e0ef | ||
|
|
4138ab0730 | ||
|
|
86467b2be1 | ||
|
|
7b61b4bd5b | ||
|
|
1bcb7ac87f | ||
|
|
52305d69f2 | ||
|
|
bd70e7c09e | ||
|
|
d04f9f4e96 | ||
|
|
c0d097cb40 | ||
|
|
533e688d1a | ||
|
|
36d40dec70 | ||
|
|
a3f5de099f | ||
|
|
9b497e323f | ||
|
|
9a5b5595c3 | ||
|
|
24f23adf24 | ||
|
|
cc26d0bd8b | ||
|
|
96bb63ba6d | ||
|
|
0ea72b017e | ||
|
|
698612bb3d | ||
|
|
2f23292836 | ||
|
|
085e6ae95a | ||
|
|
3be2aedfe3 | ||
|
|
1f01d974e3 | ||
|
|
910c04544b | ||
|
|
10674b8930 | ||
|
|
8c9522e22b | ||
|
|
f60afe1196 | ||
|
|
0cc30a7934 |
No files matched your search
@@ -32,3 +32,4 @@ trace_data/
|
||||
/src/lib/orderman/perf_analysis/last_pretty_log.csv
|
||||
/src/lib/orderman/perf_analysis/log.csv
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
Vendored
+2
-2
@@ -104,7 +104,7 @@
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
@@ -249,7 +249,7 @@
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[mbo]"
|
||||
"SingleWriterConcurrentVector"
|
||||
],
|
||||
"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)
|
||||
@@ -4,6 +4,129 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
|
||||
## 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
|
||||
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
|
||||
- Fixed Net/Absolute radio button in risk admin UI
|
||||
- Fixed double counting of spread legs in limits
|
||||
- Removed expired fills from the system
|
||||
- Improved handling of orders with undiscovered securities
|
||||
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
|
||||
- Fixed display of algo studio algo types in order book
|
||||
- Hid net change setting in synthetic ladders
|
||||
- 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
|
||||
|
||||
## 2.1.3
|
||||
### User Visible Changes
|
||||
- Fixed a simulator bug in the handling of 0 sized orders
|
||||
|
||||
## 2.2.1
|
||||
### User Visible Changes
|
||||
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
|
||||
|
||||
## 2.1.1
|
||||
### User Visible Changes
|
||||
- Added PIQ to GLBX Iceberg orders
|
||||
- Corrected handling of stop limit orders
|
||||
|
||||
## 2.1.0
|
||||
### User Visible Changes
|
||||
- Added support for the GLBX Iceberg order type
|
||||
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
|
||||
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
|
||||
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
|
||||
- Added day position information to the ladder
|
||||
- Added PIQ to the mini order book
|
||||
- Fixed a rare bug that could lead to server failure when requesting market data
|
||||
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
|
||||
|
||||
## 2.0.5
|
||||
### User Visible Changes
|
||||
- Fixed a bug in the handling of rejected QHO orders
|
||||
- Implemented stricter position limit enforcement
|
||||
- Fixed a bug that could cause server failure when trying to attach to already attached orders
|
||||
|
||||
## 2.0.4
|
||||
### User Visible Changes
|
||||
- Fixed performance regression in order manager
|
||||
|
||||
### Internal changes
|
||||
- Improved market data diagnostics for bad configurations
|
||||
- Changed market data default buffer size
|
||||
|
||||
## 2.0.3
|
||||
### Internal changes
|
||||
- Improved backwards compatibility in UI settings
|
||||
@@ -295,6 +418,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,10 +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();
|
||||
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;
|
||||
|
||||
@@ -24,10 +24,13 @@ namespace mbts.ui {
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
|
||||
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
|
||||
DocumentGeometryKey = nameof(DocumentGeometryKey),
|
||||
DocumentAddGeometryKey = nameof(DocumentAddGeometryKey),
|
||||
DocumentBrokenGeometryKey = nameof(DocumentBrokenGeometryKey),
|
||||
DocumentCopyGeometryKey = nameof(DocumentCopyGeometryKey),
|
||||
DocumentEmptyGeometryKey = nameof(DocumentEmptyGeometryKey),
|
||||
@@ -145,11 +148,14 @@ 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);
|
||||
public static ImageSource Disk => create_icon(DiskGeometryKey);
|
||||
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
public static ImageSource DocumentAdd => create_icon(DocumentAddGeometryKey);
|
||||
public static ImageSource DocumentBroken => create_icon(DocumentBrokenGeometryKey);
|
||||
public static ImageSource DocumentCopy => create_icon(DocumentCopyGeometryKey);
|
||||
public static ImageSource DocumentEmpty => create_icon(DocumentEmptyGeometryKey);
|
||||
|
||||
@@ -48,6 +48,14 @@
|
||||
</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>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/chanut
|
||||
Ttile: Cancel free icon
|
||||
@@ -107,6 +115,9 @@
|
||||
s1-0.448,1-1v-5h5c0.552,0,1-0.448,1-1S52.052,46,51.5,46z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DataEditGeometryKey}">
|
||||
M17.997977,17.396268C18.197928,17.396268,18.297904,17.496276,18.497975,17.596284L19.497972,18.295283C19.99797,18.595292,20.697919,19.195308,21.197917,19.495299L22.196937,20.195323C22.696935,20.495315,22.596959,20.995322,22.096961,21.195338L20.997966,21.695346C20.497968,21.89434,19.597947,22.294355,19.097949,22.494355L17.997977,22.994363C17.497979,23.194362,17.097957,22.894355,17.197932,22.394347L17.297908,21.195338C17.398005,20.595323,17.497979,19.695315,17.497979,19.195308L17.597955,17.995277C17.497979,17.695283,17.69793,17.396268,17.997977,17.396268z M1.799981,13.597213L19.697923,13.597213 17.997977,15.896244C17.797906,16.096259 17.69793,16.396253 17.69793,16.69626 17.197932,16.796268 16.79791,17.296275 16.697934,17.895284L16.597959,19.195308 1.799981,19.195308C0.79998477,19.195308,0,18.395291,0,17.396268L0,15.396237C0,14.396221,0.79998477,13.597213,1.799981,13.597213z M1.799981,6.7981106L21.796914,6.7981106C22.696935,6.7981106,23.49698,7.498118,23.596955,8.3981257L20.697919,12.397197 1.799981,12.397197C0.79998477,12.397197,0,11.597181,0,10.597165L0,8.5981418C0,7.598126,0.79998477,6.7981106,1.799981,6.7981106z M27.796891,4.3990708C27.996963,4.3990708,28.096938,4.3990708,28.296889,4.5990711L31.695924,7.0981179C31.995851,7.3981105,32.095948,7.7981258,31.895875,8.0981341L23.296908,19.795308C23.096957,20.095315,22.596959,20.195323,22.196937,19.895314L18.797902,17.396268C18.497975,17.096274,18.398002,16.69626,18.597951,16.396253L27.196918,4.6990786C27.396869,4.4990784,27.59694,4.3990708,27.796891,4.3990708z M1.799981,0L21.796914,0C22.79691,4.9549271E-08,23.596955,0.8000156,23.596955,1.800031L23.596955,3.7990554C23.596955,4.7990709,22.79691,5.5990864,21.796914,5.5990864L1.799981,5.5990864C0.79998477,5.4990941,0,4.6990786,0,3.6990629L0,1.7000236C0,0.8000156,0.79998477,4.9549271E-08,1.799981,0z
|
||||
</sys:String>
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DiskGeometryKey}">
|
||||
M403.213,20H0v472h512V128.787L403.213,20z M131,50h180v100h10h20h10V50h30v130H131V50z M381,462H131V315
|
||||
c0-8.271,6.729-15,15-15h220c8.271,0,15,6.729,15,15V462z M482,462h-71V315c0-24.813-20.187-45-45-45H146
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
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;
|
||||
@@ -117,8 +119,8 @@ namespace mbts.spark {
|
||||
// show fills
|
||||
var show_fills_cmd = new DelegateCommand(() => {
|
||||
var sc = _selected_security_criteria;
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsWindowFactory.StartupData(
|
||||
new FillsWindowFactory.FilterStates {
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
|
||||
new FillsFilterStates {
|
||||
security = sc.is_defined() ? sc : _state.security_criteria
|
||||
}));
|
||||
});
|
||||
@@ -134,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),
|
||||
@@ -143,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,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
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;
|
||||
@@ -16,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 {
|
||||
@@ -69,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;
|
||||
}))
|
||||
@@ -92,8 +92,8 @@ namespace mbts.spark {
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
FillsWindowFactory.StartupData get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
|
||||
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
new FillsSettings(new FillsFilterStates {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
});
|
||||
void update_criteria(SecurityCriteria c) {
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ShowQuantityTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
>
|
||||
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
|
||||
<DataTemplate>
|
||||
@@ -115,6 +116,16 @@
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
|
||||
<DataTemplate>
|
||||
<!-- Note: the motivation for this Template was to enforce a Minimum via the TickUpDownUInt -->
|
||||
<!-- As of 2019/06/17, we believe such a Minimum/Maximum is not actually getting enforced and -->
|
||||
<!-- furthermore is not the appropriate way to go about enforcing said value. It is near the end -->
|
||||
<!-- of the cycle though so that is why we are leaving this Template in here for now until we -->
|
||||
<!-- can come up with a better solution. -->
|
||||
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}" />
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace mbts.ui.spark {
|
||||
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
|
||||
var rv = new ContentControl();
|
||||
rv.SetBinding(ContentControl.ContentProperty, new Binding());
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding() { Converter = cell_template_converter });
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -306,17 +306,23 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate MarketDataTypeTemplate { get; set; }
|
||||
public DataTemplate MarketDataCalculationTemplate { get; set; }
|
||||
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
|
||||
public DataTemplate ShowQuantityTemplate { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
Type t = (Type)value;
|
||||
if (t == typeof(double)) return DoubleTemplate;
|
||||
else if (t == typeof(bool)) return BooleanTemplate;
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
else return null;
|
||||
if (value is ICell cell) {
|
||||
if (cell.row is ShowQuantityQuantityModel) {
|
||||
return ShowQuantityTemplate;
|
||||
}
|
||||
Type t = cell.data_type;
|
||||
if (t == typeof(double)) return DoubleTemplate;
|
||||
else if (t == typeof(bool)) return BooleanTemplate;
|
||||
else if (t == typeof(int)) return IntTemplate;
|
||||
else if (t == typeof(uint)) return UIntTemplate;
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.synthetic;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -14,12 +15,14 @@ namespace mbts.spark {
|
||||
readonly IBus _bus;
|
||||
readonly SyntheticSecurityService _service;
|
||||
readonly ErrorReporter _error;
|
||||
readonly uint _user_id;
|
||||
|
||||
[ImportingConstructor]
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
|
||||
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
|
||||
_bus = bus;
|
||||
_service = service;
|
||||
_error = error;
|
||||
_user_id = user_id;
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
|
||||
@@ -39,29 +42,40 @@ namespace mbts.spark {
|
||||
return (result, success);
|
||||
});
|
||||
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
await lookup_strategy_async(security.Id);
|
||||
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
|
||||
lookup_strategy_async(security.Id);
|
||||
|
||||
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
|
||||
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
|
||||
if (result.success) {
|
||||
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
|
||||
if (metadata != null) {
|
||||
return metadata.Strategy;
|
||||
}
|
||||
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
|
||||
_error.safe_execute(
|
||||
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
|
||||
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
|
||||
mdc => $"did not find strategy for security id '{id}.'",
|
||||
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
|
||||
"lookup strategy");
|
||||
|
||||
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return null;
|
||||
if (synthetic.Synthetic.UserId != _user_id) {
|
||||
var msynth = synthetic.CloneToMutable();
|
||||
msynth.Synthetic.UserId = _user_id;
|
||||
synthetic = new SecurityImmutable(msynth);
|
||||
}
|
||||
return null;
|
||||
return _error.safe_execute(
|
||||
() => _service.create_synthetic_async(synthetic),
|
||||
r => r.Error == CreateResult.Types.Error.Ok,
|
||||
r => r.Error.ToString(),
|
||||
(r, success) => success ? r.Key : (uint?)null,
|
||||
"creating synthetic");
|
||||
}
|
||||
|
||||
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
|
||||
var result = await _service.create_synthetic_async(synthetic.Value);
|
||||
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
|
||||
}
|
||||
|
||||
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
|
||||
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
|
||||
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
|
||||
}
|
||||
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
_error.safe_execute(
|
||||
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
|
||||
r => r.Error == UpdateResult.Types.Error.Ok,
|
||||
r => r.Error.ToString(),
|
||||
(r, success) => success,
|
||||
"saving strategy");
|
||||
|
||||
public async Task<bool> verify_name_is_unique(string name) {
|
||||
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
|
||||
@@ -108,6 +108,9 @@ namespace bts.shell {
|
||||
NTicks = 2,
|
||||
PriceType = ConditionalQuoting.Types.PriceType.Leg
|
||||
};
|
||||
public static NullableQuantity ShowQuantity => new NullableQuantity {
|
||||
Quantity = 1
|
||||
};
|
||||
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
|
||||
#endregion
|
||||
|
||||
@@ -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 " +
|
||||
|
||||
@@ -76,8 +76,8 @@ namespace mbts.spark {
|
||||
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
|
||||
}
|
||||
}));
|
||||
contract_picker_model.cancel_action = _close_new_leg_popup;
|
||||
contract_picker_model.ok_action = () => {
|
||||
contract_picker_model.cancel_action += _close_new_leg_popup;
|
||||
contract_picker_model.ok_action += () => {
|
||||
if (!leg_tuples.Any(l => l.leg.Security.Id == contract_picker_model.result.security.Id)) {
|
||||
leg_tuples.Add(new LegSpreaderParamsTuple(
|
||||
SyntheticDataHelper.Defaults.ContractLeg(contract_picker_model.result.security),
|
||||
@@ -134,7 +134,7 @@ namespace mbts.spark {
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
uint? id = await model.create_synthetic(_synthetic);
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
|
||||
@@ -177,6 +177,8 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
var show_quantity_quantity = new ShowQuantityQuantityModel();
|
||||
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
|
||||
|
||||
@@ -211,6 +213,8 @@ namespace mbts.spark {
|
||||
//new QuoteHiddenModel(),
|
||||
new QuoteQueueHolderModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new ShowQuantityEnabledModel(show_quantity_quantity),
|
||||
show_quantity_quantity,
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
@@ -321,9 +325,12 @@ namespace mbts.spark {
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
synthetic.Synthetic.Legs
|
||||
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) {
|
||||
foreach (var l in synthetic.Synthetic.Legs) {
|
||||
if (strategy.LegParamsMap.TryGetValue(l.Security.Id, out var p))
|
||||
yield return new LegSpreaderParamsTuple(l, p);
|
||||
}
|
||||
}
|
||||
|
||||
void _close_new_leg_popup() {
|
||||
is_new_leg_popup_open = false;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
using bts.api;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using mbts;
|
||||
|
||||
namespace bts.shell {
|
||||
public class BrowserWindow : IShellWindow {
|
||||
@@ -75,12 +70,15 @@ namespace bts.shell {
|
||||
public string window_type => _window_type;
|
||||
|
||||
public IShellModule Owner {get; set; }
|
||||
|
||||
public ShellWindowUI GetContent() { return new ShellWindowUI(_browser); }
|
||||
|
||||
public void GetState(System.IO.Stream save_here) {
|
||||
_isw.save(new BrowserWindowState() { uri = uri.ToString() }, save_here);
|
||||
}
|
||||
|
||||
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
|
||||
|
||||
public void Dispose() {
|
||||
if (_browser != null) {
|
||||
_browser.Dispose();
|
||||
@@ -102,5 +100,4 @@ namespace bts.shell {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ namespace mbts.spark {
|
||||
} else if (t.definition != null) {
|
||||
return t.definition.subscribe_is_definition_dirty();
|
||||
} else return Observable.Return(false);
|
||||
}).RefCount();
|
||||
});
|
||||
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
|
||||
_vm.save_template(t).ignore();
|
||||
@@ -116,7 +116,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_commands.Add(undo_spark_cmd);
|
||||
_resources.Add(can_undo_source.Subscribe(undo_cmd));
|
||||
_resources.Add(can_undo_source.Connect());
|
||||
// redo
|
||||
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
|
||||
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
|
||||
@@ -133,7 +132,6 @@ namespace mbts.spark {
|
||||
});
|
||||
_commands.Add(redo_spark_cmd);
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
_resources.Add(can_redo_source.Connect());
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
[ProtoContract]
|
||||
@@ -14,6 +16,11 @@ namespace mbts.spark {
|
||||
[ProtoMember(3)]
|
||||
readonly uint? _persisted_key;
|
||||
|
||||
public static readonly AlgoID[] exchange_algo_keys = utilities.OrderDataHelper.outright_order_types
|
||||
.Concat(new CustomOrderDefinition.CustomOneofCase[] { CustomOrderDefinition.CustomOneofCase.Spreader })
|
||||
.Select(cooc => new AlgoID(cooc))
|
||||
.ToArray();
|
||||
|
||||
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
|
||||
_factory_key = factory_key;
|
||||
_dynamic_key = null;
|
||||
@@ -32,33 +39,41 @@ namespace mbts.spark {
|
||||
_persisted_key = persisted_key;
|
||||
}
|
||||
|
||||
public static AlgoID from_order_def(OrderDefinitionImmutable order_def) {
|
||||
switch (order_def.CustomDef.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(order_def.CustomDef.CustomCase);
|
||||
public static AlgoID from_custom_def(CustomOrderDefinitionImmutable custom_def) {
|
||||
switch (custom_def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(custom_def.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(custom_def.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(custom_def.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
public static AlgoID from_algo_key(AlgoKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (key.factory.structure.structure.Count > 0)
|
||||
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
|
||||
break;
|
||||
return AlgoStructureSerializer.to_algo_id(key.factory.structure, dynamic_type_index);
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
public static AlgoKey to_algo_key(AlgoID id, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (id._factory_key.HasValue)
|
||||
return new AlgoKey(id._factory_key.Value);
|
||||
if (id._persisted_key.HasValue)
|
||||
return new AlgoKey(new AlgoPersistedKey(id._persisted_key.Value));
|
||||
if (id._dynamic_key != null)
|
||||
return new AlgoKey(new AlgoNameStructureKey(id._dynamic_key, AlgoStructureSerializer.structure_key(dynamic_type_index.to_int(id._dynamic_key))));
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoID other) =>
|
||||
_factory_key == other._factory_key &&
|
||||
_dynamic_key == other._dynamic_key &&
|
||||
_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(
|
||||
|
||||
@@ -73,6 +73,9 @@ namespace mbts.spark {
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
|
||||
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static AlgoStructureKey structure_key(int dynamic_type) =>
|
||||
new AlgoStructureKey(new[] { dynamic_type | _dynamic_mask });
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
@@ -103,6 +106,13 @@ namespace mbts.spark {
|
||||
return mdef;
|
||||
}
|
||||
|
||||
public static AlgoID to_algo_id(AlgoStructureKey key, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (key.structure.Count == 0)
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
int i = key.structure[0];
|
||||
return BitHelper.remove_flags(ref i, _dynamic_mask) ? new AlgoID(dynamic_type_index.from_int(i)) : new AlgoID((CustomOrderDefinition.CustomOneofCase)i);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
|
||||
+12
-38
@@ -1,5 +1,4 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
@@ -110,38 +109,24 @@ namespace mbts.spark {
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
return await _create_definition(definition, user_id);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.CreateDefinition);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) =>
|
||||
await _create_definition(definition, _admin_model.user.id);
|
||||
|
||||
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.CreateTemplate);
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
|
||||
new TemplateCreateRequest { UserId = _admin_model.user.id, NewTemplate = template.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(value.Record);
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
} else {
|
||||
_report_no_user_id(ActionMetadata.UpdateTemplate);
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
|
||||
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
|
||||
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
|
||||
if (success)
|
||||
return _Utils.template_data(template);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -175,17 +160,6 @@ namespace mbts.spark {
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
bool _try_get_user(out uint id) {
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
id = user.Value.id;
|
||||
return true;
|
||||
} else {
|
||||
id = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// used for error handling/reporting
|
||||
interface IActionMetadata {
|
||||
string desc { get; }
|
||||
|
||||
+11
-24
@@ -1,14 +1,11 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman.algo;
|
||||
using bts.persist.algo;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -27,7 +24,12 @@ namespace mbts.spark {
|
||||
_admin_model = admin_model;
|
||||
_error_reporter = error_reporter;
|
||||
this.ui_configuration = ui_configuration;
|
||||
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
@@ -39,15 +41,11 @@ namespace mbts.spark {
|
||||
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
if (_try_get_user(out uint user_id)) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
} else {
|
||||
_report_no_user_id("save template");
|
||||
}
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -105,16 +103,5 @@ namespace mbts.spark {
|
||||
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
|
||||
bool _try_get_user(out uint id) {
|
||||
var user = _admin_model.get_current_user();
|
||||
if (user.HasValue) {
|
||||
id = user.Value.id;
|
||||
return true;
|
||||
} else {
|
||||
id = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace mbts.spark {
|
||||
_dispose = new System.Reactive.Disposables.CompositeDisposable(
|
||||
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
|
||||
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).RefCount().Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).Subscribe(_ => _refresh_on_local_change.execute()),
|
||||
Disposable.wrap_collection(algo_map.Values)) {
|
||||
() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace mbts.spark {
|
||||
_model = model;
|
||||
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
|
||||
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).RefCount().OnDispatcher());
|
||||
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).OnDispatcher());
|
||||
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
|
||||
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
|
||||
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
|
||||
@@ -77,7 +77,7 @@ namespace mbts.spark {
|
||||
definitions_view.Refresh();
|
||||
}),
|
||||
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).RefCount().OnDispatcher()
|
||||
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -18,11 +19,9 @@ 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.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
@@ -34,22 +33,19 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
namespace mbts.spark {
|
||||
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
|
||||
readonly OrdersModel _model;
|
||||
readonly OperableCollection<Row, FillTuple, string> _rows;
|
||||
readonly DIYGridSortManager<Columns> _sort_manager;
|
||||
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
|
||||
readonly DIYGridRowsManager<FillTuple, Columns, FillsFilterStates> _rows_manager;
|
||||
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
|
||||
List<(ColumnBase col, Columns key, ListSortDirection direction)> _active_comparers = new List<(ColumnBase col, Columns key, ListSortDirection direction)>();
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
int _total_row_count;
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FilterStates>, Columns>(c => c.key);
|
||||
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FillsFilterStates>, Columns>(c => c.key);
|
||||
// security
|
||||
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(),
|
||||
DiscreteValuesFilterVM.Create<FillTuple, SecurityImmutable, SecurityCriteria>(
|
||||
@@ -64,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,
|
||||
@@ -86,29 +90,29 @@ namespace mbts.spark {
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
/// Rows
|
||||
_rows = new OperableCollection<Row, FillTuple, string>(
|
||||
(ft, fk) => new Row(ft, model),
|
||||
ft => ft.fill.FillKey,
|
||||
new ComparerBuilder<FillTuple>(new[] {
|
||||
_get_column_comparer(Columns.FillTime),
|
||||
_get_column_comparer(Columns.FillKey)
|
||||
}).to_comparer());
|
||||
// row sort/filter managers
|
||||
_sort_manager = new DIYGridSortManager<Columns>(col_tups.WrapSelector(t => (t.col, t.key)), Columns.FillKey);
|
||||
_filters_manager = DIYGridFiltersManager.create(col_tups);
|
||||
var rows_manager = new DIYGridRowsManager<FillTuple, Columns, FilterStates>(_filters_manager, _sort_manager, _get_column_comparer, f => _rows.filter = f, c => _rows.comparer = c, _rows.reset, FillsWindow.window_type_name);
|
||||
// rows
|
||||
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
//
|
||||
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
|
||||
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
|
||||
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
|
||||
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
|
||||
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
|
||||
o.OnNext(last_unfiltered_fills.as_snapshot());
|
||||
_unfiltered_fills_changed += o.OnNext;
|
||||
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
|
||||
});
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
_rows.update(t);
|
||||
last_unfiltered_fills = t;
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
_unfiltered_fills_changed?.Invoke(t);
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
@@ -116,16 +120,15 @@ namespace mbts.spark {
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
var fs = s.filters ?? new FilterStates {
|
||||
var fs = s.filters ?? new FillsFilterStates {
|
||||
fill_time = new TimeFilterTuple(
|
||||
TimeFilterMode.CMEOpen,
|
||||
TimeTextTuple.FromDateTime(DateTime.Today),
|
||||
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
|
||||
};
|
||||
rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
_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) =>
|
||||
@@ -135,14 +138,17 @@ namespace mbts.spark {
|
||||
Width = width
|
||||
}, filter);
|
||||
void add_col_expl<TFilter>(Columns col_key, ColumnBase col, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter) =>
|
||||
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
|
||||
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FillsFilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
|
||||
// discrete-values-filter factories
|
||||
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter<T>(IReadOnlyCollection<T> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
|
||||
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter_rx<T>(IObservable<IEnumerable<T>> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
|
||||
}
|
||||
|
||||
// this event is used to forward updates from the main data to the filtered data that drives the rows, after resetting the total row count.
|
||||
event Action<TableUpdate<FillTuple, string>> _unfiltered_fills_changed;
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public ObservableCollection<Row> rows => _rows;
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public int total_row_count => _total_row_count;
|
||||
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
@@ -154,26 +160,26 @@ namespace mbts.spark {
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public FillsFilterStates get_filters() =>
|
||||
_rows_manager.get_filters();
|
||||
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
|
||||
_sort_manager.create();
|
||||
_rows_manager.get_sorting_criterion();
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
_rows_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples;
|
||||
}
|
||||
|
||||
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> 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;
|
||||
@@ -212,8 +219,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
|
||||
public readonly FillTuple fill_tuple;
|
||||
|
||||
readonly OrdersModel _model;
|
||||
readonly FillTuple _fill_tuple;
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
Brush _badge_fill;
|
||||
@@ -221,7 +229,7 @@ namespace mbts.spark {
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) :
|
||||
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
|
||||
_fill_tuple = fill_tuple;
|
||||
this.fill_tuple = fill_tuple;
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
@@ -231,9 +239,9 @@ namespace mbts.spark {
|
||||
public DateTime fill_time => _fill.Timestamp.to_local_time();
|
||||
public TimeInForce time_in_force => _order.time_in_force();
|
||||
public OrderType order_type => _order.order_type();
|
||||
public string display_name => OrderUtils.display_name(_order, _fill_tuple.algo);
|
||||
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
|
||||
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_fill_tuple.algo_meta, order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
|
||||
public string account { get; private set; }
|
||||
public string user => _order.user();
|
||||
public Venue exchange => security.Exchange;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
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;
|
||||
@@ -13,21 +15,20 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
class FillsWindow : SparkWindow<FillsSettings>, ISharedWindow {
|
||||
public const string window_type_name = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
FillsVM _vm;
|
||||
FillsView _view;
|
||||
StartupData _state;
|
||||
FillsSettings _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
@@ -35,15 +36,15 @@ namespace mbts.spark {
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
event Action<FillsSettings> _state_changed;
|
||||
|
||||
public override string window_type => window_type_name;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
@@ -53,33 +54,32 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_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;
|
||||
}))
|
||||
};
|
||||
_subscriptions = Disposable.Create(() =>
|
||||
_state_changed?.Invoke(
|
||||
_state = new StartupData(_state.orders,
|
||||
_state = new FillsSettings(_state.orders,
|
||||
_vm.get_sorting_criterion().ToList(),
|
||||
_vm.get_filters(),
|
||||
_vm.grid_settings_manager.create()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -16,7 +17,7 @@ using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
|
||||
namespace bts.shell {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
|
||||
class FillsWindowFactory : ShellWindowFactory<FillsSettings> {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
@@ -52,13 +53,13 @@ namespace bts.shell {
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
public override Task<IShellWindow> load(FillsSettings state) =>
|
||||
Task.FromResult(_create(state));
|
||||
|
||||
public async void show(StartupData state) =>
|
||||
public async void show(FillsSettings state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
|
||||
IShellWindow _create(StartupData state) {
|
||||
IShellWindow _create(FillsSettings state) {
|
||||
GridSettingsHelper.normalize_sorting_criterion(state.sorting_criterion, _sort_member_to_id);
|
||||
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
|
||||
state.grid,
|
||||
@@ -66,162 +67,5 @@ namespace bts.shell {
|
||||
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
|
||||
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
public const int Version = 1;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public StartupData() { }
|
||||
public StartupData(uint[] orders) : this(orders, null, null) { }
|
||||
public StartupData(FilterStates filters) : this(null, null, filters) { }
|
||||
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static StartupData Default() =>
|
||||
new StartupData(null, null, new FilterStates(), null);
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
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);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
[DIYGridColumnID(nameof(Columns.FillTime))]
|
||||
public TimeFilterTuple fill_time { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[DIYGridColumnID(nameof(Columns.TimeInForce))]
|
||||
public TimeInForce[] time_in_force { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
[DIYGridColumnID(nameof(Columns.User))]
|
||||
public string[] user { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
[DIYGridColumnID(nameof(Columns.Exchange))]
|
||||
public Venue[] exchange { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[DIYGridColumnID(nameof(Columns.Security))]
|
||||
public SecurityCriteria security { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[DIYGridColumnID(nameof(Columns.Side))]
|
||||
public Side[] side { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
[DIYGridColumnID(nameof(Columns.Price))]
|
||||
public DoubleMinMaxState price { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderKey))]
|
||||
public string order_key { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
[DIYGridColumnID(nameof(Columns.SecurityKey))]
|
||||
public string security_key { get; set; }
|
||||
|
||||
[ProtoMember(14)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKind))]
|
||||
public Fill.Types.FillKind[] fill_kind { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using Columns = mbts.ui.spark.FillsView.Columns;
|
||||
|
||||
namespace bts.shell.fills {
|
||||
[ProtoContract]
|
||||
class FillsFilterStates {
|
||||
[ProtoMember(1)]
|
||||
[DIYGridColumnID(nameof(Columns.FillTime))]
|
||||
public TimeFilterTuple fill_time { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
[DIYGridColumnID(nameof(Columns.TimeInForce))]
|
||||
public TimeInForce[] time_in_force { get; set; }
|
||||
|
||||
[ProtoMember(3)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderType))]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
|
||||
|
||||
[ProtoMember(4)]
|
||||
[DIYGridColumnID(nameof(Columns.Account))]
|
||||
public string[] account { get; set; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
[DIYGridColumnID(nameof(Columns.User))]
|
||||
public string[] user { get; set; }
|
||||
|
||||
[ProtoMember(6)]
|
||||
[DIYGridColumnID(nameof(Columns.Exchange))]
|
||||
public Venue[] exchange { get; set; }
|
||||
|
||||
[ProtoMember(7)]
|
||||
[DIYGridColumnID(nameof(Columns.Security))]
|
||||
public SecurityCriteria security { get; set; }
|
||||
|
||||
[ProtoMember(8)]
|
||||
[DIYGridColumnID(nameof(Columns.Side))]
|
||||
public Side[] side { get; set; }
|
||||
|
||||
[ProtoMember(9)]
|
||||
[DIYGridColumnID(nameof(Columns.Price))]
|
||||
public DoubleMinMaxState price { get; set; }
|
||||
|
||||
[ProtoMember(10)]
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
|
||||
[ProtoMember(12)]
|
||||
[DIYGridColumnID(nameof(Columns.OrderKey))]
|
||||
public string order_key { get; set; }
|
||||
|
||||
[ProtoMember(13)]
|
||||
[DIYGridColumnID(nameof(Columns.SecurityKey))]
|
||||
public string security_key { get; set; }
|
||||
|
||||
[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 {
|
||||
public static Func<FillTuple, bool> build_predicate(this FillsFilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<FillTuple>();
|
||||
// fill time
|
||||
pb.add_time_filter(fs.fill_time, ft => ft.fill_time());
|
||||
// security
|
||||
pb.add_security_criteria(fs.security, ft => ft.security);
|
||||
// regex
|
||||
pb.add_regex(fs.fill_key, ft => ft.fill.FillKey);
|
||||
pb.add_regex(fs.order_key, ft => ft.order_key().ToString());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.user, ft => ft.user());
|
||||
pb.add_discrete_values(fs.account, ft => ft.account);
|
||||
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
|
||||
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
|
||||
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);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using Columns = mbts.ui.spark.FillsView.Columns;
|
||||
|
||||
namespace bts.shell.fills {
|
||||
[ProtoContract]
|
||||
class FillsSettings {
|
||||
public const int Version = 1;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint[] orders;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criteria;
|
||||
[ProtoMember(3)]
|
||||
public FillsFilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
|
||||
public FillsSettings() { }
|
||||
public FillsSettings(uint[] orders) : this(orders, null, null) { }
|
||||
public FillsSettings(FillsFilterStates filters) : this(null, null, filters) { }
|
||||
public FillsSettings(uint[] orders, List<SortingCriteria> sorting_criteria, FillsFilterStates filters, GridSettings grid = default) {
|
||||
this.orders = orders;
|
||||
this._sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
|
||||
public static FillsSettings Default() =>
|
||||
new FillsSettings(null, null, new FillsFilterStates(), null);
|
||||
|
||||
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);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!
|
||||
@@ -119,7 +120,8 @@ namespace mbts.spark {
|
||||
buy = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.MarketData.bid, false) },
|
||||
sell = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.MarketData.ask, false) },
|
||||
passive = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.passive_volume_bar, false) },
|
||||
total = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.total_volume_bar), show_numbers = true }
|
||||
total = new HistogramBarAnimation { bars = new ColorShowProto((uint)BTSColors.LadderSpecial.total_volume_bar), show_numbers = true },
|
||||
show_ltq = false
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -134,6 +136,8 @@ namespace mbts.spark {
|
||||
public HistogramBarAnimation passive { get; set; }
|
||||
[ProtoMember(5)]
|
||||
public HistogramBarAnimation total{ get; set; }
|
||||
[ProtoMember(6)]
|
||||
public bool show_ltq { get; set; }
|
||||
|
||||
public bool Equals(VolumeColumnSettings other) =>
|
||||
other != null &&
|
||||
@@ -141,7 +145,8 @@ namespace mbts.spark {
|
||||
buy.safe_equals(other.buy) &&
|
||||
sell.safe_equals(other.sell) &&
|
||||
passive.safe_equals(other.passive) &&
|
||||
total.safe_equals(other.total);
|
||||
total.safe_equals(other.total) &&
|
||||
show_ltq.Equals(other.show_ltq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as VolumeColumnSettings);
|
||||
|
||||
@@ -151,7 +156,8 @@ namespace mbts.spark {
|
||||
buy.GetHashCode(),
|
||||
sell.GetHashCode(),
|
||||
passive.GetHashCode(),
|
||||
total.GetHashCode());
|
||||
total.GetHashCode(),
|
||||
show_ltq.GetHashCode());
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -215,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.
|
||||
@@ -223,10 +246,12 @@ 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,
|
||||
// the default values are applied correctly; thx to John for this tip
|
||||
/// TODO: all default values should be consts, because they must be referenced in two places (in the default constructor, and the <see cref="DefaultValueAttribute"/>)
|
||||
public LadderSettings() {
|
||||
day_visible = true;
|
||||
high_visible = true;
|
||||
@@ -238,6 +263,7 @@ namespace mbts.spark {
|
||||
tif_visible = true;
|
||||
order_type_visible = true;
|
||||
account_position_visible = true;
|
||||
day_position_visible = true;
|
||||
size_presets_visible = true;
|
||||
cancel_buys_visible = true;
|
||||
cancel_sells_visible = true;
|
||||
@@ -248,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 {
|
||||
@@ -279,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 },
|
||||
@@ -301,6 +328,7 @@ namespace mbts.spark {
|
||||
tif_visible = true,
|
||||
order_type_visible = true,
|
||||
account_position_visible = true,
|
||||
day_position_visible = true,
|
||||
size_presets_visible = true,
|
||||
cancel_buys_visible = true,
|
||||
cancel_sells_visible = true,
|
||||
@@ -311,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;
|
||||
@@ -319,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; }
|
||||
@@ -346,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)]
|
||||
@@ -356,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; }
|
||||
@@ -397,6 +440,8 @@ namespace mbts.spark {
|
||||
public bool order_type_visible { get; set; }
|
||||
[ProtoMember(36), DefaultValue(true)]
|
||||
public bool account_position_visible { get; set; }
|
||||
[ProtoMember(50), DefaultValue(true)]
|
||||
public bool day_position_visible { get; set; }
|
||||
[ProtoMember(37), DefaultValue(true)]
|
||||
public bool size_presets_visible { get; set; }
|
||||
[ProtoMember(38), DefaultValue(true)]
|
||||
@@ -436,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 &&
|
||||
@@ -449,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) &&
|
||||
@@ -473,6 +561,7 @@ namespace mbts.spark {
|
||||
tif_visible == other.tif_visible &&
|
||||
order_type_visible == other.order_type_visible &&
|
||||
account_position_visible == other.account_position_visible &&
|
||||
day_position_visible == other.day_position_visible &&
|
||||
size_presets_visible == other.size_presets_visible &&
|
||||
cancel_buys_visible == other.cancel_buys_visible &&
|
||||
cancel_sells_visible == other.cancel_sells_visible &&
|
||||
@@ -484,10 +573,13 @@ 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);
|
||||
|
||||
// TODO: remove mutable props from hash logic (or conversely, make them immutable).
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
row_height.GetHashCode(),
|
||||
@@ -500,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(),
|
||||
@@ -535,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;
|
||||
|
||||
@@ -4,13 +4,17 @@ using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
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 +56,217 @@ namespace bts.shell {
|
||||
public async override Task<IShellWindow> load(LadderSaveState state) {
|
||||
await Wait.until_true(() => _securities.ContainsKey(state.security_key), _ladder_conf.security_timeout);
|
||||
if (_securities.TryGetValue(state.security_key, out SecurityMD security)) {
|
||||
return create_ladder_window(security, state.settings);
|
||||
return create_ladder_window(security, state.settings, state.settings_id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static LadderSaveState _get_state(LadderVM vm) =>
|
||||
new LadderSaveState { security_key = vm.security.Id, settings = vm.get_save_state() };
|
||||
LadderSaveState _get_state(LadderVM vm, uint id) {
|
||||
LadderSettings s = vm.get_save_state();
|
||||
if (id != 0) {
|
||||
if (!_settings_hub.current.ladder_profiles.TryGetFirst(lp => lp.id == id, out var result)) {
|
||||
// profile was deleted
|
||||
id = 0;
|
||||
} else {
|
||||
var curr_profile = _fit_to_sec(result.value, vm.security.ContractCase);
|
||||
if (!s.Equals(curr_profile)) {
|
||||
// ladder's settings diverged from the profile (this happens when the user changes settings outside of the settings dialog, such as column widths, left panel width, etc.)
|
||||
id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LadderSaveState { security_key = vm.security.Id, settings = s, settings_id = id };
|
||||
}
|
||||
|
||||
int _ladder_count = 0;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
|
||||
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
// 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()));
|
||||
state_settings?.sanitize();
|
||||
/// When settings are adjusted outside of the settings dialog, the vm should raise the <see cref="LadderVM.settings_invalidated"/> event.
|
||||
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
|
||||
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
|
||||
bool initialized = false, needs_resync = false;
|
||||
LadderSettings last_value = fit_to_sec(state_settings);
|
||||
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
|
||||
LadderSettings fit_to_sec(LadderSettings s) =>
|
||||
_fit_to_sec(s, md.security.ContractCase);
|
||||
|
||||
void on_settings_invalidated() =>
|
||||
needs_resync = true;
|
||||
|
||||
void ensure_synced() {
|
||||
if (!needs_resync)
|
||||
return;
|
||||
var curr = vm.get_save_state();
|
||||
if (settings_id == 0) {
|
||||
last_value = curr;
|
||||
} else if (!curr.Equals(last_value)) {
|
||||
settings_id = 0;
|
||||
last_value = curr;
|
||||
}
|
||||
needs_resync = false;
|
||||
}
|
||||
|
||||
void on_user_settings(UserSettings us) {
|
||||
if (initialized) {
|
||||
ensure_synced();
|
||||
if (settings_id == 0) {
|
||||
// not on a profile, don't respond to default settings changes
|
||||
return;
|
||||
} else if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
if (!last_value.Equals(vm.get_save_state())) {
|
||||
settings_id = 0;
|
||||
} else {
|
||||
settings_subject.OnNext(last_value = fit_to_sec(profile.value));
|
||||
}
|
||||
} else {
|
||||
// profile was deleted; keep the current settings and switch to the 'Local' profile.
|
||||
settings_id = 0;
|
||||
}
|
||||
} else if (_settings_hub.initialized) {
|
||||
initialized = true;
|
||||
if (settings_id != 0) {
|
||||
if (us.ladder_profiles.TryGetFirst(p => p.id == settings_id, out var profile)) {
|
||||
last_value = fit_to_sec(profile.value);
|
||||
} else {
|
||||
settings_id = 0;
|
||||
}
|
||||
}
|
||||
if (settings_id == 0 && last_value == null) {
|
||||
foreach (var lp in _settings_hub.current.ladder_profiles) {
|
||||
// TODO: Use a kind of 'match priority' to resolve conflicts when the security is included in more than 1 profile's security criteria.
|
||||
// In the meantime, simply use the first match.
|
||||
if (lp.security_criteria.includes(md.security)) {
|
||||
last_value = fit_to_sec(lp.value);
|
||||
settings_id = lp.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (last_value == null)
|
||||
last_value = fit_to_sec(_settings_hub.current.ladder);
|
||||
}
|
||||
settings_subject.OnNext(last_value);
|
||||
}
|
||||
}
|
||||
|
||||
void open_settings_dialog() {
|
||||
// dialog vm
|
||||
var dialog_vm = new ProfiledShellSettingsDialogVM<LadderSettings, LadderSettingsVM, LadderSettingsProfile>(
|
||||
profile_service,
|
||||
us => us.ladder ?? LadderSettings.Default,
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
|
||||
svm.load(s);
|
||||
return svm;
|
||||
},
|
||||
fit_to_sec,
|
||||
vm.get_save_state(),
|
||||
UserSettings.FileExtensions.LADDER,
|
||||
settings_id);
|
||||
dialog_vm.applied += apply;
|
||||
// dialog
|
||||
var dialog = new ShellSettingsDialog { DataContext = dialog_vm };
|
||||
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,26 +283,19 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: whenever we re-implement the linked ladder, need to add some field to the LadderSaveState to indicate the owning window
|
||||
//void show_linked(IObservable<SecurityMD> keystream) {
|
||||
// var view = new LadderView();
|
||||
// IDisposable sub = null;
|
||||
// var window =
|
||||
// ShellWindow.create(module, "loading", view,
|
||||
// Disposable.Create(() => {
|
||||
// (view.DataContext as LadderVM)?.Dispose();
|
||||
// sub?.Dispose();
|
||||
// }), s => {
|
||||
// var vm = view.DataContext as LadderVM;
|
||||
// if (vm != null) save(_get_state(vm), s);
|
||||
// }, Icons.Ladder);
|
||||
// sub =
|
||||
// keystream
|
||||
// .Select(smd => new LadderVM(_bus, new LadderModel(smd, _orders_model),
|
||||
// Observable.Return(_orders_model.factory.default_ladder_settings(smd.security)))
|
||||
// ).Aggregate((old, vm) => { old.Dispose(); return vm; })
|
||||
// .Subscribe(vm => { view.DataContext = vm; window.Title = vm.security.ToString(); });
|
||||
//}
|
||||
LadderSettings _fit_to_sec(LadderSettings s, Security.ContractOneofCase sec_type) {
|
||||
if (s == null) return null;
|
||||
var algo_key = AlgoID.to_algo_key(s.default_algo, _orders_model.dynamic_helper.dynamic_type_index);
|
||||
if (algo_key.mode == AlgoKey.Mode.Factory) {
|
||||
var fitted_algo = OrderDataHelper.fit_to_type(algo_key.factory.structure.build_algo(_orders_model.dynamic_helper), sec_type, true);
|
||||
if (fitted_algo.HasValue) {
|
||||
// be sure to clone ('s' may be shared)
|
||||
s = SparkUtils.clone_proto(s);
|
||||
s.default_algo = AlgoID.from_custom_def(fitted_algo.Value);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
readonly struct LadderConfig {
|
||||
public readonly uint max_open_ladders;
|
||||
@@ -167,5 +318,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;
|
||||
@@ -292,50 +294,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"),
|
||||
@@ -52,31 +70,25 @@ namespace mbts.spark {
|
||||
_tif_visibility = new OrderEntryPanelItemVisibilityVM("Time In Force"),
|
||||
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
|
||||
_account_position_visibility = new OrderEntryPanelItemVisibilityVM("Account Position"),
|
||||
_day_position_visibility = new OrderEntryPanelItemVisibilityVM("Day Position"),
|
||||
_size_preset_visibility = new OrderEntryPanelItemVisibilityVM("Size Presets"),
|
||||
_auto_clear_visibility = new OrderEntryPanelItemVisibilityVM("Auto Clear"),
|
||||
_clear_visibility = new OrderEntryPanelItemVisibilityVM("Clear"),
|
||||
_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;
|
||||
@@ -85,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, false);
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
private void _badge_style_Changed(LadderSettings.BadgeStyle oldval, LadderSettings.BadgeStyle newval) {
|
||||
if (newval == LadderSettings.BadgeStyle.Alternate && _orders.font_size == 10) {
|
||||
_orders.font_size = 7;
|
||||
@@ -109,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;
|
||||
@@ -164,6 +237,7 @@ namespace mbts.spark {
|
||||
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 _clear_visibility;
|
||||
yield return _auto_clear_visibility;
|
||||
@@ -173,87 +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;
|
||||
_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();
|
||||
@@ -261,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, _model.dynamic_helper.dynamic_type_index) :
|
||||
(_desired_algo ?? LadderSettings.Default.default_algo);
|
||||
// - components
|
||||
clone.auto_clear = auto_clear;
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.day_visible = _day_visibility.show;
|
||||
clone.high_visible = _high_visibility.show;
|
||||
clone.low_visible = _low_visibility.show;
|
||||
@@ -278,31 +413,30 @@ namespace mbts.spark {
|
||||
clone.tif_visible = _tif_visibility.show;
|
||||
clone.order_type_visible = _order_type_visibility.show;
|
||||
clone.account_position_visible = _account_position_visibility.show;
|
||||
clone.day_position_visible = _day_position_visibility.show;
|
||||
clone.size_presets_visible = _size_preset_visibility.show;
|
||||
clone.auto_clear_visible = _auto_clear_visibility.show;
|
||||
clone.clear_visible = _clear_visibility.show;
|
||||
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) {
|
||||
@@ -310,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, _model.dynamic_helper.dynamic_type_index), out var vm)) {
|
||||
_desired_algo = null;
|
||||
_default_algo.bypass_setter(vm);
|
||||
}
|
||||
}
|
||||
|
||||
public class LadderColumnSettingsVM : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly Property<double> _font_size;
|
||||
@@ -319,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);
|
||||
@@ -334,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(
|
||||
@@ -366,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() {
|
||||
@@ -391,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 {
|
||||
@@ -449,6 +607,7 @@ namespace mbts.spark {
|
||||
passive = new VolumeAtPriceAnimationVM("Passive", new VolumeAtPriceSampleData(this, 72, 127, 198));
|
||||
total = new VolumeAtPriceAnimationVM("Total", new VolumeAtPriceSampleData(this, 81, 100, 144));
|
||||
_any_visible = backing(nameof(any_visible), () => volume_components.Any(vc => vc.show_numbers || vc.bars.enabled));
|
||||
_show_last_trade = backing(nameof(show_last_trade), false);
|
||||
foreach (var vc in volume_components) {
|
||||
vc.show_numbers_changed += _any_visible.refresh;
|
||||
vc.bars.enabled_changed += _any_visible.refresh;
|
||||
@@ -458,11 +617,14 @@ 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; }
|
||||
public VolumeAtPriceAnimationVM total { get; }
|
||||
|
||||
readonly Property<bool> _show_last_trade;
|
||||
public bool show_last_trade { get => _show_last_trade.Value; set => _show_last_trade.Value = value; }
|
||||
public bool any_visible => volume_components.Any(vc => vc.show_numbers || vc.bars.enabled);
|
||||
|
||||
public IEnumerable<VolumeAtPriceAnimationVM> volume_components {
|
||||
@@ -481,6 +643,7 @@ namespace mbts.spark {
|
||||
sell.assign_from(settings.sell);
|
||||
passive.assign_from(settings.passive);
|
||||
total.assign_from(settings.total);
|
||||
show_last_trade = settings.show_ltq;
|
||||
}
|
||||
|
||||
public VolumeColumnSettings get_settings() =>
|
||||
@@ -490,6 +653,7 @@ namespace mbts.spark {
|
||||
sell = sell.get_settings(),
|
||||
passive = passive.get_settings(),
|
||||
total = total.get_settings(),
|
||||
show_ltq = show_last_trade
|
||||
};
|
||||
}
|
||||
|
||||
@@ -556,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; }
|
||||
@@ -586,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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -601,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;
|
||||
}
|
||||
@@ -614,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;
|
||||
@@ -25,40 +23,39 @@ namespace mbts.spark {
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly LadderModel _model;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _position, _left_panel_width, _zoom_factor;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _left_panel_width, _zoom_factor;
|
||||
//readonly Property<double> _window_width, _window_height;
|
||||
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
|
||||
readonly Property<ulong> _volume;
|
||||
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
readonly Property<bool> _auto_clear;
|
||||
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
double _position, _day_position;
|
||||
bool _show_account_position, _show_day_position;
|
||||
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);
|
||||
@@ -77,73 +74,76 @@ namespace mbts.spark {
|
||||
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
|
||||
_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
|
||||
var account_positions = new KeyDrivenObservableSeries<string, double>(a => _model.order_gateway.security_total_position(security.Id, a));
|
||||
_position = backing(nameof(position), account_positions.OnDispatcher());
|
||||
_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);
|
||||
// brush props
|
||||
_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background.rgb_brush());
|
||||
_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground.rgb_brush());
|
||||
_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background.rgb_brush());
|
||||
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
|
||||
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
|
||||
// bool visibility props
|
||||
_show_day = bool_prop(nameof(show_day), s => s.day_visible);
|
||||
_show_high = bool_prop(nameof(show_high), s => s.high_visible);
|
||||
_show_low = bool_prop(nameof(show_low), s => s.low_visible);
|
||||
_show_open = bool_prop(nameof(show_open), s => s.open_visible);
|
||||
_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible);
|
||||
_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible);
|
||||
_show_account = bool_prop(nameof(show_account), s => s.account_visible);
|
||||
_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible);
|
||||
_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible);
|
||||
_show_account_position = bool_prop(nameof(show_account_position), s => s.account_position_visible);
|
||||
_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible);
|
||||
_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible);
|
||||
_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible);
|
||||
_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible);
|
||||
_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible);
|
||||
_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible);
|
||||
_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible);
|
||||
|
||||
Property<bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_bool);
|
||||
// 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,
|
||||
(_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background)).set,
|
||||
(_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground)).set,
|
||||
// bool visibility props
|
||||
(_show_day = bool_prop(nameof(show_day), s => s.day_visible)).set,
|
||||
(_show_high = bool_prop(nameof(show_high), s => s.high_visible)).set,
|
||||
(_show_low = bool_prop(nameof(show_low), s => s.low_visible)).set,
|
||||
(_show_open = bool_prop(nameof(show_open), s => s.open_visible)).set,
|
||||
(_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible)).set,
|
||||
(_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible)).set,
|
||||
(_show_account = bool_prop(nameof(show_account), s => s.account_visible)).set,
|
||||
(_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible)).set,
|
||||
(_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible)).set,
|
||||
(_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible)).set,
|
||||
(_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible)).set,
|
||||
(_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible)).set,
|
||||
(_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible)).set,
|
||||
(_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible)).set,
|
||||
(_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible)).set,
|
||||
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
|
||||
s => {
|
||||
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) =>
|
||||
backing(name, init, get_bool, EqualityComparer.Create(get_bool));
|
||||
|
||||
PropertyAutoWrapper<LadderSettings, Brush> brush_prop(string name, Func<LadderSettings, uint> get_color_uint) =>
|
||||
backing(name, init, s => get_color_uint(s).rgb_brush(), EqualityComparer.Create(get_color_uint));
|
||||
//
|
||||
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
|
||||
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(position)));
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(_show_day_position ? day_position : position)));
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
|
||||
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_command,
|
||||
non_shared_wsm,
|
||||
settings.OnDispatcher().Subscribe(settings_observer),
|
||||
get_order_properties_editor_hooks(),
|
||||
account_positions,
|
||||
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 => {
|
||||
@@ -168,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;
|
||||
}
|
||||
|
||||
@@ -215,18 +215,33 @@ namespace mbts.spark {
|
||||
WorkLoop.Start($"Ladder '{security}'", _tick),
|
||||
Disposable.Create(() => _on_disposed_impl?.Invoke(true))
|
||||
);
|
||||
IDisposable get_order_properties_editor_hooks() =>
|
||||
order_properties_vm == null ?
|
||||
Disposable.empty :
|
||||
Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
|
||||
order_properties_vm,
|
||||
order_properties_vm.observe_account(true).Select(a => a?.name ?? string.Empty).Subscribe(account_positions));
|
||||
IDisposable get_order_properties_editor_hooks() {
|
||||
if (order_properties_vm == null)
|
||||
return Disposable.empty;
|
||||
var position_src = order_properties_vm.observe_account(true)
|
||||
.Select(a => a?.name ?? string.Empty)
|
||||
.SelectManyInSeries(a => _model.order_gateway.on_security_positions(security.Id)
|
||||
.Select(table => table.state.Values.FirstOrDefault(pos => pos.Item2.Account == a, tup => tup.Item2, (bts.api.position.PositionUnitImmutable?)null)));
|
||||
return Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
|
||||
order_properties_vm,
|
||||
position_src.OnDispatcher().Subscribe(pos => {
|
||||
double total, day;
|
||||
if (pos.HasValue) {
|
||||
total = pos.Value.total_pos();
|
||||
day = pos.Value.day_pos();
|
||||
} else {
|
||||
total = day = double.NaN;
|
||||
}
|
||||
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));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -261,15 +276,17 @@ 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;
|
||||
public double open => _open.Value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double position => _position.Value;
|
||||
public double position => _position;
|
||||
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; }
|
||||
@@ -277,30 +294,41 @@ namespace mbts.spark {
|
||||
public ICommand send_order_command { get; }
|
||||
public ICommand show_book_command { get; }
|
||||
public ICommand show_ticket_command { get; }
|
||||
public Brush bid_background => _bid_background.Value;
|
||||
public Brush bid_foreground => _bid_foreground.Value;
|
||||
public Brush ask_background => _ask_background.Value;
|
||||
public Brush ask_foreground => _ask_foreground.Value;
|
||||
public DialogCommand settings_command { get; }
|
||||
public bool show_day { get => _show_day.Value; set => _show_day.set(value); }
|
||||
public bool show_high { get => _show_high.Value; set => _show_high.set(value); }
|
||||
public bool show_low { get => _show_low.Value; set => _show_low.set(value); }
|
||||
public bool show_open { get => _show_open.Value; set => _show_open.set(value); }
|
||||
public bool show_settle { get => _show_settle.Value; set => _show_settle.set(value); }
|
||||
public bool show_volume { get => _show_volume.Value; set => _show_volume.set(value); }
|
||||
public bool show_account { get => _show_account.Value; set => _show_account.set(value); }
|
||||
public bool show_tif { get => _show_tif.Value; set => _show_tif.set(value); }
|
||||
public bool show_order_type { get => _show_order_type.Value; set => _show_order_type.set(value); }
|
||||
public bool show_account_position { get => _show_account_position.Value; set => _show_account_position.set(value); }
|
||||
public bool show_size_preset { get => _show_size_preset.Value; set => _show_size_preset.set(value); }
|
||||
public bool show_auto_clear { get => _show_auto_clear.Value; set => _show_auto_clear.set(value); }
|
||||
public bool show_clear { get => _show_clear.Value; set => _show_clear.set(value); }
|
||||
public bool show_cancel_buys { get => _show_cancel_buys.Value; set => _show_cancel_buys.set(value); }
|
||||
public bool show_cancel_sells { get => _show_cancel_sells.Value; set => _show_cancel_sells.set(value); }
|
||||
public bool show_cancel_all { get => _show_cancel_all.Value; set => _show_cancel_all.set(value); }
|
||||
public bool show_zoom { get => _show_zoom.Value; set => _show_zoom.set(value); }
|
||||
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 bool show_day => _show_day.view;
|
||||
public bool show_high => _show_high.view;
|
||||
public bool show_low => _show_low.view;
|
||||
public bool show_open => _show_open.view;
|
||||
public bool show_settle => _show_settle.view;
|
||||
public bool show_volume => _show_volume.view;
|
||||
public bool show_account => _show_account.view;
|
||||
public bool show_tif => _show_tif.view;
|
||||
public bool show_order_type => _show_order_type.view;
|
||||
public bool show_account_position => _show_account_position;
|
||||
public bool show_day_position => _show_day_position;
|
||||
public bool show_size_preset => _show_size_preset.view;
|
||||
public bool show_auto_clear => _show_auto_clear.view;
|
||||
public bool show_clear => _show_clear.view;
|
||||
public bool show_cancel_buys => _show_cancel_buys.view;
|
||||
public bool show_cancel_sells => _show_cancel_sells.view;
|
||||
public bool show_cancel_all => _show_cancel_all.view;
|
||||
public bool show_zoom => _show_zoom.view;
|
||||
public double left_panel_width { get => _left_panel_width.Value; set => _left_panel_width.set(value); }
|
||||
public double zoom_factor { get => _zoom_factor.Value; set => _zoom_factor.set(value); }
|
||||
public string position_display_text {
|
||||
get {
|
||||
if (show_day_position) {
|
||||
string day_pos_text = SparkUtils.fmt_position(day_position);
|
||||
return show_account_position ? $"{day_pos_text} ({SparkUtils.fmt_position(position)})" : day_pos_text;
|
||||
} else if (show_account_position) {
|
||||
return SparkUtils.fmt_position(position);
|
||||
} else return string.Empty;
|
||||
}
|
||||
}
|
||||
//public double window_height { get => _window_height.Value; set => _window_height.set(value); }
|
||||
//public double window_width { get => _window_width.Value; set => _window_width.set(value); }
|
||||
public uint orders_column_width { get => _orders_column_width.Value; set => _orders_column_width.set(value); }
|
||||
@@ -310,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();
|
||||
@@ -330,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, _model.order_gateway.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void key_down(KeyEventArgs e) {
|
||||
@@ -348,36 +394,45 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public async 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;
|
||||
else if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
var order = order_properties_vm.create(args.side.Value);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
order.CustomDef.Stop.StopPrice = args.price;
|
||||
} else if (order.CustomDef.StopLimit != null) {
|
||||
// swap the prices here so that the level clicked
|
||||
// in the ladder is the StopPrice (trigger) and the Price
|
||||
// is what was specificed in the extra parameter box
|
||||
double temp_price = order.CustomDef.StopLimit.StopPrice;
|
||||
order.CustomDef.StopLimit.StopPrice = args.price;
|
||||
args.price = temp_price;
|
||||
}
|
||||
}
|
||||
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
|
||||
await _model.order_gateway.send_order(order, args.price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
send_order_async(args.side.Value, size, args.price).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task send_order_async(Side side, uint size, double price) {
|
||||
var order = order_properties_vm.create(side);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
order.CustomDef.Stop.StopPrice = price;
|
||||
} else if (order.CustomDef.StopLimit != null) {
|
||||
// swap the prices here so that the level clicked
|
||||
// in the ladder is the StopPrice (trigger) and the Price
|
||||
// is what was specificed in the extra parameter box
|
||||
double temp_price = order.CustomDef.StopLimit.StopPrice;
|
||||
order.CustomDef.StopLimit.StopPrice = price;
|
||||
price = temp_price;
|
||||
}
|
||||
}
|
||||
await _model.order_gateway.send_order(order, price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
}
|
||||
|
||||
void _update_header() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.md;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using bts.config;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.spark.algo;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -22,10 +21,10 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using mbts.config;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
@@ -67,6 +66,12 @@ namespace mbts.spark {
|
||||
MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
bool _tooltips_are_hooked_up = false;
|
||||
ToolTip tooltip_view = new ToolTip();
|
||||
IDisposable _parameterized_order_popup;
|
||||
|
||||
public LadderGridHost() {
|
||||
AllowDrop = true;
|
||||
@@ -320,30 +325,128 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
protected override async void OnMouseDown(MouseButtonEventArgs e) {
|
||||
if (_settings == null || e.Handled)
|
||||
return;
|
||||
Keyboard.ClearFocus();
|
||||
Focus();
|
||||
var point = e.GetPosition(this);
|
||||
var column = _column_for_point(point);
|
||||
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle) {
|
||||
show_mbo_popup(point).ignore();
|
||||
}
|
||||
else if (_settings != null) {
|
||||
var price = price_for_point(point);
|
||||
Side? bs = null;
|
||||
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
|
||||
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command != null && Command.CanExecute(pbs)) {
|
||||
if (bs.HasValue) {
|
||||
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
|
||||
await Command.ExecuteAsync(pbs);
|
||||
var price = price_for_point(point);
|
||||
Side? bs = null;
|
||||
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
|
||||
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
if (bs.HasValue) {
|
||||
_send_parameterized_order(point, bs.Value);
|
||||
return;
|
||||
} else if (column.column_type == ColumnType.Price) {
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ||
|
||||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
|
||||
show_mbo_popup(point).ignore();
|
||||
} else {
|
||||
_scroll_to_center();
|
||||
}
|
||||
else {
|
||||
Command.Execute(pbs);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
base.OnMouseDown(e);
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command is ICommand cmd && cmd.CanExecute(pbs)) {
|
||||
if (bs.HasValue) {
|
||||
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
|
||||
await cmd.ExecuteAsync(pbs);
|
||||
} else {
|
||||
cmd.Execute(pbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _send_parameterized_order(Point point, Side side) {
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
if (side == Side.Undefined)
|
||||
throw new ArgumentException("Side is undefined.", nameof(side));
|
||||
if (!(_vm.size_calculator.value is uint size) || size == 0 || !(_vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false))
|
||||
return;
|
||||
double price = _price_for_row(_row_for_point(point));
|
||||
var grid = new Grid { DataContext = _vm };
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
var close_cmd = new DelegateCommand(() => Disposable.dispose(ref _parameterized_order_popup));
|
||||
var send_order_cmd = new DelegateCommand(send_order);
|
||||
LadderColumnSettings lcs = side == Side.Buy ? _settings.bid_column : _settings.ask_column;
|
||||
grid.Children.Add(new AlgoParamsView { DataContext = _vm });
|
||||
// dialog skirt
|
||||
var dialog_skirt = new DialogSkirt {
|
||||
RightItems = new[] {
|
||||
new Button {
|
||||
Background = lcs.background.rgb_brush(),
|
||||
Command = send_order_cmd,
|
||||
Content = $"{side} @ {security.spec_or_default().format(price)}",
|
||||
Foreground = lcs.foreground.rgb_brush(),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
Height = 22
|
||||
},
|
||||
new Button {
|
||||
Content = "Cancel",
|
||||
Command = close_cmd,
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
Height = 22
|
||||
}
|
||||
}
|
||||
};
|
||||
Grid.SetRow(dialog_skirt, 1);
|
||||
grid.Children.Add(dialog_skirt);
|
||||
// root
|
||||
var root = new Border {
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderBrush = Brushes.DarkGray,
|
||||
BorderThickness = new Thickness(1),
|
||||
Child = grid
|
||||
};
|
||||
// popup
|
||||
var popup = new Popup {
|
||||
Child = root,
|
||||
PlacementTarget = this,
|
||||
PlacementRectangle = new Rect(point, point),
|
||||
StaysOpen = false
|
||||
};
|
||||
popup.Closed += on_closed;
|
||||
root.Loaded += (s, e) => {
|
||||
if (_parameterized_order_popup == null)
|
||||
_parameterized_order_popup = Disposable.Create(() => popup.IsOpen = false);
|
||||
};
|
||||
popup.PreviewMouseDown += (s, e) => {
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
e.Handled = true;
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
}
|
||||
};
|
||||
popup.IsOpen = true;
|
||||
// local methods
|
||||
void send_order() {
|
||||
// Double check that size hasn't changed (if it has, do NOT send anything).
|
||||
// While this should be impossible, sending an order with an unexpected size is dangerous enough to warrant redunant checks.
|
||||
if (_vm.size_calculator.value == size) {
|
||||
_vm.send_order_async(side, size, price).ignore();
|
||||
} else {
|
||||
Log.Ui.warn($"Parameterized algo popup '{side}' order was deliberately ignored by the ui: the 'quantity' box value changed during the lifetime of the popup (was {size} when the popup was first opened; currently is {_vm.size_calculator.value}).");
|
||||
}
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
}
|
||||
void on_closed(object sender, EventArgs e) =>
|
||||
_vm.order_properties_vm.algo_param_rows.revert_all();
|
||||
}
|
||||
|
||||
private async Task show_mbo_popup(Point point) {
|
||||
@@ -410,6 +513,18 @@ namespace mbts.spark {
|
||||
uint? _ask_size_from_row(int row) =>
|
||||
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
|
||||
|
||||
uint _volume_from_row(int row) {
|
||||
if (_price_list != null) {
|
||||
var price = _price_list[row];
|
||||
var vapls = _volume_container.read_new_value();
|
||||
if (vapls != null) {
|
||||
return vapls.SelectFirstOrDefault(v => MathUtils.epsilon_equals(v.Price, price), v => v.Volume);
|
||||
}
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
int _row_for_price(double price) => LadderRender.index_of(_price_list, price, _price_comparer);
|
||||
#endregion
|
||||
|
||||
@@ -535,6 +650,72 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
#region tooltip handling
|
||||
void _toggle_tooltips() {
|
||||
if (_tooltips_are_hooked_up && !_settings.volume_column.show_ltq) {
|
||||
this.MouseMove -= _mouse_move_handler;
|
||||
_tooltips_are_hooked_up = false;
|
||||
_hide_tool_tip();
|
||||
// somehow even after hiding an unhooking the event handler, after toggling the setting off and
|
||||
// then hovering over the Volume column, the tooltip will still display, so we explicitly set it
|
||||
// to null here.
|
||||
this.ToolTip = null;
|
||||
}
|
||||
|
||||
if (_settings.volume_column.show_ltq && !_tooltips_are_hooked_up) {
|
||||
this.MouseMove += _mouse_move_handler;
|
||||
_tooltips_are_hooked_up = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _mouse_move_handler(object sender, MouseEventArgs e) {
|
||||
var pt = e.GetPosition(this);
|
||||
if (_column_for_point(pt)?.column_type == ColumnType.Volume) {
|
||||
var row = _row_for_point(pt);
|
||||
// mark dirty so that we'll always get a value on our read
|
||||
_volume_container.mark_dirty();
|
||||
var volume = _volume_from_row(row);
|
||||
if (volume > uint.MinValue && row != _old_tooltip_row) {
|
||||
if (tooltip_view.IsOpen) {
|
||||
_old_tooltip_row = row;
|
||||
// we've changed rows, so hide the old tooltip
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
|
||||
_hide_tool_tip();
|
||||
}
|
||||
|
||||
tooltip_view.Content = $"{volume}";
|
||||
tooltip_view.IsOpen = true;
|
||||
this.ToolTip = tooltip_view;
|
||||
} else {
|
||||
// got 0 volume or we're over the same row
|
||||
if (volume == uint.MinValue) {
|
||||
// The latest volume query resulted in a 0 volume
|
||||
// Perhaps we read it too quickly, or perhaps we are on a row where there is no volume
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
void _hide_tool_tip() {
|
||||
// I am leaving this here for posterity
|
||||
// Apparently even when setting IsOpen to false first and then setting Content to null afterward,
|
||||
// the tooltip will *still* display as empty for a brief moment, which looks odd. So, we just hide it now.
|
||||
tooltip_view.IsOpen = false;
|
||||
//tooltip_view.Content = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
void _on_vm_disposed(bool obj) =>
|
||||
_dispose_resources();
|
||||
|
||||
@@ -683,10 +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);
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column);
|
||||
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} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +1083,7 @@ namespace mbts.spark {
|
||||
case ColumnType.PIQ: SetCurrentValue(PIQColumnWidthProperty, col.settings.width); break;
|
||||
case ColumnType.Volume:
|
||||
SetCurrentValue(VolumeColumnWidthProperty, col.settings.width);
|
||||
_toggle_tooltips();
|
||||
_volume_container.mark_dirty();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -194,7 +194,9 @@ namespace mbts.spark {
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction
|
||||
) {
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -213,32 +215,52 @@ 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;
|
||||
}
|
||||
double max = data.Max(v => v.Volume);
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
_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;
|
||||
|
||||
// render the total first; each component (buy/sell/passive) will be rendered on top of the total
|
||||
if (total_brush != null) {
|
||||
// if we have to change the brush to indicate this is the last, save brush, draw, then restore brush
|
||||
var temp = total_brush;
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
total_brush = BTSColors.yellow_brush;
|
||||
}
|
||||
render_bar(dc, 0, y, vap.Volume, total_brush, out double _);
|
||||
total_brush = temp;
|
||||
}
|
||||
List<string> numbers_text = new List<string>();
|
||||
double x_bar = 0;
|
||||
foreach ((var get_volume, Brush bars, bool show_numbers) in components) {
|
||||
uint v = get_volume(vap);
|
||||
render_bar(dc, x_bar, y, v, bars, out double width);
|
||||
double width;
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
render_bar(dc, x_bar, y, v, BTSColors.yellow_brush, out width);
|
||||
} else {
|
||||
render_bar(dc, x_bar, y, v, bars, out width);
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(v.ToString());
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(vap.Volume.ToString());
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
numbers_text.Add(last_size_and_direction);
|
||||
text_brush = BTSCoreColors.white;
|
||||
}
|
||||
if (numbers_text.Count > 0) {
|
||||
string str = string.Join("/", numbers_text);
|
||||
GlyphRun gr = glyph_context.get_glyph_run(str, settings.general.font_size, settings.general.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, y, true);
|
||||
@@ -253,7 +275,9 @@ namespace mbts.spark {
|
||||
}
|
||||
double pct = v / max;
|
||||
width = pct * settings.general.width;
|
||||
dc.DrawRectangle(brush, null, new Rect(x, y, width, row_height - 1));
|
||||
// 4 seems like a good number to move the y pos down by and similarly
|
||||
// to shorten the total y coponent by (-4 for the shift of +4, and another -4 to give it some space);
|
||||
dc.DrawRectangle(brush, null, new Rect(x, y + 4, width, row_height - 2 * 4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@@ -149,11 +149,34 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _host_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
|
||||
void _host_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
// This will reset the cursor appropriately if the middle mouse button
|
||||
// has been pressed while over the bid or ask columns
|
||||
var point = e.GetPosition(_host);
|
||||
_set_current_row_and_column(point);
|
||||
|
||||
_is_mouse_down = false;
|
||||
_is_mouse_dragging = false;
|
||||
InvalidateVisual();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/OrderDetailsPopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoParamsView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateFavoriteButtonsView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToInVisibility" VisibilityForTrue="Collapsed" VisibilityForFalse="Visible" />
|
||||
@@ -31,7 +32,6 @@
|
||||
</converters:BooleanToObjectConverter.ValueForFalse>
|
||||
</converters:BooleanToObjectConverter>
|
||||
<converters:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<converters:DoubleTextConverter x:Key="PositionTextConverter" Format="0" NaNText="-"/>
|
||||
<converters:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<converters:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
@@ -288,8 +288,11 @@
|
||||
/>
|
||||
</Grid.LayoutTransform>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="60" MaxWidth="150"/>
|
||||
<ColumnDefinition
|
||||
Width="{Binding left_panel_width, Mode=TwoWay, Converter={StaticResource GridLengthPixelConverter}}"
|
||||
MinWidth="{x:Static spark:LadderView.MinLeftPanelWidth}"
|
||||
MaxWidth="{x:Static spark:LadderView.MaxLeftPanelWidth}"
|
||||
/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -309,11 +312,14 @@
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
<StackPanel x:Name="_slider_stack_panel" Margin="5,5,5,0">
|
||||
<Slider x:Name="_ui_scaler" Value="{Binding zoom_factor}"
|
||||
Minimum="{StaticResource MinZoomFactor}"
|
||||
Maximum="{StaticResource MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Slider
|
||||
x:Name="_ui_scaler"
|
||||
Value="{Binding zoom_factor}"
|
||||
Minimum="{x:Static spark:LadderView.MinZoomFactor}"
|
||||
Maximum="{x:Static spark:LadderView.MaxZoomFactor}"
|
||||
Width="{Binding Path=ActualWidth, ElementName=_slider_stack_panel}"
|
||||
Visibility="{Binding show_zoom, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</StackPanel>
|
||||
<Grid Margin="2,4,2,0">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -455,13 +461,33 @@
|
||||
<Button
|
||||
Margin="2"
|
||||
Command="{Binding position_click_command}"
|
||||
Content="{Binding position, Converter={StaticResource PositionTextConverter}}"
|
||||
Content="{Binding position_display_text}"
|
||||
FontWeight="Bold"
|
||||
Foreground="{Binding position, Converter={StaticResource NumericSignBrushConverter}}"
|
||||
Visibility="{Binding show_account_position, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<Button.Visibility>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<converters:MultiValueConverterPipe>
|
||||
<converters:BooleanOrConverter/>
|
||||
<converters:BooleanToVisibilityConverter/>
|
||||
</converters:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</Button.Visibility>
|
||||
<Button.ToolTip>
|
||||
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
|
||||
<!--<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>-->
|
||||
<TextBlock TextAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static spark:LadderView.PositionToolTipConverter}">
|
||||
<Binding Path="order_properties_vm.selected_account"/>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button.ToolTip>
|
||||
</Button>
|
||||
<!-- Qty textbox -->
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using bts.collections;
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.converters;
|
||||
using mbts.utils;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
@@ -8,7 +11,40 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderView : UserControl {
|
||||
readonly double _max_zoom, _min_zoom;
|
||||
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";
|
||||
if (!account.HasValue)
|
||||
return string.Empty;
|
||||
var sb = new StringBuilderEx(account.Value.name);
|
||||
string primary_pos;
|
||||
if (show_day_pos) {
|
||||
primary_pos = day_pos_text;
|
||||
if (show_total_pos) {
|
||||
sb.Add(" '", primary_pos, " ");
|
||||
sb.Add("(", total_pos_text, ")' ");
|
||||
} else {
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
}
|
||||
} else if (show_total_pos) {
|
||||
primary_pos = total_pos_text;
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
} else {
|
||||
// unexpected code path (the button for which this tooltip is destined should be invisible in this case), but no need to throw an exception.
|
||||
return string.Empty;
|
||||
}
|
||||
sb.Add("position\n(click to copy ");
|
||||
if (show_day_pos && show_total_pos)
|
||||
sb.Add(primary_pos, " position ");
|
||||
sb.Add("to size)");
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
InitializeComponent();
|
||||
@@ -18,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;
|
||||
@@ -46,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ManualFillRow : DisposableViewModelBase, IEquatable<ManualFillRow> {
|
||||
public const int _uncommitted_record_id = -1;
|
||||
static readonly PropertyInfo[] _snapshot_props =
|
||||
Array.ConvertAll(new[] {
|
||||
nameof(security),
|
||||
nameof(account),
|
||||
nameof(user),
|
||||
nameof(side),
|
||||
nameof(size),
|
||||
nameof(price)
|
||||
}, typeof(ManualFillRow).GetProperty);
|
||||
readonly Property<bool> _is_dirty;
|
||||
readonly Property<EditableItemState> _editable_item_state;
|
||||
readonly DelegateProperty<bool> _is_valid;
|
||||
SecurityImmutable? _security;
|
||||
AccountTuple? _account;
|
||||
UserTuple? _user;
|
||||
Side _side;
|
||||
uint _size;
|
||||
double _price;
|
||||
bool _is_selected;
|
||||
ManualFillData? _data;
|
||||
IDisposable _committed_snapshot;
|
||||
|
||||
public ManualFillRow(ManualFillData data) {
|
||||
_data = data;
|
||||
key = data.key;
|
||||
_security = data.value.security;
|
||||
_account = data.value.account;
|
||||
_user = data.value.user;
|
||||
_side = data.value.manual_fill.Side;
|
||||
_size = data.value.manual_fill.Quantity;
|
||||
_price = data.value.manual_fill.Price;
|
||||
if (placeholder_key.HasValue) {
|
||||
_is_dirty = backing(nameof(is_dirty), false);
|
||||
_editable_item_state = backing(nameof(editable_item_state), EditableItemState.Uncommitted);
|
||||
_committed_snapshot = ObjectSnapshot.create(this, _snapshot_props);
|
||||
} else {
|
||||
_is_dirty = backing(nameof(is_dirty), ObjectSnapshot.dirty_monitor(this, out _committed_snapshot, _snapshot_props));
|
||||
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
|
||||
}
|
||||
_is_valid = backing(nameof(is_valid), () => _validate(this));
|
||||
}
|
||||
|
||||
public event Action<bool> is_selected_changed;
|
||||
// key/id
|
||||
public ManualFillKey key { get; }
|
||||
public long? placeholder_key => key.unpersisted;
|
||||
public int record_id => key.persisted ?? _uncommitted_record_id;
|
||||
public string record_id_display_text => key.persisted?.ToString();
|
||||
// security/spec
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (_set(ref _security, value))
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}
|
||||
}
|
||||
public ContractSpecificationImmutable? spec => security.spec_or_default();
|
||||
// account/user
|
||||
public AccountTuple? account { get => _account; set => _set(ref _account, value); }
|
||||
public UserTuple? user { get => _user; set => _set(ref _user, value); }
|
||||
// side/size/price
|
||||
public Side side { get => _side; set => _set(ref _side, value); }
|
||||
public uint size { get => _size; set => _set(ref _size, value); }
|
||||
public double price { get => _price; set => _set(ref _price, value); }
|
||||
// is selected
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (_set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
// dirty/valid/editable-state
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_valid => _is_valid.Value;
|
||||
public EditableItemState editable_item_state => _editable_item_state.Value;
|
||||
|
||||
public ManualFillData data => _data ??
|
||||
(_data = new ManualFillData(key, new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user))).Value;
|
||||
|
||||
public void revert() =>
|
||||
_committed_snapshot.Dispose();
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() =>
|
||||
_is_dirty.observe_current(true);
|
||||
|
||||
public IObservable<bool> subscribe_is_selected() =>
|
||||
Observable.Create<bool>(o => {
|
||||
o.OnNext(is_selected);
|
||||
is_selected_changed += o.OnNext;
|
||||
return Disposable.Create(() => is_selected_changed -= o.OnNext);
|
||||
});
|
||||
|
||||
public ManualFill to_man_fill() =>
|
||||
new ManualFill {
|
||||
RecordId = record_id,
|
||||
AccountId = account?.id ?? 0,
|
||||
AccountName = account?.name ?? string.Empty,
|
||||
UserId = user?.id ?? 0,
|
||||
Price = price,
|
||||
Side = side,
|
||||
Quantity = size,
|
||||
SecurityKey = security?.Id ?? 0
|
||||
};
|
||||
|
||||
public ManualFillTuple to_tuple() =>
|
||||
new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user);
|
||||
|
||||
public bool Equals(ManualFillRow other) =>
|
||||
data == other?.data;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as ManualFillRow);
|
||||
public override int GetHashCode() => record_id;
|
||||
|
||||
static bool _validate(ManualFillRow row) =>
|
||||
row.security.HasValue &&
|
||||
row.account.HasValue &&
|
||||
row.user.HasValue &&
|
||||
row.side != Side.Undefined &&
|
||||
!double.IsNaN(row._price);
|
||||
|
||||
bool _set<T>(ref T backing, T value, [CallerMemberName] string prop_name = "") =>
|
||||
_set(ref backing, value, EqualityComparer<T>.Default, prop_name);
|
||||
|
||||
bool _set<T>(ref T backing, T value, IEqualityComparer<T> comparer, [CallerMemberName] string prop_name = "") {
|
||||
if (Set(ref backing, value, comparer, prop_name)) {
|
||||
_is_valid.refresh();
|
||||
_data = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillRowEditor"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">8</sys:Double>
|
||||
<Style x:Key="HeaderText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{x:Static u:BTSColors.header_text_brush}"/>
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="11" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.header_brush}"/>
|
||||
<TextBlock Grid.Column="0" Text="Security" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="2" Text="Account" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="4" Text="User" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="6" Text="Side" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="8" Text="Qty" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="10" Text="Price" Style="{StaticResource HeaderText}"/>
|
||||
|
||||
<c:ContractPickerPopup
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
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}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Content="OK" Command="{x:Static usp:ManualFillRowEditor.CommitCommand}" CommandParameter="{Binding}" IsDefault="True"/>
|
||||
<Button Content="Cancel" Command="{x:Static usp:ManualFillRowEditor.CancelCommand}" IsCancel="True"/>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillRowEditor.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillRowEditor : UserControl {
|
||||
public const string
|
||||
SecuritiesKey = nameof(SecuritiesKey),
|
||||
AccountsKey = nameof(AccountsKey),
|
||||
UsersKey = nameof(UsersKey),
|
||||
SidesKey = nameof(SidesKey);
|
||||
|
||||
public static readonly RoutedCommand
|
||||
CommitCommand = new RoutedCommand(),
|
||||
CancelCommand = new RoutedCommand();
|
||||
|
||||
public ManualFillRowEditor() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.ManualFillsImportConflictsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Fills Conflicts"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:ManualFillsImportConflictsView Margin="12,10,12,8"/>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Command="{Binding ok_cmd}">Continue</Button>
|
||||
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsImportConflictsDialog.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsImportConflictsDialog : Window {
|
||||
public ManualFillsImportConflictsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Component = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ManualFillsImportVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly RefCountMap<ParsedManualFill> _conflicts = new RefCountMap<ParsedManualFill>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public ManualFillsImportVM(int num_fills) {
|
||||
rows_view = CollectionViewSource.GetDefaultView(rows);
|
||||
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
|
||||
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
|
||||
this.num_fills = num_fills;
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(rows, r =>
|
||||
r.subscribe_has_error().Subscribe(has_error => {
|
||||
if (has_error) {
|
||||
if (r.affected_fills.Count(_conflicts.increment_adds_key) == 0)
|
||||
return;
|
||||
} else if (r.affected_fills.Count(_conflicts.decrement_removes_key) == 0)
|
||||
return;
|
||||
RaisePropertyChanged(nameof(num_successfully_parsed_fills));
|
||||
}));
|
||||
}
|
||||
|
||||
public event Action import_request;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public ICollectionView rows_view { get; }
|
||||
public int num_fills { get; }
|
||||
public int num_successfully_parsed_fills => num_fills - _conflicts.Count;
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
if (!ReferenceEquals(_security_map, value)) {
|
||||
_security_map = value;
|
||||
_resolve_securities();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand ok_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
|
||||
void _resolve_securities() {
|
||||
if (_security_map == null)
|
||||
return;
|
||||
var reverse_lookup = ManualFillsParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
|
||||
foreach (var r in rows) {
|
||||
if (r.component == Component.Security && r.has_error) {
|
||||
var id = reverse_lookup(r.text);
|
||||
if (id != 0 && _security_map.TryGetValue(id, out var smd))
|
||||
r.value = smd.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Row : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly DelegateProperty<bool> _has_error;
|
||||
object _value;
|
||||
string _text;
|
||||
|
||||
public Row(Component component, string text, IReadOnlyCollection<ParsedManualFill> affected_fills, object error_value, object value) {
|
||||
this.component = component;
|
||||
this.text = text;
|
||||
this.affected_fills = affected_fills;
|
||||
_value = value;
|
||||
_has_error = backing(nameof(has_error), () => Equals(this.value, error_value));
|
||||
}
|
||||
|
||||
public Component component { get; }
|
||||
public string text { get => _text; set => Set(ref _text, value); }
|
||||
public IReadOnlyCollection<ParsedManualFill> affected_fills { get; }
|
||||
public object value {
|
||||
get => _value;
|
||||
set {
|
||||
if (Set(ref _value, value))
|
||||
_has_error.refresh();
|
||||
}
|
||||
}
|
||||
public bool has_error => _has_error.Value;
|
||||
string ITemplateSelectorKey.key => component.ToString();
|
||||
|
||||
public IObservable<bool> subscribe_has_error() => _has_error.observe_current(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillsImportConflictsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
MinWidth="400"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<!--Value Cell Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<!--Value Cell Editing Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Security, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding value}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Account, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static User, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Side, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Size, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<!--Content-->
|
||||
<Grid x:Name="content_root">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image VerticalAlignment="Center" Margin="0,0,8,0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
|
||||
<Run Text="{Binding num_successfully_parsed_fills, Mode=OneWay}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding num_fills, Mode=OneWay}"/>
|
||||
<Run Text=" fills have been successfully converted."/>
|
||||
<Run Text="Resolve conflicts below by selecting a value to assign each affected fill,"/>
|
||||
<Run Text="or modify individual fills in the main window."/>
|
||||
<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Run Text="Click 'Continue' to proceed to the main window."/>
|
||||
</TextBlock>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
Margin="0,8,0,0"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Component" Binding="{Binding component}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Affected Fills" Binding="{Binding affected_fills.Count}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsImportConflictsView.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsImportConflictsView : UserControl {
|
||||
public ManualFillsImportConflictsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public enum ManualFillComponent {
|
||||
Security,
|
||||
Account,
|
||||
User,
|
||||
Side,
|
||||
Size,
|
||||
Price
|
||||
}
|
||||
|
||||
public static class ManualFillComponentNames {
|
||||
public static readonly string
|
||||
Security = ManualFillComponent.Security.ToString(),
|
||||
Account = ManualFillComponent.Account.ToString(),
|
||||
User = ManualFillComponent.User.ToString(),
|
||||
Side = ManualFillComponent.Side.ToString(),
|
||||
Size = ManualFillComponent.Size.ToString(),
|
||||
Price = ManualFillComponent.Price.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
struct ManualFillKey : IEquatable<ManualFillKey>, IComparable<ManualFillKey> {
|
||||
public readonly int? persisted;
|
||||
public readonly long? unpersisted;
|
||||
|
||||
public ManualFillKey(int persisted) {
|
||||
this.persisted = persisted;
|
||||
unpersisted = null;
|
||||
}
|
||||
|
||||
public ManualFillKey(long unpersisted) {
|
||||
this.unpersisted = unpersisted;
|
||||
persisted = null;
|
||||
}
|
||||
|
||||
public int CompareTo(ManualFillKey other) {
|
||||
if (persisted.HasValue) {
|
||||
if (other.persisted.HasValue) {
|
||||
return persisted.Value.CompareTo(other.persisted.Value);
|
||||
} else if (other.unpersisted.HasValue) {
|
||||
return -1;
|
||||
} else return 1;
|
||||
} else if (unpersisted.HasValue) {
|
||||
return other.unpersisted.HasValue ? unpersisted.Value.CompareTo(other.unpersisted.Value) : 1;
|
||||
} else return !other.persisted.HasValue && !other.unpersisted.HasValue ? 0 : -1;
|
||||
}
|
||||
|
||||
public override string ToString() => persisted.HasValue ? persisted.Value.ToString() : unpersisted.HasValue ? unpersisted.Value.ToString() : "(N/A)";
|
||||
|
||||
public bool Equals(ManualFillKey other) =>
|
||||
persisted == other.persisted &&
|
||||
unpersisted == other.unpersisted;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => persisted ?? unpersisted?.GetHashCode() ?? 0;
|
||||
public static bool operator ==(ManualFillKey a, ManualFillKey b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillKey a, ManualFillKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct ManualFillData : IEquatable<ManualFillData> {
|
||||
public static readonly Func<ManualFillData, ManualFillKey> key_fn = mfd => mfd.key;
|
||||
|
||||
public readonly ManualFillKey key;
|
||||
public readonly ManualFillTuple value;
|
||||
|
||||
public ManualFillData(ManualFillKey key, ManualFillTuple value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool Equals(ManualFillData other) =>
|
||||
key == other.key &&
|
||||
value.Equals(other.value);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillData other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public static bool operator ==(ManualFillData a, ManualFillData b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillData a, ManualFillData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
class ManualFillsModel {
|
||||
readonly OrdersModel _model;
|
||||
|
||||
public ManualFillsModel(OrdersModel model)
|
||||
: this(model, model.on_manual_fill_tuples,
|
||||
model.factory.subscribe_accounts().Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(u => u.id),
|
||||
model.factory.subscribe_users().Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id)) { }
|
||||
|
||||
public ManualFillsModel(OrdersModel model, IObservable<TableUpdate<ManualFillTuple, int>> manual_fills, IObservable<TableUpdate<AccountTuple, uint>> accounts, IObservable<TableUpdate<UserTuple, uint>> users) {
|
||||
_model = model;
|
||||
this.manual_fills = manual_fills.SelectKeyValue(
|
||||
mft => new ManualFillData(new ManualFillKey(mft.manual_fill.RecordId), mft),
|
||||
ManualFillData.key_fn);
|
||||
this.accounts = accounts;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<ManualFillData, ManualFillKey>> manual_fills { get; }
|
||||
public IObservable<TableUpdate<AccountTuple, uint>> accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> users { get; }
|
||||
|
||||
public async Task delete_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.delete_manual_fills(fills);
|
||||
|
||||
public async Task<bool> create_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.create_manual_fills(fills);
|
||||
|
||||
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.update_manual_fills(fills);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ParsedManualFill {
|
||||
public readonly Guid unpersisted_id = Guid.NewGuid();
|
||||
public readonly ManualFill result = new ManualFill();
|
||||
|
||||
public (string text, bool success) security { get; set; }
|
||||
public (string text, bool success) account { get; set; }
|
||||
public (string text, bool success) user { get; set; }
|
||||
public (string text, bool success) side { get; set; }
|
||||
public (string text, bool success) quantity { get; set; }
|
||||
public (string text, bool success) price { get; set; }
|
||||
|
||||
public bool Equals(ParsedManualFill other) => other != null && unpersisted_id == other.unpersisted_id;
|
||||
public override bool Equals(object obj) => Equals(obj as ParsedManualFill);
|
||||
public override int GetHashCode() => unpersisted_id.GetHashCode();
|
||||
}
|
||||
|
||||
static class ManualFillsParser {
|
||||
public const string file_extension = "csv";
|
||||
|
||||
public static Action<ManualFill, object> assign_component(ManualFillComponent component) {
|
||||
switch (component) {
|
||||
case ManualFillComponent.Security: return (mf, val) => mf.SecurityKey = ConvertUtils.try_convert<SecurityImmutable?>(val)?.Id ?? 0;
|
||||
case ManualFillComponent.Account: return (mf, val) => mf.AccountId = ConvertUtils.try_convert<AccountTuple?>(val)?.id ?? 0;
|
||||
case ManualFillComponent.User: return (mf, val) => mf.UserId = ConvertUtils.try_convert<UserTuple?>(val)?.id ?? 0;
|
||||
case ManualFillComponent.Side: return (mf, val) => mf.Side = ConvertUtils.try_convert<Side>(val, Side.Undefined);
|
||||
case ManualFillComponent.Size: return (mf, val) => mf.Quantity = ConvertUtils.try_convert<uint>(val);
|
||||
case ManualFillComponent.Price: return (mf, val) => mf.Price = ConvertUtils.try_convert(val, double.NaN);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: the returned dictionary will have entries for each <see cref="ManualFillComponent"/>, regardless of whether or not that component has any conflicts.
|
||||
public static Dictionary<ManualFillComponent, Dictionary<string, List<ParsedManualFill>>> get_conflicts(IEnumerable<ParsedManualFill> parsed_fills) {
|
||||
var missing_components = EnumHelper.get_values<ManualFillComponent>().ToDictionary(c => c, c => new Dictionary<string, List<ParsedManualFill>>());
|
||||
var mk_list = new Func<string, List<ParsedManualFill>>(_ => new List<ParsedManualFill>());
|
||||
foreach (var pmf in parsed_fills) {
|
||||
if (!pmf.security.success) {
|
||||
add_missing(ManualFillComponent.Security, pmf.security.text, pmf);
|
||||
}
|
||||
if (!pmf.account.success) {
|
||||
add_missing(ManualFillComponent.Account, pmf.account.text, pmf);
|
||||
}
|
||||
if (!pmf.user.success) {
|
||||
add_missing(ManualFillComponent.User, pmf.user.text, pmf);
|
||||
}
|
||||
if (!pmf.quantity.success) {
|
||||
add_missing(ManualFillComponent.Size, pmf.quantity.text, pmf);
|
||||
}
|
||||
if (!pmf.side.success) {
|
||||
add_missing(ManualFillComponent.Side, pmf.side.text, pmf);
|
||||
}
|
||||
if (!pmf.price.success) {
|
||||
add_missing(ManualFillComponent.Price, pmf.price.text, pmf);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(ManualFillComponent component, string text, ParsedManualFill result) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add(result);
|
||||
}
|
||||
|
||||
// This implementation prioritizes simplicity over speed.
|
||||
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, IReadOnlyDictionary<uint, string> account_map, IReadOnlyDictionary<uint, string> user_map) =>
|
||||
parse_rows(rows,
|
||||
reverse_lookup(MappedDictionary.create(security_map, kv => kv.Value.ToString())),
|
||||
reverse_lookup(account_map),
|
||||
reverse_lookup(user_map));
|
||||
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, Func<string, uint> security_id_lookup, Func<string, uint> account_id_lookup, Func<string, uint> user_id_lookup) {
|
||||
var columns = new[] {
|
||||
(Headers.Security, Cells.security(security_id_lookup)),
|
||||
(Headers.Account, Cells.account(account_id_lookup)),
|
||||
(Headers.User, Cells.user(user_id_lookup)),
|
||||
(Headers.Side, Cells.side),
|
||||
(Headers.Quantity, Cells.quantity),
|
||||
(Headers.Price, Cells.price)
|
||||
}.ToDictionary(col => col.Item1, col => col.Item2.with_null_check());
|
||||
return RowParser.parse_rows(rows, columns, Cells.delimiter);
|
||||
}
|
||||
|
||||
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
|
||||
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
|
||||
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
|
||||
}
|
||||
|
||||
static class Headers {
|
||||
public const string
|
||||
Security = "Security",
|
||||
Account = "Account",
|
||||
User = "User",
|
||||
Side = "Side",
|
||||
Quantity = "Quantity",
|
||||
Price = "Price";
|
||||
}
|
||||
|
||||
static class Cells {
|
||||
public const char delimiter = ',';
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> security(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.SecurityKey = reverse_id_lookup(text);
|
||||
pmf.security = (text, pmf.result.SecurityKey != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void side(ref ParsedManualFill pmf, string cell_text) {
|
||||
switch (cell_text[0]) {
|
||||
case 'B': pmf.result.Side = Side.Buy; break;
|
||||
case 'S': pmf.result.Side = Side.Sell; break;
|
||||
default: pmf.side = (cell_text, false); break;
|
||||
}
|
||||
pmf.side = (cell_text, true);
|
||||
}
|
||||
|
||||
public static void quantity(ref ParsedManualFill pmf, string cell_text) {
|
||||
if (uint.TryParse(cell_text, out uint v)) {
|
||||
pmf.result.Quantity = v;
|
||||
pmf.quantity = (cell_text, true);
|
||||
} else {
|
||||
pmf.quantity = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void price(ref ParsedManualFill pmf, string cell_text) {
|
||||
if (double.TryParse(cell_text, out var v)) {
|
||||
pmf.result.Price = v;
|
||||
pmf.price = (cell_text, true);
|
||||
} else {
|
||||
pmf.price = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> account(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.AccountId = reverse_id_lookup(text);
|
||||
pmf.account = (text, pmf.result.AccountId != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> user(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.UserId = reverse_id_lookup(text);
|
||||
pmf.user = (text, pmf.result.UserId != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
[ProtoContract]
|
||||
class ManualFillsSettings {
|
||||
[ProtoMember(1)]
|
||||
readonly SortingCriteria[] _sorting_criterion;
|
||||
|
||||
public static ManualFillsSettings Default =>
|
||||
new ManualFillsSettings(new[] { new SortingCriteria(nameof(mbts.ui.spark.ManualFillsView.Columns.Security), true) });
|
||||
|
||||
public ManualFillsSettings() { }
|
||||
|
||||
public ManualFillsSettings(IEnumerable<SortingCriteria> sorting_criterion) =>
|
||||
_sorting_criterion = sorting_criterion.ToArray();
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? Array.Empty<SortingCriteria>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
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.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Disposable = mbts.Disposable;
|
||||
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
enum EditableItemState {
|
||||
None, // Committed and not modified
|
||||
Modified, // Committed and modified
|
||||
Uncommitted // Uncommitted (modified n/a)
|
||||
}
|
||||
|
||||
class ManualFillsVM : DisposableViewModelBase {
|
||||
const int _max_unconfirmed_removes = 16;
|
||||
static readonly IComparer<ManualFillRow> _row_key_comparer = Comparer<ManualFillRow>.Create((a, b) => a.key.CompareTo(b.key));
|
||||
readonly IBus _bus;
|
||||
readonly ManualFillsModel _model;
|
||||
readonly ObservableCollection<ManualFillRow>
|
||||
_persisted_rows = new ObservableCollection<ManualFillRow>(),
|
||||
_placeholder_rows = new ObservableCollection<ManualFillRow>();
|
||||
long _placeholder_id_counter = long.MinValue;
|
||||
IDisposable _import_fills_hooks;
|
||||
|
||||
public static ManualFillsVM create(IBus bus, OrdersModel model) =>
|
||||
new ManualFillsVM(bus, new ManualFillsModel(model));
|
||||
|
||||
public ManualFillsVM(IBus bus, ManualFillsModel model) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
var merged_rows = new ObservableAggregateCollection<ManualFillRow>(_persisted_rows, _placeholder_rows);
|
||||
rows_view = CollectionViewSource.GetDefaultView(merged_rows);
|
||||
// mons
|
||||
var dirty_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_dirty());
|
||||
var selection_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_selected());
|
||||
var placeholder_mon = new ReactiveThresholdMonitor<ManualFillRow>();
|
||||
// commands
|
||||
commit_all_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(merged_rows), out var commit_all_fils_observer);
|
||||
commit_selected_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(selection_mon.filtered_items), out var commit_selected_fills_observer);
|
||||
revert_all_cmd = ObservantCommand.create(() => revert(dirty_mon.filtered_items.ToList()), out var revert_all_observer);
|
||||
revert_selected_fills_cmd = ObservantCommand.create(() => revert(selection_mon.filtered_items.ToList()), out var revert_selected_fills_observer);
|
||||
remove_selected_fills_cmd = ObservantAsyncCommand.create(remove_selected_fills_async, out var remove_selected_fills_observer);
|
||||
add_fill_cmd = new DelegateCommand<ManualFillRow>(_add_fill);
|
||||
import_fills_cmd = FileDialogCommand.open_file(_import_fills, extensions: ManualFillsParser.file_extension);
|
||||
//
|
||||
var dirty_or_new_src = dirty_mon.CombineLatest(placeholder_mon, LogicalDelegates.Or);
|
||||
_dispose = Disposable.Create(
|
||||
RXCollection.connect(_persisted_rows,
|
||||
_model.manual_fills.OnDispatcher(),
|
||||
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = m;
|
||||
RaisePropertyChanged(nameof(security_map));
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
|
||||
ObservableAutoWrapper.connect_item_hooks(_placeholder_rows, r => Disposable.Create(selection_mon.join(r), placeholder_mon.join(r, Observable.Return(true)))),
|
||||
// command param subs
|
||||
dirty_mon.Subscribe(revert_all_observer),
|
||||
dirty_or_new_src.OnDispatcher().Subscribe(commit_all_fils_observer),
|
||||
selection_mon.Subscribe(remove_selected_fills_observer),
|
||||
selection_mon.CombineLatest(dirty_mon, LogicalDelegates.And).OnDispatcher().Subscribe(revert_selected_fills_observer),
|
||||
selection_mon.CombineLatest(dirty_or_new_src, LogicalDelegates.And).OnDispatcher().Subscribe(commit_selected_fills_observer),
|
||||
// accounts/users subs
|
||||
RXCollection.connect(accounts, model.accounts.OnDispatcher(), a => new AccountTuple?(a), a => a.Value, Comparer<AccountTuple>.Default),
|
||||
RXCollection.connect(users, model.users.OnDispatcher(), u => new UserTuple?(u), u => u.Value, Comparer<UserTuple>.Default),
|
||||
Disposable.Create(() => Disposable.dispose(ref _import_fills_hooks))
|
||||
);
|
||||
// local methods
|
||||
void revert(IReadOnlyList<ManualFillRow> rows) {
|
||||
foreach (var r in rows)
|
||||
r.revert();
|
||||
}
|
||||
async Task remove_selected_fills_async() {
|
||||
if (selection_mon.filtered_items.Count < _max_unconfirmed_removes || BTSMessageBox.Confirm($"Are you sure you want to delete {selection_mon.filtered_items.Count} fills?", "Delete Fills")) {
|
||||
var persisted_fills = new List<ManualFill>();
|
||||
foreach (var r in selection_mon.filtered_items.ToList()) {
|
||||
if (r.placeholder_key.HasValue)
|
||||
_placeholder_rows.RemoveSorted(r, _row_key_comparer);
|
||||
else persisted_fills.Add(r.to_man_fill());
|
||||
}
|
||||
await _model.delete_fills(persisted_fills);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICollectionView rows_view { get; }
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map { get; private set; } = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
// commands
|
||||
public ICommand add_fill_cmd { get; }
|
||||
public ICommand commit_all_fills_cmd { get; }
|
||||
public ICommand commit_selected_fills_cmd { get; }
|
||||
public ICommand import_fills_cmd { get; }
|
||||
public ICommand remove_selected_fills_cmd { get; }
|
||||
public ICommand revert_all_cmd { get; }
|
||||
public ICommand revert_selected_fills_cmd { get; }
|
||||
|
||||
public ManualFillRow row_factory() =>
|
||||
new ManualFillRow(
|
||||
new ManualFillData(
|
||||
new ManualFillKey(_placeholder_id_counter++),
|
||||
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
|
||||
Side = Side.Buy
|
||||
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
|
||||
|
||||
void _add_fill(ManualFillRow new_row) =>
|
||||
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
|
||||
new_row : throw new InvalidOperationException("New row is already persisted."),
|
||||
_row_key_comparer);
|
||||
|
||||
async Task _commit_async(IEnumerable<ManualFillRow> rows) {
|
||||
var updates = new List<ManualFill>();
|
||||
var adds = new List<(ManualFill man_fill, long pl_key)>();
|
||||
foreach (var r in rows) {
|
||||
if (r.is_dirty) {
|
||||
updates.Add(r.to_man_fill());
|
||||
} else if (r.placeholder_key.HasValue) {
|
||||
adds.Add((r.to_man_fill(), r.placeholder_key.Value));
|
||||
}
|
||||
}
|
||||
if (updates.Count > 0)
|
||||
await _model.update_fills(updates);
|
||||
if (adds.Count > 0) {
|
||||
await _model.create_fills(adds.WrapSelector(r => r.man_fill, null));
|
||||
_placeholder_rows
|
||||
.WrapSelector(r => r.placeholder_key.Value, null)
|
||||
.RemoveSorted(adds.Select(r => r.pl_key));
|
||||
}
|
||||
}
|
||||
|
||||
void _import_fills(string file_name) {
|
||||
try {
|
||||
var lines = File.ReadAllLines(file_name);
|
||||
var parsed_fills = ManualFillsParser.parse_rows(
|
||||
lines,
|
||||
MappedDictionary.create(security_map, kv => kv.Value.security),
|
||||
accounts.ToDictionary(a => a.Value.id, a => a.Value.name),
|
||||
users.ToDictionary(u => u.Value.id, u => u.Value.name)
|
||||
).ToList();
|
||||
var conflicts = ManualFillsParser.get_conflicts(parsed_fills);
|
||||
if (conflicts.Values.Any(c => c.Count > 0)) {
|
||||
// init vm
|
||||
var vm = new ManualFillsImportVM(parsed_fills.Count) {
|
||||
security_map = security_map
|
||||
};
|
||||
// add rows
|
||||
foreach (var kv in conflicts) {
|
||||
var assign_del = ManualFillsParser.assign_component(kv.Key);
|
||||
foreach (var text_groups in kv.Value) {
|
||||
var eval = get_error_value(kv.Key);
|
||||
var row = new ManualFillsImportVM.Row(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
|
||||
vm.rows.Add(row);
|
||||
vm.import_request += () => {
|
||||
foreach (var pmf in text_groups.Value)
|
||||
assign_del(pmf.result, row.value);
|
||||
};
|
||||
}
|
||||
}
|
||||
var win = new mbts.ui.spark.ManualFillsImportConflictsDialog {
|
||||
DataContext = vm,
|
||||
Title = ManualFillsWindowFactory.window_name,
|
||||
Topmost = true
|
||||
};
|
||||
vm.RequestClose += (s, e) => Disposable.dispose(ref _import_fills_hooks);
|
||||
vm.import_request += () => {
|
||||
add_imported_fills();
|
||||
Disposable.dispose(ref _import_fills_hooks);
|
||||
};
|
||||
Disposable.replace(ref _import_fills_hooks, Disposable.Create(
|
||||
ObservableAutoWrapper.connect(accounts, vm.accounts),
|
||||
ObservableAutoWrapper.connect(users, vm.users),
|
||||
vm,
|
||||
Disposable.Create(close)
|
||||
));
|
||||
win.move_to_mouse(-200, -75);
|
||||
win.Show();
|
||||
void close() => WindowUtilities.force_close(win);
|
||||
object get_error_value(ManualFillComponent component) {
|
||||
switch (component) {
|
||||
case ManualFillComponent.Price: return double.NaN;
|
||||
case ManualFillComponent.Size: return 0;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
add_imported_fills();
|
||||
}
|
||||
void add_imported_fills() =>
|
||||
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
|
||||
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
|
||||
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
|
||||
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<!--Row Header Style-->
|
||||
<DataGrid.RowHeaderStyle>
|
||||
<Style TargetType="DataGridRowHeader">
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Border x:Name="Root">
|
||||
<u:StateManager.VisualState>
|
||||
<MultiBinding Converter="{x:Static usp:ManualFillsView.RowHeaderVisualStateConverter}">
|
||||
<Binding Path="editable_item_state"/>
|
||||
<Binding Path="is_valid"/>
|
||||
</MultiBinding>
|
||||
</u:StateManager.VisualState>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState Name="None"/>
|
||||
<VisualState Name="Modified">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="pencil" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="0.7"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has modified properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Uncommitted">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#B3006000"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Error">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="exclamation" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has invalid properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="ErrorUncommitted">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#CCCD4848"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill has invalid properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Image x:Name="new_item" usp:ManualFillsView.ImageBrush="Transparent" Height="14">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Brush="{Binding Path='(usp:ManualFillsView.ImageBrush)', ElementName=new_item}" Geometry="{Binding Source={StaticResource {x:Static u:Icons.DocumentAddGeometryKey}}}"/>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Image x:Name="exclamation" Height="14" Opacity="0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Image x:Name="pencil" Height="14" Source="{x:Static u:Icons.Pencil}" Opacity="0">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform Angle="45" CenterX="0" CenterY="7"/>
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.RowHeaderStyle>
|
||||
<!--Cell Style-->
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<!--Row Style-->
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<!--Record ID-->
|
||||
<DataGridTextColumn c:DataGridEx.CellKey="{x:Static RecordID, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="ID" Binding="{Binding record_id_display_text}" SortMemberPath="record_id" IsReadOnly="True"/>
|
||||
<!---Security-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Security, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Security" SortMemberPath="security">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding security}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
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>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Account-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Account, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Account" SortMemberPath="account">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding account}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<ComboBox
|
||||
x:Name="mutable_view"
|
||||
ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
SelectedItem="{Binding account, UpdateSourceTrigger=PropertyChanged}"
|
||||
Visibility="Visible"
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="readonly_view"
|
||||
Text="{Binding account}"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding placeholder_key}" Value="{x:Null}">
|
||||
<Setter TargetName="mutable_view" Property="Visibility" Value="Collapsed"/>
|
||||
<Setter TargetName="readonly_view" Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--User-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static User, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="User" SortMemberPath="user">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding user}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding user, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Side-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Side, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Side" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding side}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding side, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Size-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Price-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Price, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Price" SortMemberPath="price">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="price"/>
|
||||
<Binding Path="spec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl ContractSpec="{Binding spec}" Value="{Binding price, UpdateSourceTrigger=PropertyChanged}" AlwaysReformatOnSpecChange="True"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,97 @@
|
||||
using bts.shell.manual_fills;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.converters;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsView.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsView : UserControl {
|
||||
public static readonly IMultiValueConverter
|
||||
RowHeaderVisualStateConverter = ValueConverter.create<EditableItemState, bool, string>(
|
||||
(edit_state, is_valid) => is_valid ? edit_state.ToString() : edit_state == EditableItemState.Uncommitted ? "ErrorUncommitted" : "Error");
|
||||
|
||||
public enum Columns {
|
||||
RecordID,
|
||||
Security,
|
||||
Account,
|
||||
User,
|
||||
Side,
|
||||
Size,
|
||||
Price
|
||||
}
|
||||
|
||||
IDisposable _transaction;
|
||||
|
||||
public ManualFillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static readonly DependencyProperty ImageBrushProperty =
|
||||
DependencyProperty.RegisterAttached("ImageBrush", typeof(Brush), typeof(ManualFillsView));
|
||||
|
||||
public static Brush GetImageBrush(DependencyObject d) => d.GetValue(ImageBrushProperty) as Brush;
|
||||
public static void SetImageBrush(DependencyObject d, Brush value) => d.SetValue(ImageBrushProperty, value);
|
||||
|
||||
public void begin_add_fill() {
|
||||
if (!(DataContext is ManualFillsVM vm))
|
||||
return;
|
||||
Disposable.dispose(ref _transaction);
|
||||
var row = vm.row_factory();
|
||||
var row_editor = new ManualFillRowEditor { DataContext = row };
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
|
||||
// dynamic resources
|
||||
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
|
||||
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
|
||||
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
|
||||
FocusManager.SetIsFocusScope(row_editor, true);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
|
||||
// popup adorner
|
||||
var popup_adorner = new PopupAdorner(
|
||||
data_grid, data_grid,
|
||||
new Border {
|
||||
Child = row_editor,
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderBrush = Brushes.Black,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(0, 6, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
},
|
||||
HorizontalPosition.Middle,
|
||||
VerticalPosition.Middle,
|
||||
0,
|
||||
22) {
|
||||
OpenRelativeToMouse = true,
|
||||
EnableDragMove = true,
|
||||
ClipToBounds = true
|
||||
};
|
||||
popup_adorner.attach();
|
||||
popup_adorner.IsOpen = true;
|
||||
row_editor.Focus();
|
||||
_transaction = new CompositeDisposable(sec_hook) { destroy_popup };
|
||||
// local methods
|
||||
void commit_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
vm.add_fill_cmd?.TryExecuteIfCan(row);
|
||||
Disposable.dispose(ref _transaction);
|
||||
}
|
||||
void cancel_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
row.revert();
|
||||
Disposable.dispose(ref _transaction);
|
||||
}
|
||||
void destroy_popup() {
|
||||
popup_adorner.IsOpen = false;
|
||||
popup_adorner.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using bts.api;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell {
|
||||
class ManualFillsWindowFactory : ShellWindowFactory<ManualFillsSettings> {
|
||||
public const string
|
||||
window_type = "ManualFills",
|
||||
window_name = "Manual Fills";
|
||||
const double _window_min_width = 466;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ManualFillsConfig _manual_fills_conf = ManualFillsConfig.read();
|
||||
|
||||
public ManualFillsWindowFactory(IShellModule owner, IBus bus, OrdersModel orders_model) : base(owner, window_name) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowManualFillsTag).Subscribe(open_window);
|
||||
//
|
||||
async void open_window(ManualFillsSettings state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(ManualFillsSettings settings) {
|
||||
return Task.FromResult<IShellWindow>(SparkWindow<ManualFillsSettings>.create(
|
||||
window_type, window_name, _bus, this, Icons.DataEdit, _manual_fills_conf.thread_id, thread_init, _window_min_width));
|
||||
//
|
||||
(UIElement, IEnumerable<SparkCommand>, Func<ManualFillsSettings>, IDisposable) thread_init() {
|
||||
var vm = ManualFillsVM.create(_bus, _orders_model);
|
||||
var view = new ManualFillsView { DataContext = vm };
|
||||
view.data_grid.Sorting += on_sorting;
|
||||
IReadOnlyList<SortingCriteria> last_sorting_criterion = Array.Empty<SortingCriteria>();
|
||||
using (apply_sort()) {
|
||||
var col_sort_tuples =
|
||||
from sc in settings.sorting_criterion
|
||||
join col in view.data_grid.Columns on sc.col_name equals _col_id(col)
|
||||
select (col, sc);
|
||||
foreach (var (col, sc) in col_sort_tuples) {
|
||||
col.SortDirection = sc.direction();
|
||||
vm.rows_view.SortDescriptions.Add(new SortDescription(col.SortMemberPath, sc.direction()));
|
||||
}
|
||||
}
|
||||
var cmds = new[] {
|
||||
new SparkCommandCommand("Add New", Icons.AddBoxes, _manual_fills_conf.modal_transactions ? new DelegateCommand(() => view.begin_add_fill()) : vm.add_fill_cmd) { group = CommandGroups.Add },
|
||||
new SparkCommandCommand("Import Fills", Icons.Import, vm.import_fills_cmd) { group = CommandGroups.Add },
|
||||
new SparkCommandCommand("Revert Selected", Icons.UndoCircular,vm.revert_selected_fills_cmd) { group = CommandGroups.Revert, show_in_toolbar = false },
|
||||
new SparkCommandCommand("Revert All", Icons.UndoCircular, vm.revert_all_cmd) { group = CommandGroups.Revert },
|
||||
new SparkCommandCommand("Save Selected", Icons.Disk, vm.commit_selected_fills_cmd) { group = CommandGroups.Save, show_in_toolbar = false },
|
||||
new SparkCommandCommand("Save All", Icons.Disk, vm.commit_all_fills_cmd) { group = CommandGroups.Save },
|
||||
new SparkCommandCommand("Delete Selected", Icons.Trash, vm.remove_selected_fills_cmd)
|
||||
};
|
||||
return (view, cmds, get_settings, Disposable.Create(
|
||||
vm.rows_view.subscribe_sorting_criterion(sm => view.data_grid.Columns.TryGetFirst(dc => dc.SortMemberPath == sm, out DataGridColumn match) ? _col_id(match) : null)
|
||||
.Subscribe(sc => last_sorting_criterion = sc),
|
||||
vm));
|
||||
//
|
||||
void on_sorting(object sender, DataGridSortingEventArgs e) {
|
||||
e.Handled = true;
|
||||
using (apply_sort()) {
|
||||
var new_sort_desc = new SortDescription(e.Column.SortMemberPath, e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending);
|
||||
e.Column.SortDirection = new_sort_desc.Direction;
|
||||
if (KeyboardExtensions.IsShiftDown()) {
|
||||
if (vm.rows_view.SortDescriptions.TryGetFirst(sd => sd.PropertyName == e.Column.SortMemberPath, out var match, out int index)) {
|
||||
vm.rows_view.SortDescriptions[index] = new_sort_desc;
|
||||
} else {
|
||||
vm.rows_view.SortDescriptions.Add(new_sort_desc);
|
||||
}
|
||||
} else {
|
||||
vm.rows_view.SortDescriptions.Clear();
|
||||
foreach (var col in view.data_grid.Columns.Except(new[] { e.Column }))
|
||||
col.SortDirection = null;
|
||||
vm.rows_view.SortDescriptions.Add(new_sort_desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Ensures that the primary sort description property (i.e., at index 0) is the placeholder_key.
|
||||
/// This behavior has the effect of grouping placeholder rows at the bottom, without using <see cref="ICollectionView.GroupDescriptions"/> (which is incompatible with row virtualization).
|
||||
IDisposable apply_sort() =>
|
||||
Disposable.Create(() => {
|
||||
var pk_sort_desc = new SortDescription(nameof(ManualFillRow.placeholder_key), ListSortDirection.Ascending);
|
||||
if (vm.rows_view.SortDescriptions.Count > 0) {
|
||||
if (vm.rows_view.SortDescriptions[0].Equals(pk_sort_desc))
|
||||
return;
|
||||
vm.rows_view.SortDescriptions.removeFirst(sd => sd.PropertyName == pk_sort_desc.PropertyName);
|
||||
}
|
||||
vm.rows_view.SortDescriptions.Insert(0, pk_sort_desc);
|
||||
});
|
||||
|
||||
ManualFillsSettings get_settings() =>
|
||||
new ManualFillsSettings(last_sorting_criterion);
|
||||
}
|
||||
}
|
||||
|
||||
static string _col_id(DataGridColumn col) =>
|
||||
DataGridEx.GetCellKey(col)?.ToString();
|
||||
|
||||
readonly struct ManualFillsConfig {
|
||||
public readonly bool modal_transactions;
|
||||
public readonly string thread_id;
|
||||
|
||||
ManualFillsConfig(string thread_id, bool modal_transactions) {
|
||||
this.thread_id = thread_id;
|
||||
this.modal_transactions = modal_transactions;
|
||||
}
|
||||
|
||||
public static ManualFillsConfig read() => new ManualFillsConfig(
|
||||
mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().manual_fills ? window_type : null,
|
||||
mbts.config.Configuration.get<bts.config.ManualFillsOptions>().modal_transactions);
|
||||
}
|
||||
|
||||
static class CommandGroups {
|
||||
public const string
|
||||
Add = "Add",
|
||||
Save = "Save",
|
||||
Revert = "Revert";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<OrderSecurityAlgoMeta> filter { get; }
|
||||
IDataGridColumnFilter<OrderSecurity> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ 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<OrderSecurityAlgoMeta, SecurityImmutable, SecurityCriteria>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, SecurityImmutable, SecurityCriteria>(
|
||||
order_securities,
|
||||
ort => ort.security ?? SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
SecurityCriteria.from_securities,
|
||||
@@ -85,7 +85,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Symbol,
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ort => ort.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)
|
||||
@@ -98,7 +98,7 @@ namespace mbts.spark {
|
||||
"Time",
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.created(),
|
||||
nameof(OrderBookRowVM.time_created)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
@@ -109,7 +109,7 @@ namespace mbts.spark {
|
||||
"Last Change",
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
new TimeFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_change(),
|
||||
nameof(OrderBookRowVM.last_change)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
@@ -120,7 +120,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.User,
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
ort => ort.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
@@ -132,7 +132,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Account,
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
ort => ort.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
@@ -146,7 +146,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.Exchange,
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Venue>(
|
||||
EnumHelper.get_values_except(Venue.Unset).ToList(),
|
||||
ort => ort.security.exchange_or_default(),
|
||||
nameof(OrderBookRowVM.exchange)),
|
||||
@@ -158,14 +158,14 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, TimeInForce>(
|
||||
EnumHelper.get_values<TimeInForce>().ToList(),
|
||||
ort => ort.order.time_in_force(),
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
// (algo items)
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key, model.dynamic_helper.dynamic_type_index)).OnDispatcher();
|
||||
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
|
||||
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
|
||||
// order type
|
||||
@@ -174,10 +174,9 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.badge_fill),
|
||||
nameof(OrderBookRowVM.display_name),
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
o => AlgoID.from_order_def(o.order.Def),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
@@ -187,10 +186,9 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.root_algo_badge_fill),
|
||||
nameof(OrderBookRowVM.root_algo_display_name),
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurity, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
o => AlgoID.from_algo_key(o.root_algo.key),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
vm => AlgoID.from_algo_key(vm.data.key, model.dynamic_helper.dynamic_type_index), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
root_algo_type_items_view.CustomSort = algo_items_comp;
|
||||
@@ -201,7 +199,7 @@ namespace mbts.spark {
|
||||
"Sub Type",
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
@@ -209,27 +207,27 @@ namespace mbts.spark {
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Side>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, Side>(
|
||||
new[] { Side.Buy, Side.Sell },
|
||||
ort=> ort.order.side(),
|
||||
ort => ort.order.side(),
|
||||
nameof(OrderBookRowVM.side))));
|
||||
// quantity
|
||||
Add(qty_column = new SizeColumn(
|
||||
new UIntMinMax<OrderSecurityAlgoMeta>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.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<OrderSecurityAlgoMeta>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
new UIntMinMax<OrderSecurity>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityAlgoMeta>(
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.price(),
|
||||
nameof(OrderBookRowVM.price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
@@ -245,7 +243,7 @@ namespace mbts.spark {
|
||||
"Avg Price",
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityAlgoMeta>(
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.order.avg_price(),
|
||||
nameof(OrderBookRowVM.avg_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
@@ -256,18 +254,21 @@ namespace mbts.spark {
|
||||
"Last Filled",
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new PriceMinMax<OrderSecurityAlgoMeta>(
|
||||
new PriceMinMax<OrderSecurity>(
|
||||
ort => ort.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))));
|
||||
// status
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurity, OrderStatus>(
|
||||
EnumHelper.get_values<OrderStatus>().ToList(),
|
||||
ort => ort.order.status(),
|
||||
nameof(OrderBookRowVM.status)),
|
||||
@@ -275,7 +276,7 @@ namespace mbts.spark {
|
||||
_wrap_comparer(r => r.status)));
|
||||
// last message
|
||||
Add(last_message_column = new OrderBookLastMessageColumn(
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.last_msg(),
|
||||
nameof(OrderBookRowVM.last_msg))));
|
||||
// security key
|
||||
@@ -284,7 +285,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.security_key().ToString(),
|
||||
nameof(OrderBookRowVM.security_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -295,7 +296,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.Key.ToString(),
|
||||
nameof(OrderBookRowVM.order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -306,7 +307,7 @@ namespace mbts.spark {
|
||||
"Parent Key",
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.ParentKey.ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -317,7 +318,7 @@ namespace mbts.spark {
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
new RegexFilterVM<OrderSecurity>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
@@ -350,6 +351,7 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<string> order_key_column { get; }
|
||||
public SimpleTextColumn<string> parent_key_column { get; }
|
||||
public SimpleTextColumn<string> attached_target_order_key_column { get; }
|
||||
public PiqColumn piq_column { get; }
|
||||
|
||||
public FilterStates create_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -374,7 +376,8 @@ namespace mbts.spark {
|
||||
exchange_message_regex = last_message_column.filter.get_state(),
|
||||
last_change = last_change_column.filter.get_state(),
|
||||
security_criteria = security_column.filter.get_state(),
|
||||
symbols = symbol_column.filter.get_state()
|
||||
symbols = symbol_column.filter.get_state(),
|
||||
piq = piq_column.filter.get_state()
|
||||
};
|
||||
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() =>
|
||||
@@ -404,6 +407,7 @@ namespace mbts.spark {
|
||||
status_column.filter.load_state(fs.statuses);
|
||||
last_message_column.filter.load_state(fs.exchange_message_regex);
|
||||
last_change_column.filter.load_state(fs.last_change);
|
||||
piq_column.filter.load_state(fs.piq);
|
||||
}
|
||||
_active_comparers.Clear();
|
||||
foreach (var col in this)
|
||||
@@ -424,9 +428,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public Func<OrderSecurityAlgoMeta, bool> get_filter() {
|
||||
public Func<OrderSecurity, 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<OrderSecurityAlgoMeta, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
return active_filters.Length == 0 ? null : new Func<OrderSecurity, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
}
|
||||
|
||||
public OrderBookSortingData get_sorting_data() {
|
||||
@@ -527,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<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
@@ -544,10 +548,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, T> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -561,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<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
IDataGridColumnPersistentFilter<OrderSecurity, 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) =>
|
||||
@@ -571,7 +575,7 @@ namespace mbts.spark {
|
||||
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurity, 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) =>
|
||||
@@ -580,7 +584,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurity, 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) =>
|
||||
@@ -589,7 +593,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurity, 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) =>
|
||||
@@ -601,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<OrderSecurityAlgoMeta, 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<OrderSecurity, 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;
|
||||
@@ -611,11 +615,11 @@ namespace mbts.spark {
|
||||
this.comparer = row_comparer;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, AlgoID[]> filter { get; }
|
||||
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -633,7 +637,7 @@ namespace mbts.spark {
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, string> filter)
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderSecurity, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
@@ -642,10 +646,10 @@ namespace mbts.spark {
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, string> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurity, string> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
@@ -654,6 +658,15 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public class PiqColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public PiqColumn(IDataGridColumnPersistentFilter<OrderSecurity, 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) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.piq_text, position, row_height,
|
||||
RenderUtils.get_foreground(row, font_brush), null, HorizontalAlignment.Right, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
|
||||
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding(), _foreground_binding;
|
||||
readonly IValueConverter _expander_visiblilty_converter = new ui.converters.BooleanToVisibilityConverter { VisibilityForFalse = Visibility.Hidden };
|
||||
@@ -661,7 +674,7 @@ namespace mbts.spark {
|
||||
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
@@ -670,10 +683,10 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderSecurity, SecurityCriteria> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderSecurity> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
@@ -711,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;
|
||||
|
||||
@@ -799,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);
|
||||
@@ -816,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;
|
||||
@@ -901,7 +869,6 @@ namespace mbts.spark {
|
||||
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region Detach Button
|
||||
@@ -937,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,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);
|
||||
@@ -982,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;
|
||||
@@ -1073,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();
|
||||
@@ -1082,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,7 +1177,6 @@ namespace mbts.spark {
|
||||
public class CommitColumn : FrameworkElementColumnBase<Columns> {
|
||||
public CommitColumn() : base(Columns.Commit) {
|
||||
id = key.ToString();
|
||||
Header = "";
|
||||
Width = 20;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurityAlgoMeta>, INotifyPropertyChanged {
|
||||
partial class OrderBookRowVM : IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, IHierarchicalRow<OrderSecurity>, INotifyPropertyChanged {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -27,14 +27,16 @@ namespace mbts.spark {
|
||||
// for mini-ob
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
Action<bool> _on_selection_changed;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityAlgoMeta order_tuple, bool is_expanded) {
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurity order_tuple, bool is_expanded, Action<bool> on_selection_changed) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
_is_expanded = is_expanded;
|
||||
prospective_quantity = this.quantity;
|
||||
prospective_price = this.price;
|
||||
_on_selection_changed = on_selection_changed;
|
||||
}
|
||||
|
||||
//// This takes care of the case where the OB has all rows visible
|
||||
@@ -43,19 +45,19 @@ 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<OrderSecurityAlgoMeta> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
|
||||
public IObservable<OrderSecurity> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurity>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
|
||||
public event Action<(OrderSecurity oldval, OrderSecurity newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// Core
|
||||
public OrderSecurityAlgoMeta order_tuple { get; private set; }
|
||||
public OrderSecurity order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.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));
|
||||
@@ -91,6 +93,8 @@ 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 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); }
|
||||
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
|
||||
@@ -116,7 +120,11 @@ namespace mbts.spark {
|
||||
public int depth_level { get => _depth_level; set => Set(ref _depth_level, value); }
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set => Set(ref _is_selected, value);
|
||||
set {
|
||||
if (Set(ref _is_selected, value)) {
|
||||
_on_selection_changed(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
public uint prospective_quantity {
|
||||
get => _prospective_quantity;
|
||||
@@ -134,14 +142,14 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool is_qty_dirty => this.prospective_quantity != this.quantity;
|
||||
public bool is_qty_dirty => this.prospective_quantity != this.quantity;
|
||||
public bool is_price_dirty => this.prospective_price != this.price;
|
||||
|
||||
// Commands
|
||||
public ICommand toggle_command => _toggle_expanded_cmd ?? (
|
||||
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
|
||||
|
||||
public ICommand show_order_modify_cmd =>
|
||||
public ICommand show_order_modify_cmd =>
|
||||
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
|
||||
public ICommand cancel_command => _cancel_cmd ??
|
||||
@@ -188,14 +196,18 @@ namespace mbts.spark {
|
||||
public ICommand attach_command => _attach_command ?? (
|
||||
_attach_command = new DelegateCommand(() => {
|
||||
// the _select me will trigger the OBVM.attach_vm to get primed
|
||||
_select_me();
|
||||
if (!is_selected) {
|
||||
_select_me();
|
||||
}
|
||||
_owner.begin_attach_algos();
|
||||
})
|
||||
);
|
||||
|
||||
public ICommand update_command => _update_command ?? (
|
||||
_update_command = new DelegateCommand(() => {
|
||||
_select_me();
|
||||
if (!is_selected) {
|
||||
_select_me();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -214,7 +226,7 @@ namespace mbts.spark {
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
int IHierarchicalRow.depth_level => depth_level;
|
||||
OrderSecurityAlgoMeta IHierarchicalRow<OrderSecurityAlgoMeta>.value => order_tuple;
|
||||
OrderSecurity IHierarchicalRow<OrderSecurity>.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.
|
||||
@@ -226,17 +238,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<OrderSecurityAlgoMeta>.on_value_changed(OrderSecurityAlgoMeta value) => update(value);
|
||||
void IHierarchicalRow<OrderSecurity>.on_value_changed(OrderSecurity 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,
|
||||
@@ -244,7 +254,7 @@ 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(OrderSecurityAlgoMeta order_tuple) {
|
||||
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.");
|
||||
// _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);
|
||||
@@ -257,11 +267,11 @@ namespace mbts.spark {
|
||||
// quantity
|
||||
if ((Columns.Size & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(quantity), nameof(is_qty_dirty));
|
||||
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
||||
// via the Mini OB update column mechanism (vs the EditableQty column)
|
||||
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
||||
// row will no longer match the filter
|
||||
prospective_quantity = quantity;
|
||||
// we need to update the prospective_quantity here b/c an update could have come in while the row was in edit mode
|
||||
// via the Mini OB update column mechanism (vs the EditableQty column)
|
||||
// prospecive price does not need to be updated in the same manner b/c if the price is changed, the whole
|
||||
// row will no longer match the filter
|
||||
prospective_quantity = quantity;
|
||||
// price
|
||||
if ((Columns.Price & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(price));
|
||||
@@ -288,6 +298,10 @@ namespace mbts.spark {
|
||||
// root display name
|
||||
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
|
||||
RaisePropertyChanged(nameof(root_algo_display_name));
|
||||
// root order type
|
||||
if (old_order_tuple.root_algo.value.CustomCase != order_tuple.root_algo.value.CustomCase) {
|
||||
RaisePropertyChanged(nameof(root_algo_order_type));
|
||||
}
|
||||
// status
|
||||
if ((Columns.Status & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(status));
|
||||
@@ -314,6 +328,9 @@ namespace mbts.spark {
|
||||
// this will update the cancel column in the mini ob
|
||||
cancel_command.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
if ((Columns.Piq & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(piq), nameof(piq_text));
|
||||
}
|
||||
// always refresh attach and detach b/c
|
||||
// conditionally raise invalidated
|
||||
if ((dirty & ViewRefresh.invalidate_mask) != 0)
|
||||
@@ -374,13 +391,14 @@ namespace mbts.spark {
|
||||
Columns.Status |
|
||||
Columns.ParentKey |
|
||||
Columns.AttachedTargetOrderKey |
|
||||
Columns.LastMessage,
|
||||
Columns.LastMessage |
|
||||
Columns.Piq,
|
||||
filled_status_desc_mask =
|
||||
Columns.Filled |
|
||||
Columns.AvgFilled |
|
||||
Columns.Status;
|
||||
|
||||
public static Columns get_dirty_cells(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) {
|
||||
public static Columns get_dirty_cells(OrderSecurity oldval, OrderSecurity newval) {
|
||||
var dirty = (Columns)0;
|
||||
if (oldval.security != newval.security)
|
||||
dirty |= Columns.Security;
|
||||
@@ -404,8 +422,11 @@ namespace mbts.spark {
|
||||
dirty |= Columns.Status;
|
||||
if (oldval.order.last_msg() != newval.order.last_msg())
|
||||
dirty |= Columns.LastMessage;
|
||||
if (oldval.piq != newval.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,11 @@ using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
@@ -19,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;
|
||||
@@ -32,21 +31,24 @@ 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 DelegateProperty<bool> _show_order_params, _show_ladder, _show_fills, _can_attach_selected_orders, _can_detach_selected_orders;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurityAlgoMeta, uint> _row_collection;
|
||||
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsWindowFactory.StartupData.MiniGridSettings, FillsWindowFactory.StartupData.MiniGridSettings);
|
||||
readonly SelectedOrderParamsMon _selected_params_mon;
|
||||
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
|
||||
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
|
||||
@@ -57,16 +59,17 @@ namespace mbts.spark {
|
||||
bool
|
||||
_show_right_panel = OrderBookStartupData.Default.show_right_panel,
|
||||
_is_attaching_orders,
|
||||
_is_row_collection_populated;
|
||||
IReadOnlyDictionary<uint, OrderSecurityAlgoMeta> _cached_orders = DictionaryHelper.empty<uint, OrderSecurityAlgoMeta>();
|
||||
_is_row_collection_populated,
|
||||
_all_rows_selected;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cached_orders = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
IReadOnlyDictionary<uint, bool> _cached_attachable = DictionaryHelper.empty<uint, bool>(), _cached_detachable = DictionaryHelper.empty<uint, bool>();
|
||||
HashSet<uint>
|
||||
_attachable_selected_orders = new HashSet<uint>(),
|
||||
_detachable_selected_orders = new HashSet<uint>();
|
||||
FilterData _cached_filter_data;
|
||||
Dictionary<uint, IDisposable> _on_updated_single_callback_gatekeeper = new Dictionary<uint, IDisposable>();
|
||||
|
||||
public OrderBookVM(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
@@ -75,7 +78,8 @@ namespace mbts.spark {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
|
||||
_selected_params_mon = new OrderParamsAggregator(model.dynamic_helper, _selection_change_throttle);
|
||||
ladder_vm = new LadderVMContainer(bus, model);
|
||||
order_mod_vm = new OrderParamsVM(this, AlgoDefinitionVM.create(model, OrderDataHelper.Defaults.AlgoData(), editing_permissions: AlgoEditingPermissions.AllowEditValues));
|
||||
order_mod_vm.algo_vm.editing_permissions_mask = AlgoEditingPermissions.None;
|
||||
order_mod_vm.algo_vm.algo_root = null;
|
||||
@@ -98,8 +102,18 @@ namespace mbts.spark {
|
||||
_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, OrderSecurityAlgoMeta, uint>(
|
||||
o => new OrderBookRowVM(this, o, rows_are_initially_expanded) {
|
||||
_row_collection = new OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, 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.Key, out bool ia) && ia,
|
||||
is_detachable = _cached_detachable.TryGetValue(o.order.Key, out bool id)
|
||||
},
|
||||
@@ -126,27 +140,28 @@ 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;
|
||||
end_attaching += on_end_attaching;
|
||||
// switch between orders and/or orders w/ piq
|
||||
var orders_source = OrdersModel.piq_enabled ? model.on_order_tuples_with_root_and_piq : model.on_order_tuples_with_root;
|
||||
concat_disposable(
|
||||
connect_order_mod_vm(),
|
||||
_can_attach_selected_orders.observe_current().Subscribe(send_attached_orders_async_cmd, begin_attach_cmd),
|
||||
@@ -163,12 +178,17 @@ 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));
|
||||
}),
|
||||
model.on_order_tuples_with_root.OnDispatcher().Subscribe(t => {
|
||||
orders_source.OnDispatcher().Subscribe(t => {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
|
||||
}),
|
||||
@@ -204,7 +224,6 @@ namespace mbts.spark {
|
||||
order_mod_vm.algo_vm.connect_data(Observable.Return(model)),
|
||||
order_mod_vm.algo_vm.connect_data(_selected_params_mon.subscribe_spreader_security()),
|
||||
order_mod_vm.algo_vm,
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(_selected_params_mon.on_security_md_map),
|
||||
attach_vm,
|
||||
_show_fills.observe_current(true)
|
||||
.Subscribe(is_visible => {
|
||||
@@ -312,8 +331,8 @@ namespace mbts.spark {
|
||||
|
||||
IDisposable connect_order_mod_vm() =>
|
||||
Disposable.Create(
|
||||
_selected_params_mon.subscribe_algos().Subscribe(order_mod_vm.algo_vm.load_many),
|
||||
order_mod_vm.subscribe_is_dirty().Subscribe(_ => {
|
||||
_selected_params_mon.subscribe_algos().OnDispatcher().Subscribe(order_mod_vm.algo_vm.load_many),
|
||||
order_mod_vm.subscribe_is_dirty().OnDispatcher().Subscribe(_ => {
|
||||
modify_selected_orders_async_cmd.RaiseCanExecuteChanged();
|
||||
revert_selected_orders_cmd.RaiseCanExecuteChanged();
|
||||
}),
|
||||
@@ -321,10 +340,10 @@ namespace mbts.spark {
|
||||
|
||||
IDisposable connect_common_selected_params(OrderParamsVM vm) =>
|
||||
Disposable.Create(
|
||||
_selected_params_mon.subscribe_security_md().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_params_mon.subscribe_price().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().Subscribe(newval => vm.selected_account = newval));
|
||||
_selected_params_mon.subscribe_security().OnDispatcher().to_security_md(bus).OnDispatcher().Subscribe(newval => vm.security_md = newval),
|
||||
_selected_params_mon.subscribe_price().OnDispatcher().Subscribe(vm.on_common_price),
|
||||
_selected_params_mon.subscribe_size().OnDispatcher().Subscribe(vm.on_common_size),
|
||||
_selected_params_mon.subscribe_account().OnDispatcher().Subscribe(newval => vm.selected_account = newval));
|
||||
}
|
||||
|
||||
private void _row_collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
@@ -333,6 +352,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;
|
||||
@@ -418,6 +438,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);
|
||||
@@ -425,23 +453,60 @@ 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);
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_attachable_selected_orders.Remove(row_vm.order_key);
|
||||
_detachable_selected_orders.Remove(row_vm.order_key);
|
||||
}
|
||||
}
|
||||
if (e.AddedItems != null) {
|
||||
foreach (var row_vm in e.AddedItems.OfType<OrderBookRowVM>()) {
|
||||
var root = _get_root(row_vm.order_tuple);
|
||||
row_vm.is_selected = true;
|
||||
_selected_params_mon.on_row_selected(row_vm);
|
||||
_selected_security_mon.add(row_vm.security);
|
||||
if (!_on_updated_single_callback_gatekeeper.ContainsKey(row_vm.order_key)) {
|
||||
row_vm.updated += on_updated;
|
||||
_on_updated_single_callback_gatekeeper.Add(row_vm.order_key, Disposable.Create(() => row_vm.updated -= on_updated));
|
||||
}
|
||||
_selected_params_mon.Add(root);
|
||||
if (row_vm.is_attachable)
|
||||
_attachable_selected_orders.Add(row_vm.order_key);
|
||||
if (row_vm.is_detachable)
|
||||
_detachable_selected_orders.Add(row_vm.order_key);
|
||||
void on_updated((OrderSecurity oldval, OrderSecurity newval) change) {
|
||||
if (!row_vm.is_selected) {
|
||||
// self destruct
|
||||
if (_on_updated_single_callback_gatekeeper.TryGetValue(row_vm.order_key, out var unsubscribe_updated_disposable)) {
|
||||
unsubscribe_updated_disposable.Dispose();
|
||||
_on_updated_single_callback_gatekeeper.Remove(row_vm.order_key);
|
||||
}
|
||||
_selected_params_mon.Remove(_get_root(row_vm.order_tuple));
|
||||
_selected_security_mon.remove(row_vm.security);
|
||||
return;
|
||||
}
|
||||
var new_root = _get_root(change.newval);
|
||||
if (!new_root.Equals(root)) {
|
||||
using (_selected_params_mon.defer_updates()) {
|
||||
// unselect and select in one smooth motion via on_row_updated
|
||||
// because we thought there may be gremlins afoot when doing
|
||||
// on_row_unselected and then on_row_selected
|
||||
_selected_params_mon.Remove(root);
|
||||
_selected_params_mon.Add(new_root);
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
if (change.oldval.security?.Id != change.newval.security?.Id) {
|
||||
using (_selected_security_mon.defer_updates()) {
|
||||
_selected_security_mon.remove(change.oldval.security);
|
||||
_selected_security_mon.add(change.newval.security);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
@@ -519,11 +584,11 @@ namespace mbts.spark {
|
||||
if (!new_sort.Equals(old_sort)) {
|
||||
/// Both filter and sort have changed, use <see cref="OperableHierarchicalCollection{TRow, T, TKey}.defer_refresh"/> to avoid syncing twice.
|
||||
using (_row_collection.defer_refresh()) {
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
}
|
||||
} else {
|
||||
_row_collection.filter = new_filter.build_predicate();
|
||||
_row_collection.filter = new_filter.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
} else if (!new_sort.Equals(old_sort))
|
||||
_row_collection.comparer = new_sort.comparer;
|
||||
@@ -543,35 +608,28 @@ namespace mbts.spark {
|
||||
|
||||
public void begin_attach_algos() => is_attaching_orders = true;
|
||||
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurityAlgoMeta 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 OrderSecurityAlgoMeta 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_type);
|
||||
|
||||
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order.actual_order_type());
|
||||
|
||||
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));
|
||||
@@ -590,11 +648,17 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
OrderSecurity _get_root(OrderSecurity os) {
|
||||
while (os.order.ParentKey != 0 && _cached_orders.TryGetValue(os.order.ParentKey, out var pos))
|
||||
os = pos;
|
||||
return os;
|
||||
}
|
||||
|
||||
void _on_filter_changed() {
|
||||
var fd = new FilterData(grid_settings_manager.algo_tree_display_mode, _columns.create_filter_states());
|
||||
if (!fd.Equals(_cached_filter_data)) {
|
||||
_cached_filter_data = fd;
|
||||
_row_collection.filter = fd.build_predicate();
|
||||
_row_collection.filter = fd.build_predicate(_model.dynamic_helper.dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,6 +674,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;
|
||||
@@ -622,17 +687,17 @@ namespace mbts.spark {
|
||||
// their values will be cleared (in the param grid gui), and the modifications will not be applied.
|
||||
// FYI this behavior is implemented to support 'partial edits.' (e.g., changing the 'price' for multiple orders that do not have the same 'size').
|
||||
// ----
|
||||
var update_params = selected_rows.Select(update_params_factory).ToList();
|
||||
var update_params = selected_rows.Select(r => update_params_factory(_get_root(r.order_tuple))).ToList();
|
||||
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
|
||||
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderSecurity order_tuple) {
|
||||
var mod_algo = order_tuple.order.Def.CustomDef.CloneToMutable();
|
||||
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
|
||||
} else {
|
||||
mod_algo_vm.assign_to(mod_algo);
|
||||
}
|
||||
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
|
||||
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
return (order_tuple.order, OrderDataHelper.EqualsHelpers.equals(order_tuple.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
}
|
||||
} else await Task.WhenAll(selected_rows.Select(r => _model.update_order(r.order, price)));
|
||||
}
|
||||
@@ -675,52 +740,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
static IObservable<ISet<uint>> _subscribe_selected_order_keys(ObservableCollection<OrderBookRowVM> selected_rows) =>
|
||||
Observable.Create<ISet<uint>>(o => {
|
||||
bool waiting_for_mouse_up = false;
|
||||
var sub_container = new DisposableContainer();
|
||||
var builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
var selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
return Disposable.Create(selected_rows.Subscribe(on_selection_changed), sub_container);
|
||||
void on_selection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
if (waiting_for_mouse_up)
|
||||
return;
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (OrderBookRowVM row in e.NewItems)
|
||||
selected_keys = selected_keys.Add(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (OrderBookRowVM row in e.OldItems)
|
||||
selected_keys = selected_keys.Remove(row.order_key);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
selected_keys = ImmutableHashSet<uint>.Empty;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
waiting_for_mouse_up = true;
|
||||
wait_for_mouse_up();
|
||||
async void wait_for_mouse_up() {
|
||||
await Wait.until_true(() => Mouse.LeftButton == MouseButtonState.Released, TimeSpan.FromSeconds(60), 333);
|
||||
builder = ImmutableHashSet<uint>.Empty.ToBuilder();
|
||||
foreach (var row in selected_rows)
|
||||
builder.Add(row.order_key);
|
||||
selected_keys = builder.ToImmutable();
|
||||
o.OnNext(selected_keys);
|
||||
waiting_for_mouse_up = false;
|
||||
}
|
||||
} else {
|
||||
o.OnNext(selected_keys);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public class OrderParamsVM : DisposableViewModelBase {
|
||||
readonly SelectorProperty<AccountTuple, AccountTuple?> _accounts;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -731,8 +750,6 @@ namespace mbts.spark {
|
||||
AlgoParamPermissions
|
||||
_price_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write,
|
||||
_size_permissions = AlgoParamPermissions.ReadOnly | AlgoParamPermissions.Write;
|
||||
HashSet<uint> _selected_order_tree = new HashSet<uint>();
|
||||
LadderVM _ladder_vm;
|
||||
SecurityMD? _security_md;
|
||||
|
||||
public OrderParamsVM(OrderBookVM owner)
|
||||
@@ -783,13 +800,7 @@ namespace mbts.spark {
|
||||
appended_param_rows.insert_front(new AlgoParamRowVM(root, _price_param, null, AlgoParamTemplateKeys.Price));
|
||||
}
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join),
|
||||
owner._subscribe_throttled_selected_rows().OnDispatcher().Subscribe(_ => {
|
||||
_selected_order_tree = new HashSet<uint>(_owner.selected_hierarchical_rows.Select(r => r.order_key));
|
||||
_reset_ladder_vm();
|
||||
})) {
|
||||
() => Disposable.dispose(ref _ladder_vm)
|
||||
};
|
||||
ObservableAutoWrapper.connect_item_hooks(param_rows, dirty_params_mon.join));
|
||||
// payup methods
|
||||
void payup_bid(int ticks) => payup(bbo.bid, -ticks);
|
||||
void payup_ask(int ticks) => payup(bbo.ask, ticks);
|
||||
@@ -817,8 +828,7 @@ namespace mbts.spark {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (Set(ref _security_md, value)) {
|
||||
_reset_ladder_vm();
|
||||
RaisePropertyChanged(nameof(security), nameof(spec), nameof(ladder_vm));
|
||||
RaisePropertyChanged(nameof(security), nameof(spec));
|
||||
bbo.security_md = value;
|
||||
_payup_bid_cmd.RaiseCanExecuteChanged();
|
||||
_payup_ask_cmd.RaiseCanExecuteChanged();
|
||||
@@ -842,7 +852,6 @@ namespace mbts.spark {
|
||||
}
|
||||
public AlgoDefinitionVM algo_vm { get; }
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> param_rows { get; }
|
||||
public LadderVM ladder_vm => _ladder_vm;
|
||||
public BBOVM bbo { get; }
|
||||
public ICommand send_order { get; }
|
||||
public ICommand payup_bid_cmd => _payup_bid_cmd;
|
||||
@@ -877,18 +886,6 @@ namespace mbts.spark {
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
|
||||
|
||||
void _reset_ladder_vm() {
|
||||
if (_ladder_vm == null && !_security_md.HasValue)
|
||||
return;
|
||||
Disposable.replace(ref _ladder_vm,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(
|
||||
_owner._bus,
|
||||
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
|
||||
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;
|
||||
@@ -907,8 +904,8 @@ namespace mbts.spark {
|
||||
this.column_filters = column_filters;
|
||||
}
|
||||
|
||||
public Func<OrderSecurityAlgoMeta, bool> build_predicate() {
|
||||
var pred = column_filters.build_predicate();
|
||||
public Func<OrderSecurity, bool> build_predicate(IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pred = column_filters.build_predicate(dynamic_type_index);
|
||||
if (algo_tree_display_mode == AlgoTreeDisplayMode.RootAndLeafNodes) {
|
||||
if (pred == null) {
|
||||
pred = tree_disp_pred;
|
||||
@@ -916,7 +913,7 @@ namespace mbts.spark {
|
||||
var col_pred = pred;
|
||||
pred = o => tree_disp_pred(o) && col_pred(o);
|
||||
}
|
||||
bool tree_disp_pred(OrderSecurityAlgoMeta o) =>
|
||||
bool tree_disp_pred(OrderSecurity o) =>
|
||||
!o.order.has_parent() ||
|
||||
o.order.is_exchange_order() ||
|
||||
o.order.order_type() == CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
@@ -935,132 +932,6 @@ namespace mbts.spark {
|
||||
EqualityComparer.aggregate_hash((int)algo_tree_display_mode, column_filters?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class SelectedOrderParamsMon {
|
||||
static readonly TimeSpan _throttle = TimeSpan.FromSeconds(0.1);
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurityAlgoMeta>();
|
||||
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((OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval) ch) {
|
||||
using (begin_udpate()) {
|
||||
_group_mon.remove(ch.oldval);
|
||||
_group_mon.add(ch.newval);
|
||||
}
|
||||
}
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurityAlgoMeta osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurityAlgoMeta 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(OrderSecurityAlgoMeta 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;
|
||||
@@ -1123,11 +994,13 @@ namespace mbts.spark {
|
||||
var join = config.Configuration.get<bts.config.JoinAttachUIOptions>();
|
||||
var lean = config.Configuration.get<bts.config.LeanAttachUIOptions>();
|
||||
var top = config.Configuration.get<bts.config.TopAttachUIOptions>();
|
||||
var offset = config.Configuration.get<bts.config.OffsetAttachUIOptions>();
|
||||
_algo_type_conf = new Dictionary<CustomOrderDefinition.CustomOneofCase, (bool, bool)> {
|
||||
{ CustomOrderDefinition.CustomOneofCase.Cover, (cover.show_price, cover.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Join, (join.show_price, join.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Lean, (lean.show_price, lean.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) }
|
||||
{ CustomOrderDefinition.CustomOneofCase.Top, (top.show_price, top.show_quantity) },
|
||||
{ CustomOrderDefinition.CustomOneofCase.Offset, (offset.show_price, offset.show_quantity) }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
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;
|
||||
@@ -16,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 {
|
||||
@@ -64,11 +63,11 @@ 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);
|
||||
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
var toggle_show_right_panel_command = new SparkCommandCommand(
|
||||
get_toggle_show_right_panel_cmd_title(),
|
||||
@@ -160,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,
|
||||
@@ -170,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,4 +1,5 @@
|
||||
using mbts.ui;
|
||||
using bts.shell.fills;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System.Collections.Generic;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
@@ -9,11 +10,11 @@ namespace bts.shell.orderbook {
|
||||
[ProtoMember(1)]
|
||||
public GridSettings general { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public FillsWindowFactory.FilterStates filters { get; set; }
|
||||
public FillsFilterStates filters { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public List<SortingCriteria> sorting_criterion { get; set; }
|
||||
|
||||
public FillsWindowFactory.StartupData to_startup_data() =>
|
||||
new FillsWindowFactory.StartupData(null, sorting_criterion, filters, general);
|
||||
public FillsSettings to_startup_data() =>
|
||||
new FillsSettings(null, sorting_criterion, filters, general);
|
||||
}
|
||||
}
|
||||
@@ -1,201 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderSecurityAlgoMeta, bool> build_predicate(this FilterStates fs) {
|
||||
var predicates = new List<Func<OrderSecurityAlgoMeta, bool>>();
|
||||
// security
|
||||
if (fs.security_criteria?.is_defined() ?? false)
|
||||
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
|
||||
// regex
|
||||
add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
add_time_filter(fs.time_created, o => o.order.created());
|
||||
add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
add_discrete_values(fs.users, o => o.order.user());
|
||||
add_discrete_values(fs.accounts, o => o.order.account());
|
||||
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
add_discrete_values(fs.sides, o => o.order.side());
|
||||
add_discrete_values(fs.statuses, o => o.order.status());
|
||||
add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
add_numeric(fs.qty, o => o.order.quantity());
|
||||
add_numeric(fs.price, o => o.order.price());
|
||||
add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
add_numeric(fs.last_price, o => o.order.last_price());
|
||||
return predicates.Count == 0 ? null : new Func<OrderSecurityAlgoMeta, bool>(o => predicates.All(p => p(o)));
|
||||
// local methods (impl)
|
||||
void add_regex(string str, Func<OrderSecurityAlgoMeta, string> selector) {
|
||||
var regex = RegexFilterUtils.build_regex(str);
|
||||
if (regex == null)
|
||||
return;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => regex.IsMatch(selector(o) ?? string.Empty);
|
||||
}
|
||||
void add_time_filter(TimeFilterTuple t, Func<OrderSecurityAlgoMeta, DateTime> selector) {
|
||||
if (!t.is_active)
|
||||
return;
|
||||
var dt_pred = TimeFilterUtils.build_predicate(t);
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => dt_pred(selector(o));
|
||||
}
|
||||
void add_discrete_values<T>(T[] values, Func<OrderSecurityAlgoMeta, T> selector) {
|
||||
if (values.is_null_or_empty())
|
||||
return;
|
||||
if (values.Length < 16) {
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => values.Contains(selector(o));
|
||||
} else {
|
||||
var set = new HashSet<T>(values);
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => set.Contains(selector(o));
|
||||
}
|
||||
}
|
||||
void add_numeric<T>(IMinMaxState<T> value, Func<OrderSecurityAlgoMeta, T> selector) where T : struct, IComparable<T> {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) {
|
||||
var v = selector(o);
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
predicates.Add(impl);
|
||||
bool impl(OrderSecurityAlgoMeta o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
var pb = new ColumnPredicateBuilder<OrderSecurity>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key, dynamic_type_index));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -1,220 +1,244 @@
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
orderbook_grid = new OrderBookGridSettings(grid.clone(), _predicate_animations);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsWindowFactory.StartupData.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criterion = default_fills.sorting_criterion.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsWindowFactory.StartupData.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criterion = default_fills.sorting_criterion.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
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 IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
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) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
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 {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return piq;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
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 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 {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -49,7 +49,9 @@ namespace mbts.spark {
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
|
||||
@@ -548,13 +548,13 @@
|
||||
Grid.Column="1"
|
||||
Grid.Row="3"
|
||||
Panel.ZIndex="1"
|
||||
Width="{StaticResource GridSplitterWidth}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
|
||||
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"
|
||||
|
||||
@@ -19,12 +19,12 @@ using static mbts.ui.controls.DIYGrid;
|
||||
namespace bts.shell {
|
||||
internal class PAndLWindowFactory : ShellWindowFactory<PAndLWindowFactory.PAndLStartupData> {
|
||||
readonly IBus _bus;
|
||||
readonly PAndLModel _model;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, PAndLModel model) : base(owner, "SparkPAndL") {
|
||||
public PAndLWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model) : base(owner, "SparkPAndL") {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_orders_model = orders_model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace bts.shell {
|
||||
state.grid,
|
||||
get: us => us.pnl_grid,
|
||||
set: (us, gs) => us.with(provided_pnl_gridsettings: gs));
|
||||
return new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
|
||||
return new PAndLWindow(_orders_model, this, _bus, state, SparkCommands.ShowPAndL.icon, settings_manager);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
@@ -19,6 +21,7 @@ namespace bts.shell {
|
||||
FillsWindowFactory _fwf;
|
||||
PAndLWindowFactory _plwf;
|
||||
AlgoStudioWindowFactory _aswf;
|
||||
ManualFillsWindowFactory _mfwf;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TradingShellModule(
|
||||
@@ -27,15 +30,15 @@ namespace bts.shell {
|
||||
INotificationService notification_service,
|
||||
INotificationServiceDataCache notification_service_data_cache,
|
||||
UserSettingsHub settings_hub,
|
||||
OrdersModel orders_model,
|
||||
PAndLModel pandl_model,
|
||||
OrdersModel orders_model,
|
||||
md.MBOService mbo,
|
||||
ErrorReporter error_reporter) {
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
|
||||
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
|
||||
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
|
||||
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Trades;
|
||||
@@ -46,11 +49,12 @@ namespace bts.shell {
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new [] {
|
||||
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsWindowFactory.StartupData()),
|
||||
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
|
||||
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
|
||||
SparkCommands.PickLadder,
|
||||
SparkCommands.PickOrderTicket,
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData())
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
|
||||
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
|
||||
};
|
||||
|
||||
IEnumerable<IShellWindowFactory> window_factories() {
|
||||
@@ -59,6 +63,7 @@ namespace bts.shell {
|
||||
yield return _fwf;
|
||||
yield return _plwf;
|
||||
yield return _aswf;
|
||||
yield return _mfwf;
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) =>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,16 +7,17 @@ using static bts.api.orderman.Fill.Types;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillTuple : IEquatable<FillTuple> {
|
||||
public static readonly Func<FillTuple, string> key_fn = ft => ft.fill.FillKey;
|
||||
public readonly FillImmutable fill;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoData? algo;
|
||||
public readonly AlgoDefinitionMetadata? algo_meta;
|
||||
|
||||
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurityAlgoMeta? order_tuple) {
|
||||
public FillTuple((SecurityImmutable? sec, FillImmutable val) fill, OrderSecurity? order_tuple) {
|
||||
this.fill = fill.val;
|
||||
this.security = fill.sec;
|
||||
if (order_tuple is OrderSecurityAlgoMeta osam) {
|
||||
if (order_tuple is OrderSecurity osam) {
|
||||
order = osam.order;
|
||||
algo = osam.algo;
|
||||
algo_meta = osam.meta;
|
||||
@@ -45,4 +46,24 @@ namespace mbts.spark {
|
||||
order.HasValue ? order.Value.Key.GetHashCode() : 0,
|
||||
security.HasValue ? security.Value.Id.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
static class FillTupleExtensions {
|
||||
public static DateTime fill_time(this FillTuple fill_tuple) =>
|
||||
fill_tuple.fill.Timestamp.to_local_time();
|
||||
|
||||
public static TimeInForce time_in_force(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.time_in_force() : TimeInForce.Undefined;
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.order_type() : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static Venue exchange(this FillTuple fill_tuple) =>
|
||||
fill_tuple.security.HasValue ? fill_tuple.security.Value.Exchange : Venue.Unset;
|
||||
|
||||
public static uint order_key(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.Key : 0u;
|
||||
|
||||
public static string user(this FillTuple fill_tuple) =>
|
||||
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark.utilities {
|
||||
public OrderFactory(AdminModel admin_model, DynamicOrderHelper dynamic_helper) {
|
||||
_admin_model = admin_model;
|
||||
_dynamic_helper = dynamic_helper;
|
||||
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase,string>.ValueEqualityComparer);
|
||||
var algo_name_index = new HashSet<OneOf<CustomOrderDefinition.CustomOneofCase, string>>(OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
|
||||
// add standard types
|
||||
algo_name_index.AddRange(
|
||||
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None)
|
||||
@@ -58,7 +58,7 @@ namespace mbts.spark.utilities {
|
||||
algo_name_index.AddRange(dynamic_helper.dynamic_types
|
||||
.Select(dt => new OneOf<CustomOrderDefinition.CustomOneofCase, string>(dt)));
|
||||
// build abbreviation index
|
||||
_algo_abbreviation_index = DisplayNameHelper.get_word_tree_abbreviations(algo_name_index, oneof => oneof.value.ToString(), OneOf<CustomOrderDefinition.CustomOneofCase,string>.ValueEqualityComparer);
|
||||
_algo_abbreviation_index = DisplayNameHelper.get_word_tree_abbreviations(algo_name_index, oneof => oneof.value.ToString(), OneOf<CustomOrderDefinition.CustomOneofCase, string>.ValueEqualityComparer);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> subscribe_accounts() => _admin_model.on_accounts;
|
||||
@@ -70,7 +70,7 @@ namespace mbts.spark.utilities {
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type() =>
|
||||
CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child=false) {
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable security, bool is_child = false) {
|
||||
if (is_child) return CustomOrderDefinition.CustomOneofCase.Limit;
|
||||
switch (security.ContractCase) {
|
||||
case Security.ContractOneofCase.Synthetic: return CustomOrderDefinition.CustomOneofCase.Spreader;
|
||||
@@ -78,7 +78,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public CustomOrderDefinition.CustomOneofCase default_order_type(SecurityImmutable? security, bool is_child=false) =>
|
||||
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() =>
|
||||
@@ -103,13 +103,13 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child=false) =>
|
||||
public IEnumerable<CustomOrderDefinition.CustomOneofCase> default_order_types(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_order_types(security.Value, is_child) : default_order_types();
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces() =>
|
||||
EnumHelper.get_values_except(TimeInForce.Undefined);
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child=false) =>
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, bool is_child = false) =>
|
||||
default_time_in_forces();
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable security, CustomOrderDefinition.CustomOneofCase order_type, bool is_child = false) {
|
||||
@@ -120,7 +120,7 @@ namespace mbts.spark.utilities {
|
||||
return tifs;
|
||||
}
|
||||
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child=false) =>
|
||||
public IEnumerable<TimeInForce> default_time_in_forces(SecurityImmutable? security, bool is_child = false) =>
|
||||
security.HasValue ? default_time_in_forces(security.Value, is_child) : default_time_in_forces();
|
||||
|
||||
public LadderSettings default_ladder_settings(SecurityImmutable security) {
|
||||
@@ -168,17 +168,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;
|
||||
}
|
||||
|
||||
@@ -201,6 +198,7 @@ namespace mbts.spark.utilities {
|
||||
public LimitOrderDefinition limit_order() => OrderDataHelper.Defaults.LimitOrderDefinition();
|
||||
public StopOrderDefinition stop_order() => OrderDataHelper.Defaults.StopOrderDefinition();
|
||||
public StopLimitOrderDefinition stop_limit_order() => OrderDataHelper.Defaults.StopLimitOrderDefinition();
|
||||
public GLBXIcebergOrderDefinition glbx_iceberg_order() => OrderDataHelper.Defaults.GLBXIcebergOrderDefinition();
|
||||
public IcebergOrderDefinition iceberg_order() => OrderDataHelper.Defaults.IcebergOrderDefinition();
|
||||
public TimeBoxOrderDefinition time_box_order() => OrderDataHelper.Defaults.TimeBoxOrderDefinition();
|
||||
public TickOrderDefinition tick_order() => OrderDataHelper.Defaults.TickOrderDefinition();
|
||||
@@ -226,14 +224,15 @@ namespace mbts.spark.utilities {
|
||||
|
||||
public CustomOrderDefinition custom_order(CustomOrderDefinition.CustomOneofCase order_type) {
|
||||
var rv = new CustomOrderDefinition();
|
||||
switch(order_type) {
|
||||
switch (order_type) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Market: rv.Market = market_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: rv.Limit = limit_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = stop_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = stop_limit_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = glbx_iceberg_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = iceberg_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = time_box_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = tick_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = lean_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: rv.Join = join_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top: rv.Top = top_order(); break;
|
||||
@@ -244,7 +243,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: rv.Trigger = trigger_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: rv.Spreader = spreader_order(); break;
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: rv.Dynamic = dynamic_order(); break;
|
||||
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
|
||||
//case CustomOrderDefinition.CustomOneofCase.QueueHolder: rv.QueueHolder = queue_holder_order(); break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -257,6 +256,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return new OrderMetadata { KeyBinding = "GB", Color = BTSColors.Orders.glbx_iceberg_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
|
||||
@@ -266,7 +266,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.cover_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Oco: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.oco_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
|
||||
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.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
|
||||
@@ -418,6 +418,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: return new OrderMetadata { KeyBinding = "L", Color = BTSColors.green.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: return new OrderMetadata { KeyBinding = "S", Color = BTSColors.red_orange.rgb_color(), NoProps = true };
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return new OrderMetadata { KeyBinding = "SL", Color = BTSColors.pale_green.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return new OrderMetadata { KeyBinding = "GB", Color = BTSColors.Orders.glbx_iceberg_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return new OrderMetadata { KeyBinding = "I", Color = BTSColors.pale_blue.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: return new OrderMetadata { KeyBinding = "TB", Color = BTSColors.purple.rgb_color() };
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.tick_order };
|
||||
@@ -427,7 +428,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.cover_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Oco: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.oco_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
|
||||
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.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
|
||||
@@ -440,6 +441,7 @@ namespace mbts.spark.utilities {
|
||||
switch (algo_type) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit: return "LM";
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return "CV";
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return "GB";
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return "IB";
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: return "LN";
|
||||
case CustomOrderDefinition.CustomOneofCase.Market: return "MA";
|
||||
@@ -511,7 +513,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:
|
||||
@@ -537,7 +539,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];
|
||||
@@ -594,11 +597,13 @@ namespace mbts.spark.utilities {
|
||||
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
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset
|
||||
},
|
||||
// The following spreader types are not yet ready for production use:
|
||||
// CustomOrderDefinition.CustomOneofCase.Dynamic,
|
||||
@@ -615,7 +620,15 @@ namespace mbts.spark.utilities {
|
||||
CustomOrderDefinition.CustomOneofCase.Lean,
|
||||
CustomOrderDefinition.CustomOneofCase.Join,
|
||||
CustomOrderDefinition.CustomOneofCase.Cover,
|
||||
CustomOrderDefinition.CustomOneofCase.Top
|
||||
CustomOrderDefinition.CustomOneofCase.Top,
|
||||
CustomOrderDefinition.CustomOneofCase.Offset
|
||||
},
|
||||
outright_order_types = new[] {
|
||||
CustomOrderDefinition.CustomOneofCase.Market,
|
||||
CustomOrderDefinition.CustomOneofCase.Limit,
|
||||
CustomOrderDefinition.CustomOneofCase.Stop,
|
||||
CustomOrderDefinition.CustomOneofCase.StopLimit,
|
||||
CustomOrderDefinition.CustomOneofCase.GlbxIceberg,
|
||||
};
|
||||
public static readonly CustomOrderDefinition.CustomOneofCase[] all_order_types = exchange_order_types.Union(spreader_order_types).ToArray();
|
||||
|
||||
@@ -636,12 +649,12 @@ namespace mbts.spark.utilities {
|
||||
var tgt_child = cad.get_child_from_impl(tgt_type_def);
|
||||
copy_values(src_child, ref tgt_child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase get_order_type(IMessage order_type_def) =>
|
||||
order_type_def != null && _algo_type_case_index.TryGetValue(order_type_def.Descriptor.ClrType, out CustomOrderDefinition.CustomOneofCase algo_case) ?
|
||||
algo_case : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
|
||||
#region Private Members
|
||||
// get child
|
||||
static CustomOrderDefinitionImmutable? _get_iceberg_child(IcebergOrderDefinitionImmutable iceberg) => iceberg.HasChild ? iceberg.Child : (CustomOrderDefinitionImmutable?)null;
|
||||
@@ -704,7 +717,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) &&
|
||||
@@ -857,7 +870,7 @@ namespace mbts.spark.utilities {
|
||||
int phase_mult = 1;
|
||||
switch (def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover:
|
||||
add_component(def.Cover.HedgeTicks.GetHashCode());
|
||||
add_component(def.Cover.HedgeTicks.GetHashCode());
|
||||
add_component(_get_hash(_get_cover_child(def.Cover)));
|
||||
add_component(_get_hash(_get_cover_hedge(def.Cover)));
|
||||
break;
|
||||
@@ -895,7 +908,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;
|
||||
@@ -981,6 +994,7 @@ namespace mbts.spark.utilities {
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop: rv.Stop = StopOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit: rv.StopLimit = StopLimitOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Iceberg: rv.Iceberg = IcebergOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.GlbxIceberg: rv.GlbxIceberg = GLBXIcebergOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.TimeBox: rv.TimeBox = TimeBoxOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Tick: rv.Tick = TickOrderDefinition(); break;
|
||||
case bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Lean: rv.Lean = LeanOrderDefinition(); break;
|
||||
@@ -1004,6 +1018,7 @@ namespace mbts.spark.utilities {
|
||||
public static LimitOrderDefinition LimitOrderDefinition() => new LimitOrderDefinition { };
|
||||
public static StopOrderDefinition StopOrderDefinition() => new StopOrderDefinition { };
|
||||
public static StopLimitOrderDefinition StopLimitOrderDefinition() => new StopLimitOrderDefinition { };
|
||||
public static GLBXIcebergOrderDefinition GLBXIcebergOrderDefinition() => new GLBXIcebergOrderDefinition { MaxShowQuantity = 1 };
|
||||
public static IcebergOrderDefinition IcebergOrderDefinition() => new IcebergOrderDefinition { Child = CustomOrderDefinition(), ShowQuantity = 1 };
|
||||
public static TimeBoxOrderDefinition TimeBoxOrderDefinition() => new TimeBoxOrderDefinition { Child = CustomOrderDefinition(), Start = Timestamp.FromDateTime(DateTime.UtcNow), Stop = Timestamp.FromDateTime(DateTime.UtcNow) };
|
||||
public static TickOrderDefinition TickOrderDefinition() => new TickOrderDefinition { Child = CustomOrderDefinition(), CrossThreshold = QuantityMeasure };
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// Monitors selected rows, and determines whether or not they are all active-orders, with common parameters.
|
||||
/// </summary>
|
||||
class OrderParamsAggregator {
|
||||
readonly TimeSpan _throttle;
|
||||
readonly CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey> _algo_def_mon =
|
||||
new CommonValueGroupMonitor<AlgoSecurityKey, AlgoSecurityKey>(
|
||||
ask => ask,
|
||||
EqualityComparer<AlgoSecurityKey>.Default,
|
||||
EqualityComparer.Create<AlgoSecurityKey, CustomOrderDefinitionImmutable>(ask => ask.algo, OrderDataHelper.EqualsHelpers.custom_order_def_compareri));
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _spreader_security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<SecurityImmutable?> _security_mon = new CommonValueMonitor<SecurityImmutable?>();
|
||||
readonly CommonValueMonitor<double> _price_mon = new CommonValueMonitor<double>(double.NaN);
|
||||
readonly CommonValueMonitor<uint> _size_mon = new CommonValueMonitor<uint>();
|
||||
readonly CommonValueMonitor<AccountTuple> _account_mon = new CommonValueMonitor<AccountTuple>();
|
||||
readonly CommonValueMonitor<bool> _all_active_mon = new CommonValueMonitor<bool>();
|
||||
readonly CommonValueMonitorMultiplexor<OrderSecurity> _group_mon = new CommonValueMonitorMultiplexor<OrderSecurity>();
|
||||
readonly DynamicOrderHelper _dyn_helper;
|
||||
|
||||
public OrderParamsAggregator(DynamicOrderHelper dyn_helper, TimeSpan throttle) {
|
||||
_dyn_helper = dyn_helper;
|
||||
_throttle = throttle;
|
||||
_group_mon.add_member_mon(_algo_def_mon, _algo_key);
|
||||
_group_mon.add_member_mon(_spreader_security_mon, _get_spreader_sec, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_security_mon, osam => osam.security, s => s.HasValue);
|
||||
_group_mon.add_member_mon(_price_mon, osam => osam.order.price());
|
||||
_group_mon.add_member_mon(_size_mon, osam => osam.order.quantity());
|
||||
_group_mon.add_member_mon(_account_mon, osam => new AccountTuple(osam.order.account_id(), osam.order.account()));
|
||||
_group_mon.add_member_mon(_all_active_mon, osam => osam.order.is_active());
|
||||
}
|
||||
|
||||
public SecurityImmutable? security => _security_mon.common_value;
|
||||
public SecurityImmutable? spreader_security => _spreader_security_mon.common_value;
|
||||
public double price => _price_mon.try_get_common_value(out double p) ? p : double.NaN;
|
||||
public uint? size => _size_mon.common_value_nullable();
|
||||
public AccountTuple? account => _account_mon.common_value_nullable();
|
||||
public bool all_active => _all_active_mon.common_value;
|
||||
|
||||
public IObservable<IEnumerable<CustomOrderDefinitionImmutable>> subscribe_algos() => _algo_def_mon.SubscribeThrottled(_throttle, (ask, vals) => vals.Select(v => v.algo), Enumerable.Empty<CustomOrderDefinitionImmutable>());
|
||||
public IObservable<SecurityImmutable?> subscribe_spreader_security() => _spreader_security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<SecurityImmutable?> subscribe_security() => _security_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<double> subscribe_price() => _price_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<uint?> subscribe_size() => _size_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<AccountTuple?> subscribe_account() => _account_mon.SubscribeThrottledNullable(_throttle);
|
||||
public IObservable<bool> subscribe_all_active() => _all_active_mon.SubscribeThrottled(_throttle);
|
||||
public IObservable<Plurality> subscribe_security_plurality() => _security_mon.SubscribePluralityThrottled(_throttle);
|
||||
|
||||
public IDisposable defer_updates() =>
|
||||
_group_mon.defer_updates();
|
||||
|
||||
event Action<TableUpdate<OrderSecurity, uint>> _updated;
|
||||
public void Update(TableUpdate<OrderSecurity, uint> table) {
|
||||
_updated?.Invoke(table);
|
||||
}
|
||||
|
||||
public void Add(OrderSecurity order_tuple) =>
|
||||
_group_mon.add(order_tuple);
|
||||
|
||||
public void Remove(OrderSecurity order_tuple) =>
|
||||
_group_mon.remove(order_tuple);
|
||||
|
||||
static SecurityImmutable? _get_spreader_sec(OrderSecurity osam) =>
|
||||
OrderDataHelper.enumerate_algo_hierarchy(osam.order.Def.CustomDef.CloneToMutable())
|
||||
.Any(algo => algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader) ?
|
||||
osam.security : null;
|
||||
|
||||
AlgoSecurityKey _algo_key(OrderSecurity osam) =>
|
||||
new AlgoSecurityKey(osam.order.Def.CustomDef, _get_spreader_sec(osam), AlgoStructureSerializer.structure_key(osam.order.Def.CustomDef, _dyn_helper.dynamic_type_index));
|
||||
|
||||
struct AlgoSecurityKey {
|
||||
public readonly CustomOrderDefinitionImmutable algo;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly AlgoStructureKey key;
|
||||
|
||||
public AlgoSecurityKey(CustomOrderDefinitionImmutable algo, SecurityImmutable? security, AlgoStructureKey key) {
|
||||
this.algo = algo;
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(AlgoSecurityKey other) =>
|
||||
security == other.security &&
|
||||
key == other.key;
|
||||
|
||||
public override bool Equals(object obj) => obj is AlgoSecurityKey other && Equals(other);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
|
||||
public static bool operator ==(AlgoSecurityKey a, AlgoSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoSecurityKey a, AlgoSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,27 +26,26 @@ using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
class InlineOrderProperties {
|
||||
public readonly InlineUserProperty<AccountTuple> account;
|
||||
public readonly InlineUserProperty<uint> account;
|
||||
public readonly InlineUserProperty<TimeInForce> time_in_force;
|
||||
public readonly InlineUserProperty<CustomOrderDefinition> order_type_def;
|
||||
public readonly InlineUserProperty<AlgoID> algo;
|
||||
|
||||
public InlineOrderProperties(OrdersModel model, LadderSettings settings) : this(
|
||||
new InlineUserProperty<AccountTuple>(),
|
||||
new InlineUserProperty<uint>(settings.default_account, settings.default_account != 0),
|
||||
new InlineUserProperty<TimeInForce>(settings.default_time_in_force, true),
|
||||
new InlineUserProperty<CustomOrderDefinition>(model.factory.custom_order(settings.default_order_type), true)) { }
|
||||
new InlineUserProperty<AlgoID>(settings.default_algo, true)) { }
|
||||
|
||||
public InlineOrderProperties(OrdersModel model, OrderDefinitionImmutable def) : this(
|
||||
new InlineUserProperty<AccountTuple>(),
|
||||
new InlineUserProperty<TimeInForce>(def.TimeInForce, true),
|
||||
new InlineUserProperty<CustomOrderDefinition>(def.CustomDef.CloneToMutable(), true)) { }
|
||||
new InlineUserProperty<uint>(def.AccountId, def.AccountId != 0),
|
||||
new InlineUserProperty<TimeInForce>(def.TimeInForce, true)) { }
|
||||
|
||||
public InlineOrderProperties(
|
||||
InlineUserProperty<AccountTuple> account=null,
|
||||
InlineUserProperty<uint> account=null,
|
||||
InlineUserProperty<TimeInForce> time_in_force=null,
|
||||
InlineUserProperty<CustomOrderDefinition> order_type_def=null) {
|
||||
this.account = account ?? new InlineUserProperty<AccountTuple>();
|
||||
InlineUserProperty<AlgoID> algo = null) {
|
||||
this.account = account ?? new InlineUserProperty<uint>();
|
||||
this.time_in_force = time_in_force ?? new InlineUserProperty<TimeInForce>();
|
||||
this.order_type_def = order_type_def ?? new InlineUserProperty<CustomOrderDefinition>();
|
||||
this.algo = algo ?? new InlineUserProperty<AlgoID>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,14 +101,28 @@ namespace mbts.spark {
|
||||
_favorite_algo_definitions_view.LiveFilteringProperties.Add(nameof(AlgoDefinitionVM.is_favorite));
|
||||
_favorite_algo_definitions_view.IsLiveFiltering = true;
|
||||
favorite_algo_definitions_view.Filter = d => d.is_favorite;
|
||||
//_accounts
|
||||
var inline_account_src = order_properties.AggregateLatest(
|
||||
model.factory.subscribe_accounts(),
|
||||
(last, change, cache) => convert_inline_account(change.New.account, cache),
|
||||
(last, props, change) => convert_inline_account(props.account, change.New, false),
|
||||
() => new InlineUserProperty<AccountTuple>(),
|
||||
new InlineOrderProperties(),
|
||||
DictionaryHelper.empty<uint, string>());
|
||||
// Note that with the .Where on the account_tuples, we will ignore any
|
||||
// updates for which all accounts have been removed.
|
||||
// This was a safer change than changing SelectorProperty's IEnumberable.OnNext
|
||||
// so that it would not reset the selected item on server reboots; i.e. this
|
||||
// change only affects this IObservable.
|
||||
_accounts = backing(
|
||||
nameof(selected_account),
|
||||
model.factory.subscribe_account_tuples(),
|
||||
order_properties.Select(ops => ops.account),
|
||||
model.factory.subscribe_account_tuples().Where(items => items.Any()),
|
||||
inline_account_src,
|
||||
a => new AccountTuple?(a),
|
||||
() => accounts.Cast<AccountTuple?>().FirstOrDefault(),
|
||||
EqualityComparer<AccountTuple?>.Default,
|
||||
new[] { new PropertyValidation<AccountTuple?>(a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account")) }.ToReadOnlyList());
|
||||
// time in force
|
||||
_time_in_force = backing(
|
||||
nameof(selected_time_in_force),
|
||||
// remember, both observables must fire once to start the CombineLatest sequence
|
||||
@@ -164,8 +177,12 @@ namespace mbts.spark {
|
||||
}, out IObserver<bool> revert_algo_params_observer);
|
||||
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
|
||||
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
|
||||
var inline_algo_relay = new SelectionModelInlinePropRelay<AlgoDefinitionVM, AlgoDefinitionVM, AlgoID>(_selected_algo_definition, vm => AlgoID.from_algo_key(vm.key, _model.dynamic_helper.dynamic_type_index));
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
order_properties.OnDispatcher().Subscribe(props => {
|
||||
inline_algo_relay.on_inline_prop(props.algo);
|
||||
}),
|
||||
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
|
||||
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
|
||||
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
|
||||
@@ -226,6 +243,7 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _algo_param_rows_hook);
|
||||
}
|
||||
};
|
||||
// local methods
|
||||
async Task save_template_async(AlgoDefinitionTemplateVM template) {
|
||||
using (_preserve_selection_by_index())
|
||||
await AlgoPersistenceHelper.persist_template(model, algo_definitions, template);
|
||||
@@ -250,6 +268,19 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//
|
||||
// Regarding the 3rd parameter, force_override_from_inline_property:
|
||||
// When using an InlineUserProperty, in the context of assigning a default vs selected item,
|
||||
// there is somewhat of a conflict between the IObserver<InlineUserProperty<TItem>> and the
|
||||
// IObserver<IEnumerable<TItem>> OnNext methods. This 3rd paramter provides yet another override
|
||||
// to say if the force_override should come from the inline property or if the force_override should
|
||||
// be disbled, by way of the immediate return in ViewModelBase._try_auto_select when force_override
|
||||
// is false. Our intent is then accomplished because we are no longer attempting to set a default/
|
||||
// selected item when the account_tuples potentially changes
|
||||
InlineUserProperty<AccountTuple> convert_inline_account(InlineUserProperty<uint> account_id, IReadOnlyDictionary<uint, string> account_map, bool force_override_from_inline_property = true) =>
|
||||
account_id.has_value && account_map.TryGetValue(account_id.value, out string name) ?
|
||||
new InlineUserProperty<AccountTuple>(new AccountTuple(account_id.value, name), force_override_from_inline_property ? account_id.force_override : false) :
|
||||
new InlineUserProperty<AccountTuple>();
|
||||
}
|
||||
|
||||
IDisposable _template_collection_sync;
|
||||
|
||||
@@ -3,13 +3,15 @@ using mbts.spark;
|
||||
using System;
|
||||
|
||||
namespace bts.api.orderman {
|
||||
readonly struct OrderSecurity: IEquatable<OrderSecurity> {
|
||||
readonly struct OrderSecurity : IEquatable<OrderSecurity> {
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
piq = null;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity((SecurityImmutable? sec, OrderRecordImmutable order) t) {
|
||||
security = t.sec;
|
||||
@@ -17,6 +19,8 @@ namespace bts.api.orderman {
|
||||
piq = null;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq) {
|
||||
this.security = sec;
|
||||
@@ -24,6 +28,8 @@ namespace bts.api.orderman {
|
||||
this.piq = piq;
|
||||
algo = null;
|
||||
meta = null;
|
||||
root_algo = new AlgoData(order.Def.CustomDef);
|
||||
root_meta = null;
|
||||
}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta) {
|
||||
this.security = sec;
|
||||
@@ -31,24 +37,55 @@ namespace bts.api.orderman {
|
||||
this.piq = piq;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
root_meta = meta;
|
||||
}
|
||||
//public OrderSecurity(OrderSecurity t) {
|
||||
// security = t.security;
|
||||
// order = t.order;
|
||||
// piq = t.piq;
|
||||
//}
|
||||
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? rootalgo = null, AlgoDefinitionMetadata? rootmeta = null) {
|
||||
this.security = sec;
|
||||
this.order = order;
|
||||
this.piq = piq;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
if (rootalgo.HasValue) {
|
||||
this.root_algo = rootalgo.Value;
|
||||
this.root_meta = rootmeta;
|
||||
} else {
|
||||
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
this.root_meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator OrderSecurity(ValueTuple<SecurityImmutable?, OrderRecordImmutable> x) => new OrderSecurity(x);
|
||||
public SecurityImmutable? security { get; }
|
||||
public OrderRecordImmutable order { get; }
|
||||
public uint? piq { get; }
|
||||
public AlgoData? algo { get; }
|
||||
public AlgoDefinitionMetadata? meta { get; }
|
||||
public AlgoData root_algo { get; }
|
||||
public AlgoDefinitionMetadata? root_meta { get; }
|
||||
|
||||
|
||||
public OrderSecurity with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
|
||||
new OrderSecurity(security, order, piq, algo, meta, root_algo, root_meta);
|
||||
|
||||
public override bool Equals(object obj) => (obj is OrderSecurity os) && Equals(os);
|
||||
public bool Equals(OrderSecurity other) => order.Equals(other.order) && security == other.security && piq == other.piq;
|
||||
public override int GetHashCode() => (security, order, piq).GetHashCode();
|
||||
public bool Equals(OrderSecurity other) =>
|
||||
order == other.order &&
|
||||
security == other.security &&
|
||||
piq == other.piq &&
|
||||
algo == other.algo &&
|
||||
meta == other.meta &&
|
||||
root_algo == other.root_algo;
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
order.GetHashCode(),
|
||||
security?.GetHashCode() ?? 0,
|
||||
piq?.GetHashCode() ?? 0,
|
||||
algo?.GetHashCode() ?? 0,
|
||||
meta?.GetHashCode() ?? 0,
|
||||
root_algo.GetHashCode());
|
||||
|
||||
public static bool operator == (OrderSecurity left, OrderSecurity right) => left.Equals(right);
|
||||
public static bool operator != (OrderSecurity left, OrderSecurity right) => !left.Equals(right);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using bts.api.orderman;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class GLBXIcebergOrderVM : OrderTypeVMBase<GLBXIcebergOrderDefinition, GLBXIcebergOrderDefinitionImmutable> {
|
||||
readonly Property<uint?> _max_show_quantity;
|
||||
|
||||
public GLBXIcebergOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_max_show_quantity = add_nullable_property<uint>(nameof(max_show_quantity), GLBXIcebergOrderDefinition.MaxShowQuantityFieldNumber);
|
||||
}
|
||||
|
||||
public uint? max_show_quantity { get => _max_show_quantity.Value; set => _max_show_quantity.set(value); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ namespace mbts.spark {
|
||||
public static IAlgoVM pricer(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new PricerOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM offset(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new OffsetOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM trigger(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new TriggerOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM glbx_iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new GLBXIcebergOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM iceberg(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new IcebergOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM spreader(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions) => new SpreaderOrderVM(key, display_name, editing_permissions);
|
||||
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
|
||||
@@ -83,6 +84,7 @@ namespace mbts.spark {
|
||||
case AlgoType.Pricer: return pricer(key, name, editing_permissions);
|
||||
case AlgoType.Offset: vm = offset(key, name, editing_permissions); break;
|
||||
case AlgoType.Trigger: vm = trigger(key, name, editing_permissions); break;
|
||||
case AlgoType.GlbxIceberg: vm = glbx_iceberg(key, name, editing_permissions); break;
|
||||
case AlgoType.Iceberg: vm = iceberg(key, name, editing_permissions); break;
|
||||
case AlgoType.Spreader: vm = spreader(key, name, editing_permissions); break;
|
||||
case AlgoType.Dynamic: vm = dynamic(key, name, name, editing_permissions); break;
|
||||
|
||||
@@ -5,13 +5,16 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
|
||||
readonly Property<uint?> _ticks_off;
|
||||
readonly Property<QuantityMeasureImmutable?> _top_threshold;
|
||||
|
||||
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_ticks_off = add_nullable_property<uint>(nameof(ticks_off), TopDef.TicksOffFieldNumber);
|
||||
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
|
||||
}
|
||||
|
||||
public uint? ticks_off { get => _ticks_off.Value; set => _ticks_off.set(value); }
|
||||
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,23 @@ namespace mbts.spark {
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this OrderRecordImmutable order) =>
|
||||
order.HasDef && order.Def.HasCustomDef ? order.Def.CustomDef.CustomCase : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
/// <summary>
|
||||
/// Unwraps all <see cref="CompositeOrderDefinition"/>s (if any) to return the underlying order type.
|
||||
/// </summary>
|
||||
/// <param name="order">The order who's underlying algo will be returned.</param>
|
||||
/// <returns>The first non-composite algo definition in the specified <see cref="CustomOrderDefinition"/>.</returns>
|
||||
public static CustomOrderDefinition.CustomOneofCase actual_order_type(this OrderRecordImmutable order) {
|
||||
if (!order.HasDef || !order.Def.HasCustomDef)
|
||||
return CustomOrderDefinition.CustomOneofCase.None;
|
||||
var cdef = order.Def.CustomDef;
|
||||
while (cdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
if (!cdef.Composite.HasDefinition)
|
||||
return CustomOrderDefinition.CustomOneofCase.None;
|
||||
cdef = cdef.Composite.Definition;
|
||||
}
|
||||
return cdef.CustomCase;
|
||||
}
|
||||
|
||||
public static string dynamic_order_type_display_name(this OrderRecordImmutable order) =>
|
||||
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasDynamic ? order.Def.CustomDef.Dynamic.DynamicOrderType : string.Empty;
|
||||
|
||||
@@ -112,7 +129,8 @@ namespace mbts.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Limit:
|
||||
case CustomOrderDefinition.CustomOneofCase.Market:
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop:
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return true;
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit:
|
||||
case CustomOrderDefinition.CustomOneofCase.GlbxIceberg: return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -138,6 +156,8 @@ namespace mbts.spark {
|
||||
|
||||
public static string display_name(this OrderSecurityAlgoMeta order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
public static string display_name(this OrderSecurity order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
|
||||
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
|
||||
if (algo.HasValue) {
|
||||
|
||||
Loaded 100 of 459 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user