Compare commits
1863
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 | ||
|
|
bced75ea50 | ||
|
|
b48accf1f9 | ||
|
|
3758ac6101 | ||
|
|
ac45e9bdb2 | ||
|
|
769bad89a7 | ||
|
|
7dc272506a | ||
|
|
86c787efaf | ||
|
|
f917f581c3 | ||
|
|
ca1217cd64 | ||
|
|
4ed4e2e5e4 | ||
|
|
bb0171a2e4 | ||
|
|
88bf448e68 | ||
|
|
05a615038d | ||
|
|
51cfd0e5bb | ||
|
|
d2ee575f19 | ||
|
|
0b76ffb4f3 | ||
|
|
0f9b93115a | ||
|
|
8cfc6a7591 | ||
|
|
9de2dd30bc | ||
|
|
38005e3266 | ||
|
|
26ae70f432 | ||
|
|
f6ca8286ea | ||
|
|
ae471b07fa | ||
|
|
e7cea797ba | ||
|
|
f16545dfaa | ||
|
|
dcd62409f1 | ||
|
|
11252b4300 | ||
|
|
90526a8bfd | ||
|
|
e815dade3a | ||
|
|
576be7e961 | ||
|
|
407de320f6 | ||
|
|
bc0234a5e9 | ||
|
|
e1229186de | ||
|
|
a3e00217a1 | ||
|
|
07fa55d317 | ||
|
|
16f0beb3d8 | ||
|
|
d244c62c26 | ||
|
|
44730204a5 | ||
|
|
bd47816d16 | ||
|
|
2f12f2609a | ||
|
|
9c489c03c6 | ||
|
|
5ae8a66558 | ||
|
|
03882718a0 | ||
|
|
0f21d9783b | ||
|
|
2c547f37fb | ||
|
|
a7f4944bd6 | ||
|
|
a3a33ffb6a | ||
|
|
b3db0fc4ca | ||
|
|
0f066fdeb9 | ||
|
|
22f89490a3 | ||
|
|
2137d37cbd | ||
|
|
082a241bb9 | ||
|
|
0057f8f080 | ||
|
|
83650f1e1b | ||
|
|
4768c1cd87 | ||
|
|
57b70684ea | ||
|
|
af64592f52 | ||
|
|
a1b89ccb34 | ||
|
|
44f2a9daf7 | ||
|
|
0e5833b275 | ||
|
|
bded6d334c | ||
|
|
893c6702a2 | ||
|
|
627b8742c4 | ||
|
|
1f7e0a672e | ||
|
|
29e8fd1050 | ||
|
|
6ce741bcaf | ||
|
|
8dd070961a | ||
|
|
f6a3a276d9 | ||
|
|
68c8828d4f | ||
|
|
0d68ebbbea | ||
|
|
cc04e79f8b | ||
|
|
ac76462f7e | ||
|
|
d20b30557f | ||
|
|
a6fa5f9852 | ||
|
|
0db165bf76 | ||
|
|
705fd293ba | ||
|
|
523002ac56 | ||
|
|
9c25a8751e | ||
|
|
89fc99b59a | ||
|
|
cd413d10ab | ||
|
|
1c5cc9c1bb | ||
|
|
7965743a80 | ||
|
|
2172f007a8 | ||
|
|
c9db5d13a9 | ||
|
|
13e911760d | ||
|
|
a12c3128a3 | ||
|
|
6bb84b65a8 | ||
|
|
6fb1140a97 | ||
|
|
0e449844ca | ||
|
|
476206edb0 | ||
|
|
3483ac0712 | ||
|
|
d6e586c4ce | ||
|
|
0f1735fba2 | ||
|
|
e5254dcf7f | ||
|
|
7bd183c8a4 | ||
|
|
aaf09b197b | ||
|
|
6081128b92 | ||
|
|
6d6ecb33ba | ||
|
|
6e85612adc | ||
|
|
f33a13bfc8 | ||
|
|
c4eeac8d0a | ||
|
|
432f39a1fa | ||
|
|
6084da64a2 | ||
|
|
5ae484e6c1 | ||
|
|
a2d4eb7d10 | ||
|
|
8082ab12dd | ||
|
|
b1def915d6 | ||
|
|
70bb22c966 | ||
|
|
06520791fa | ||
|
|
9d762e0777 | ||
|
|
ef53c70c7e | ||
|
|
8cf4e37310 | ||
|
|
04e7180b69 | ||
|
|
d40d3615b2 | ||
|
|
4efec939aa | ||
|
|
a0f24d8227 | ||
|
|
a2278423f6 | ||
|
|
7321d5d8f7 | ||
|
|
464426537e | ||
|
|
ebb2d5618c | ||
|
|
7b9c10f47c | ||
|
|
478640e808 | ||
|
|
1fa5e3c9af | ||
|
|
7fede58044 | ||
|
|
090a7985c3 | ||
|
|
ab993423a6 | ||
|
|
ab8802c3f1 | ||
|
|
f5fa2ff23a | ||
|
|
6a1b154061 | ||
|
|
73a4e474f1 | ||
|
|
6f630a0e62 | ||
|
|
42906f5a37 | ||
|
|
931ed76673 | ||
|
|
ebc4e202fe | ||
|
|
a7dda587ba | ||
|
|
1ace2b7a3f | ||
|
|
4e30591837 | ||
|
|
3144032ed6 | ||
|
|
af2875e0ef | ||
|
|
d9221e1053 | ||
|
|
bee1df45ad | ||
|
|
316be12b71 | ||
|
|
6c51ff83d5 | ||
|
|
1cad5b6b39 | ||
|
|
4138ab0730 | ||
|
|
171396ca57 | ||
|
|
193eb4e445 | ||
|
|
69b3831271 | ||
|
|
39efacace4 | ||
|
|
e70dfc8832 | ||
|
|
60bf851567 | ||
|
|
053038df8d | ||
|
|
f0afc74e5f | ||
|
|
9d3c8aaf5c | ||
|
|
86467b2be1 | ||
|
|
8575d92373 | ||
|
|
21ee7c6af5 | ||
|
|
7b61b4bd5b | ||
|
|
1bcb7ac87f | ||
|
|
52305d69f2 | ||
|
|
bd70e7c09e | ||
|
|
f6cf0f28ea | ||
|
|
d04f9f4e96 | ||
|
|
c0d097cb40 | ||
|
|
533e688d1a | ||
|
|
36d40dec70 | ||
|
|
a3f5de099f | ||
|
|
9b497e323f | ||
|
|
9a5b5595c3 | ||
|
|
24f23adf24 | ||
|
|
cc26d0bd8b | ||
|
|
2b51bda3e7 | ||
|
|
1be2e81920 | ||
|
|
ea45c5c694 | ||
|
|
768567dfc7 | ||
|
|
870e7a9ef9 | ||
|
|
a9e572a1bf | ||
|
|
c7899a1dae | ||
|
|
7bb077a74d | ||
|
|
a29eaa2618 | ||
|
|
762369ad2b | ||
|
|
a8610beafd | ||
|
|
5e1a91f484 | ||
|
|
04fdb9626d | ||
|
|
6c5429b353 | ||
|
|
2bedf1f355 | ||
|
|
02752d9151 | ||
|
|
689bfa1c76 | ||
|
|
70af046131 | ||
|
|
7a45013782 | ||
|
|
6f7851b7ba | ||
|
|
85f42d1e23 | ||
|
|
f8ddaab884 | ||
|
|
78373c61ce | ||
|
|
13e5c017e2 | ||
|
|
3789d73593 | ||
|
|
38f42fe747 | ||
|
|
3a6f9a608d | ||
|
|
e14a5b2575 | ||
|
|
04a68a4e96 | ||
|
|
dd049374d7 | ||
|
|
526ec306ae | ||
|
|
f16ed40c82 | ||
|
|
96572539a7 | ||
|
|
793eb14ed7 | ||
|
|
1f94454d63 | ||
|
|
c7e3bdcd6c | ||
|
|
004a7b1395 | ||
|
|
96bb63ba6d | ||
|
|
0ea72b017e | ||
|
|
b1498f3df1 | ||
|
|
b0759eba62 | ||
|
|
ff69cafe7f | ||
|
|
698612bb3d | ||
|
|
f1e90394a0 | ||
|
|
b24f855321 | ||
|
|
2f23292836 | ||
|
|
085e6ae95a | ||
|
|
3be2aedfe3 | ||
|
|
6e56b6cbaf | ||
|
|
16ff473fd6 | ||
|
|
4565fada08 | ||
|
|
648f808583 | ||
|
|
7a6212932e | ||
|
|
aa933696ec | ||
|
|
39d59dbbb0 | ||
|
|
a511ea3a4b | ||
|
|
e489e809ab | ||
|
|
e56d41d26b | ||
|
|
b1d45ad593 | ||
|
|
1f01d974e3 | ||
|
|
32038f8948 | ||
|
|
910c04544b | ||
|
|
10674b8930 | ||
|
|
8c9522e22b | ||
|
|
2a90b521da | ||
|
|
2098a60af1 | ||
|
|
67c0178b9a | ||
|
|
5da07c42bb | ||
|
|
f82035be98 | ||
|
|
a652268eb8 | ||
|
|
13d054a54c | ||
|
|
84cf2a3ad1 | ||
|
|
32e0d60dcd | ||
|
|
0a6755992a | ||
|
|
f60afe1196 | ||
|
|
0cc30a7934 | ||
|
|
3cc0f32617 | ||
|
|
29ae9c6927 | ||
|
|
892abe098c | ||
|
|
9cf683f64f | ||
|
|
f2bb8de4e1 | ||
|
|
b6a28af99e | ||
|
|
13a9dd3696 | ||
|
|
60bbd12386 | ||
|
|
3e09274cb5 | ||
|
|
e9d1da4b51 | ||
|
|
68e3fcb1cd | ||
|
|
745be0909a | ||
|
|
74dd36bde1 | ||
|
|
0bee4168b4 | ||
|
|
d87c6027c9 | ||
|
|
859faadecd | ||
|
|
e8741f9151 | ||
|
|
ef8b0fc258 | ||
|
|
3b27fedcd3 | ||
|
|
0991fbfffd | ||
|
|
499072c94f | ||
|
|
fa8598adbd | ||
|
|
089c4ac35e | ||
|
|
af8b559fa6 | ||
|
|
62ba85d383 | ||
|
|
f4b2f84e68 | ||
|
|
474e7880d6 | ||
|
|
89f74b7c81 | ||
|
|
0abcb8bf23 | ||
|
|
47887b67d2 | ||
|
|
41a96b990d | ||
|
|
ce8bc5c631 | ||
|
|
eebb214574 | ||
|
|
47c827caf6 | ||
|
|
a82a22b133 | ||
|
|
1fd444c91c | ||
|
|
f28ae29990 | ||
|
|
aedabcdf00 | ||
|
|
3b37f8e48b | ||
|
|
beb328ec09 | ||
|
|
f9b9bdab47 | ||
|
|
3beb82c959 | ||
|
|
9e6776c6fb | ||
|
|
e807f6a702 | ||
|
|
07212aa5d6 | ||
|
|
a0f31d6443 | ||
|
|
df6a5096c2 | ||
|
|
43336d04d4 | ||
|
|
ca73b20fe5 | ||
|
|
09f6ad4031 | ||
|
|
97b52f2e64 | ||
|
|
0adc77a5cf | ||
|
|
e469e8fc70 | ||
|
|
10da3c517c | ||
|
|
5f7c4b5c37 | ||
|
|
c34d05218a | ||
|
|
3abf99da91 | ||
|
|
8d94238e91 | ||
|
|
dc169fc4fd | ||
|
|
abe8361b18 | ||
|
|
9f406e83aa | ||
|
|
2ee163bf8a | ||
|
|
775a720e0a | ||
|
|
62c56762bf | ||
|
|
16fbab8677 | ||
|
|
505ac39a3f | ||
|
|
84aefabfea | ||
|
|
102fefbd92 | ||
|
|
87b8a802c9 | ||
|
|
fc7a4b0800 | ||
|
|
679330b9a8 | ||
|
|
ab7b495358 | ||
|
|
9b6064e87e | ||
|
|
ab3fa25709 | ||
|
|
07af7a4298 | ||
|
|
926274e2d9 | ||
|
|
5e4c22c99d | ||
|
|
06b4f8055f | ||
|
|
81b307fa73 | ||
|
|
6d131f9b4f | ||
|
|
014c1e968a | ||
|
|
8f9321701e | ||
|
|
6b5016c9fe | ||
|
|
50e8808af0 | ||
|
|
916fda1eb5 | ||
|
|
9422e3ac98 | ||
|
|
09e8fabcf2 | ||
|
|
2064de0752 | ||
|
|
f94aa936b0 | ||
|
|
4a22ad7616 | ||
|
|
133bd89a2c | ||
|
|
6d9c083c0f | ||
|
|
63d97c8982 | ||
|
|
7540ae0ef7 | ||
|
|
3f06470c5e | ||
|
|
04537684cb | ||
|
|
6fba76b399 | ||
|
|
9f9da1847b | ||
|
|
975f7f64b7 | ||
|
|
cacf3ac862 | ||
|
|
104247c786 | ||
|
|
064ed5b692 | ||
|
|
bddb152d76 | ||
|
|
e2f3bfd61a | ||
|
|
791774490c | ||
|
|
c4f984c612 | ||
|
|
04eee0f4b7 | ||
|
|
aa9846962b | ||
|
|
881c3a5c1d | ||
|
|
59ba9dc129 | ||
|
|
b9efaef0a5 | ||
|
|
3e822bcca1 | ||
|
|
0732034dbe | ||
|
|
8dde9bf3c5 | ||
|
|
667a482fba | ||
|
|
6be8dae2d1 | ||
|
|
f478b2c9f2 | ||
|
|
300c5c9431 | ||
|
|
9c066ca703 | ||
|
|
9946c4716f | ||
|
|
ee0e719e76 | ||
|
|
5d5761288f | ||
|
|
0a683dae15 | ||
|
|
afb0288672 | ||
|
|
aefe248034 | ||
|
|
398382c3a5 | ||
|
|
cf2c16564f | ||
|
|
48ee5e1699 | ||
|
|
27c9ec8dab | ||
|
|
31e767208e | ||
|
|
0c2943e3da | ||
|
|
99ea02ca40 | ||
|
|
8ceab51228 | ||
|
|
4a69a9204b | ||
|
|
0e136637e0 | ||
|
|
d803622c01 | ||
|
|
2e49534483 | ||
|
|
8210f4f74a | ||
|
|
69e2bcb164 | ||
|
|
e4ae42e80d | ||
|
|
ad3950e2c5 | ||
|
|
6bab882f89 | ||
|
|
b6709b426c | ||
|
|
77f5723936 | ||
|
|
898574130c | ||
|
|
11faa6ca36 | ||
|
|
01dc0c004b | ||
|
|
6dbeaa14e2 | ||
|
|
9271d4e2ea | ||
|
|
06ca1461f4 | ||
|
|
f84c931d5d | ||
|
|
1d2d7099c4 | ||
|
|
00540435eb | ||
|
|
b5552a858e | ||
|
|
fa110b027b | ||
|
|
7ce7ebbf96 | ||
|
|
98315d8e15 | ||
|
|
6066e2162c | ||
|
|
4279e3d388 | ||
|
|
221814cc11 | ||
|
|
2d4e2c84b5 | ||
|
|
e84c7d4f8c | ||
|
|
da1ae1b410 | ||
|
|
147783666c | ||
|
|
2c68458762 | ||
|
|
1682cc004e | ||
|
|
d6a8925655 | ||
|
|
1dba1034b6 | ||
|
|
437efc393d | ||
|
|
b27fb045fd | ||
|
|
cc3d916dd3 | ||
|
|
b1659b0aa9 | ||
|
|
3051cff3c8 | ||
|
|
bf01bf4ad6 | ||
|
|
0151b0e16c | ||
|
|
c30e922c38 | ||
|
|
0417dd7e08 | ||
|
|
158c1cbcfd | ||
|
|
a5bd9b25c6 | ||
|
|
fbe6b38edc | ||
|
|
6d4ece0667 | ||
|
|
b19380c91c | ||
|
|
dc5585cd8d | ||
|
|
b53163d1e1 | ||
|
|
63c3e2c93a | ||
|
|
3d96bc98e2 | ||
|
|
e5bffc3e89 | ||
|
|
e613c5626e | ||
|
|
704a0cc1aa | ||
|
|
85447ccfb4 | ||
|
|
bd643aa525 | ||
|
|
1d419a0bd6 | ||
|
|
e6815f95c4 | ||
|
|
e2e5769767 | ||
|
|
23bec87ad0 | ||
|
|
992b64c08a | ||
|
|
85b6103944 | ||
|
|
9a376d5209 | ||
|
|
cee4d65d38 | ||
|
|
b81459cdf7 | ||
|
|
136a75f526 | ||
|
|
406a2c5bc9 | ||
|
|
b1b0d23839 | ||
|
|
495621a014 | ||
|
|
d37f3a4646 | ||
|
|
a9e3443cab | ||
|
|
a8db5dae3d | ||
|
|
76af564057 | ||
|
|
f6df5cc2ec | ||
|
|
747cde4a25 | ||
|
|
9b6a5ea943 | ||
|
|
00b68a4408 | ||
|
|
d14826ea96 | ||
|
|
4143a8cd05 | ||
|
|
1ae3f0d3e0 | ||
|
|
52b762ee1c | ||
|
|
5c2fdf022c | ||
|
|
f374570907 | ||
|
|
8ea1243777 | ||
|
|
12141f6c1d | ||
|
|
d1a4ebda4c | ||
|
|
5f76759c35 | ||
|
|
a1b32e3e6e | ||
|
|
744bad2bb0 | ||
|
|
5a1dd952d2 | ||
|
|
98580746e0 | ||
|
|
7c39f1318b | ||
|
|
28f8d62a1a | ||
|
|
4436cf9b04 | ||
|
|
2cd19a7984 | ||
|
|
f956ce1d2c | ||
|
|
1e578a5c77 | ||
|
|
adaac94e9c | ||
|
|
5832034208 | ||
|
|
d45366311a | ||
|
|
395463dee9 | ||
|
|
acfbf51209 | ||
|
|
9872e31a0a | ||
|
|
a80b650dcc | ||
|
|
0051d7f9d8 | ||
|
|
8f69e687ec | ||
|
|
e0438c25b0 | ||
|
|
2a2d3eddd3 | ||
|
|
bb14b8ca3d | ||
|
|
6768c5be02 | ||
|
|
80b19b616a | ||
|
|
98ab218b10 | ||
|
|
ee67f14f25 | ||
|
|
fb741fbf46 | ||
|
|
d7fe1c43b7 | ||
|
|
c402c898be | ||
|
|
e6f4c9c1ad | ||
|
|
b6e86c1ffd | ||
|
|
f18914764a | ||
|
|
7f600e361a | ||
|
|
4a2edadf6d | ||
|
|
a32283a8bd | ||
|
|
a46e7e62cc | ||
|
|
9c465baac9 | ||
|
|
d8f118cfe9 | ||
|
|
8a53863218 | ||
|
|
4cc61057d2 | ||
|
|
9fafc2d8b5 | ||
|
|
0b7e3e2c17 | ||
|
|
2e4413f44f | ||
|
|
65056c7eef | ||
|
|
ccfdb6b7b5 | ||
|
|
d6da4e7a26 | ||
|
|
2893233a03 | ||
|
|
0f3988c8e2 | ||
|
|
47e272702c | ||
|
|
428a51b071 | ||
|
|
19a9c27d9c | ||
|
|
45a4bcf8b5 | ||
|
|
00c2ac4aeb | ||
|
|
f03db0180b | ||
|
|
95755f53a1 | ||
|
|
de5b0a4264 | ||
|
|
f1f9503b3a | ||
|
|
c144631759 | ||
|
|
9fafe6bae5 | ||
|
|
757fc0eab5 | ||
|
|
227179087d | ||
|
|
33140fa901 | ||
|
|
0d94377f36 | ||
|
|
66b92c26a1 | ||
|
|
c74941b9c1 | ||
|
|
a705235a99 | ||
|
|
6e5ae8b9e0 | ||
|
|
df53c9810b | ||
|
|
e84ea8b8e5 | ||
|
|
9e347a858c | ||
|
|
f53b791703 | ||
|
|
efa267d5fb | ||
|
|
fb1e60229e | ||
|
|
00fa662883 | ||
|
|
4ef8aaa689 | ||
|
|
64164db22e | ||
|
|
dfb2178fd8 | ||
|
|
2be2163b7e | ||
|
|
711ec49d88 | ||
|
|
710eaf5ef8 | ||
|
|
ccddf3b1cd | ||
|
|
3aefa57578 | ||
|
|
ea4b330062 | ||
|
|
da6b7d5011 | ||
|
|
add897f4a6 | ||
|
|
5cb535e5df | ||
|
|
106c9a6922 | ||
|
|
1c87d414e0 | ||
|
|
62b0f1938b | ||
|
|
a7e04aa7b0 | ||
|
|
aa9f81f5c9 | ||
|
|
df18758643 | ||
|
|
b351fa9531 | ||
|
|
5303d2fd89 | ||
|
|
3e29427679 | ||
|
|
d240b75b95 | ||
|
|
cb6621ac06 | ||
|
|
cc1d796695 | ||
|
|
ed6f2d1c28 | ||
|
|
54d23ac2d3 | ||
|
|
2348e1bf31 | ||
|
|
ad09a6ad4b | ||
|
|
d8a47267f6 | ||
|
|
c882fb1018 | ||
|
|
5777685bf3 | ||
|
|
a1053cc41e | ||
|
|
a934021a5a | ||
|
|
24f5a54756 | ||
|
|
43af64b298 | ||
|
|
314bbd4f06 | ||
|
|
e8adb8fc68 | ||
|
|
49de685cd4 | ||
|
|
d4ee0db19b | ||
|
|
2171a31e5a | ||
|
|
b35c52ae27 | ||
|
|
055e27f9aa | ||
|
|
6ad12e922c | ||
|
|
489af5a703 | ||
|
|
1f03f4b839 | ||
|
|
464dada0c2 | ||
|
|
fb73752d5c | ||
|
|
766c9d10c4 | ||
|
|
bd54a9fd01 | ||
|
|
9ff4673935 | ||
|
|
282771f9f5 | ||
|
|
d60986c5f0 | ||
|
|
12fc59af41 | ||
|
|
19fca8304d | ||
|
|
81a0e1376c | ||
|
|
6e5451e314 | ||
|
|
a58d7b3a97 | ||
|
|
b7b0b46709 | ||
|
|
ee4f74aad5 | ||
|
|
ab8d8b9717 | ||
|
|
669790f12f | ||
|
|
c5bd45a429 | ||
|
|
8ef977e468 | ||
|
|
8d38f3aae5 | ||
|
|
83b7c8ae8d | ||
|
|
b71c5fb27d | ||
|
|
c536300020 | ||
|
|
37a054800b | ||
|
|
8ceb3a4c77 | ||
|
|
91146fdfb9 | ||
|
|
8f5ca8d783 | ||
|
|
2364c1123c | ||
|
|
3b27f560fb | ||
|
|
c741093ab9 | ||
|
|
405685ece3 | ||
|
|
6de078473b | ||
|
|
f9a131a480 | ||
|
|
16ba5c42ab | ||
|
|
0e892817bf | ||
|
|
0d01f7f365 | ||
|
|
f67669a830 | ||
|
|
4f5ff80199 | ||
|
|
c84b3c576b | ||
|
|
f4490623b3 | ||
|
|
2b00c00459 | ||
|
|
7e5ea37782 | ||
|
|
9ad140d774 | ||
|
|
67a457cf98 | ||
|
|
e3305bc3f5 | ||
|
|
171fcab814 | ||
|
|
0723f0ae1e | ||
|
|
c1d0c159af | ||
|
|
65ec3f6d0a | ||
|
|
009e709384 | ||
|
|
c124e3dfd2 | ||
|
|
c6f98630dc | ||
|
|
d270411578 | ||
|
|
eea68f438b | ||
|
|
575b2c0bb6 | ||
|
|
e024edb6f7 | ||
|
|
ef4bcfb1b8 | ||
|
|
94e5b8410c | ||
|
|
9edb0f2b9c | ||
|
|
440f0f1e85 | ||
|
|
707e6013cc | ||
|
|
80ee8d1afa | ||
|
|
9923cd4b9b | ||
|
|
3a4ec52766 | ||
|
|
00548ea6dd | ||
|
|
6e654049bd | ||
|
|
71c94b8c8d | ||
|
|
cd9875f67a | ||
|
|
4a118c76ac | ||
|
|
8d263cdd37 | ||
|
|
f56d1a570a | ||
|
|
63dd6ea835 | ||
|
|
6c19f959fa | ||
|
|
c4a180f936 | ||
|
|
2c272e936f | ||
|
|
55c859e928 | ||
|
|
98411ad036 | ||
|
|
d7719cf79c | ||
|
|
e7c606495b | ||
|
|
73cc7ceec3 | ||
|
|
475d5e3ff1 | ||
|
|
33f399893e | ||
|
|
015be785e1 | ||
|
|
446a0175c6 | ||
|
|
a5a438976f | ||
|
|
26f0593702 | ||
|
|
85c73783f3 | ||
|
|
79ddb8ca16 | ||
|
|
3171ae0afb | ||
|
|
6f97ddd57d | ||
|
|
ce4f850cc4 | ||
|
|
dfcef93882 | ||
|
|
667234b05b | ||
|
|
c2c460875d | ||
|
|
7df7d05628 | ||
|
|
61a4ade2f4 | ||
|
|
594c2e47b0 | ||
|
|
b6df4b64b8 | ||
|
|
7190d949f5 | ||
|
|
51db2e75f7 | ||
|
|
992d56303c | ||
|
|
84f249439c | ||
|
|
7d09a906c6 | ||
|
|
80cb260082 | ||
|
|
fc47d16800 | ||
|
|
ae237d36b6 | ||
|
|
4182ab1093 | ||
|
|
9ba7b77a7a | ||
|
|
c8adf1c972 | ||
|
|
05275c5d0d | ||
|
|
3c5e7bfef9 | ||
|
|
5724d13b7d | ||
|
|
0491b4b153 | ||
|
|
dc1d045716 | ||
|
|
484fb7b93f | ||
|
|
e7286856c9 | ||
|
|
33804a8cb2 | ||
|
|
9f6a894ddf | ||
|
|
37160cc477 | ||
|
|
be7553956f | ||
|
|
23a1eb323f | ||
|
|
3d7f469708 | ||
|
|
0bf9939d17 | ||
|
|
0da056f282 | ||
|
|
129da00daa | ||
|
|
962f41b5d6 | ||
|
|
cdc0fee934 | ||
|
|
589227a97f | ||
|
|
bde9f6e565 | ||
|
|
7b6bab1767 | ||
|
|
729cb5cddc | ||
|
|
220a4e6424 | ||
|
|
37356b2b59 | ||
|
|
42ce4f1bf8 | ||
|
|
455515f658 | ||
|
|
ad61783e15 | ||
|
|
c8d958506c | ||
|
|
c528685283 | ||
|
|
4c6522d33d | ||
|
|
eb0930b140 | ||
|
|
31105a1acb | ||
|
|
f2421133cb | ||
|
|
3fa291fa67 | ||
|
|
be3bdc44bc | ||
|
|
d7faa92027 | ||
|
|
ddb926d9fa | ||
|
|
7e4f70d046 | ||
|
|
bb9b934ef4 | ||
|
|
a34cd44967 | ||
|
|
044d7725dc | ||
|
|
8f63db7d19 | ||
|
|
f0cd761559 | ||
|
|
22f8287359 | ||
|
|
6dc979d35f | ||
|
|
e62b936fc5 | ||
|
|
6c7867917d | ||
|
|
9d7093ff9d | ||
|
|
625c1cd5c8 | ||
|
|
250fd8f570 | ||
|
|
70ff4fa09d | ||
|
|
79dbf11107 | ||
|
|
2e21b623a4 | ||
|
|
64b5102f7e | ||
|
|
f20d9b6ca9 | ||
|
|
51d02e215a | ||
|
|
8d6b7d3b2f | ||
|
|
c9e61c588e | ||
|
|
87ad211a82 | ||
|
|
377c1c5890 | ||
|
|
a3ac5c2519 | ||
|
|
79646bb941 | ||
|
|
6804d4f605 | ||
|
|
e7bfd07c0d | ||
|
|
a7ae1a89ac | ||
|
|
90499bd9fa | ||
|
|
8e73069a2e | ||
|
|
b3fe85a95a | ||
|
|
9862eb88bb | ||
|
|
5497f74534 | ||
|
|
c8da0fb6c7 | ||
|
|
f6aa45168f | ||
|
|
b4c846a170 | ||
|
|
840bb983af | ||
|
|
d6888aa2e9 | ||
|
|
4652c3006f | ||
|
|
0ef0d6c238 | ||
|
|
e24ba90096 | ||
|
|
f436471c70 | ||
|
|
8c1e43e9c8 | ||
|
|
7395be6ea4 | ||
|
|
64be45e01c | ||
|
|
9b6ac5dfe9 | ||
|
|
825651c274 | ||
|
|
7789748210 | ||
|
|
ae20a98577 | ||
|
|
c1fd5a5d27 | ||
|
|
cceb3bd147 | ||
|
|
bdc0e529f7 | ||
|
|
ddf22c09a6 | ||
|
|
d4ed08c104 | ||
|
|
5a77bb31ce | ||
|
|
8f287e939b | ||
|
|
23dbb51e4b | ||
|
|
1ee035e865 | ||
|
|
9b4a4a9698 | ||
|
|
259c0164ba | ||
|
|
55bdeea2f0 | ||
|
|
a5e2ff48c2 | ||
|
|
a7a07687d6 | ||
|
|
3fe0306864 | ||
|
|
f76aed7671 | ||
|
|
7d7ddf53d9 | ||
|
|
81394135f6 | ||
|
|
bd2c2d6aa8 | ||
|
|
692010528a | ||
|
|
8074cabba1 | ||
|
|
5e1e2d7812 | ||
|
|
510e6008b6 | ||
|
|
c23a3ee626 | ||
|
|
28cfa8ca29 | ||
|
|
15a36fb40a | ||
|
|
ac11c76829 | ||
|
|
e9dc0f05dc | ||
|
|
82d8a45d87 | ||
|
|
98b1193619 | ||
|
|
0f0d300edc | ||
|
|
fee59c5e90 | ||
|
|
0142b24884 | ||
|
|
3936783d74 | ||
|
|
4e3bdad812 | ||
|
|
6c7c84edd7 | ||
|
|
1a36af2922 | ||
|
|
5b6a7844e3 | ||
|
|
3492bcdbd5 | ||
|
|
96f1066a56 | ||
|
|
1d2682224f | ||
|
|
cc4a9511b5 | ||
|
|
5e245dff8c | ||
|
|
6bb9374103 | ||
|
|
fb5466d6e6 | ||
|
|
f57013d9b5 | ||
|
|
d25a5e3e3c | ||
|
|
67f64de7fd | ||
|
|
a3a789568c | ||
|
|
120d2bbee5 | ||
|
|
4a5dc21427 | ||
|
|
c1fe0b3191 | ||
|
|
d00d92dac9 | ||
|
|
a071b974c8 | ||
|
|
ba7261e062 | ||
|
|
786f4253b9 | ||
|
|
aade53eaeb | ||
|
|
25a1d86eec | ||
|
|
b4d36c8104 | ||
|
|
7507595bb9 | ||
|
|
a47e34a5b6 | ||
|
|
0e52613e47 | ||
|
|
cd4c3ae99f | ||
|
|
8cd8b85022 | ||
|
|
a26b44b2cc | ||
|
|
11652ac021 | ||
|
|
861ea5b426 | ||
|
|
1b731a852a | ||
|
|
730cf023aa | ||
|
|
7e88ffa5bf | ||
|
|
1885cef7f1 | ||
|
|
162ae01605 | ||
|
|
52e0c9c7b6 | ||
|
|
2b7135d421 | ||
|
|
f60435eb32 | ||
|
|
c0155047a8 | ||
|
|
fe50e40524 | ||
|
|
198fd145e7 | ||
|
|
045e0bc421 | ||
|
|
4b8d66de97 | ||
|
|
45a4c966de | ||
|
|
d18194c64f | ||
|
|
b535a855a5 | ||
|
|
9a1e6c92f0 | ||
|
|
5852827b98 | ||
|
|
912ddf3a2a | ||
|
|
fa840eb0ac | ||
|
|
e368468ffa | ||
|
|
3ef87df0bc | ||
|
|
036f53c849 | ||
|
|
d0f35657ca | ||
|
|
1e55443415 | ||
|
|
db31a4201c | ||
|
|
22665958ca | ||
|
|
cdd3afd1ee | ||
|
|
992a023bde | ||
|
|
eb053ee99d | ||
|
|
55ee293f96 | ||
|
|
fd3e98bca7 | ||
|
|
449731fb35 | ||
|
|
f0199a0bd4 | ||
|
|
1f0b71353d | ||
|
|
1cfdd5e86f | ||
|
|
f9ea4c23d8 | ||
|
|
fb024ea3c2 | ||
|
|
d02e9cf0f9 | ||
|
|
f09607caaf | ||
|
|
96ac0f30ca | ||
|
|
3b18791e90 | ||
|
|
c57bd6a1a9 | ||
|
|
1fd45a39ac | ||
|
|
60fdc57490 | ||
|
|
de73443303 | ||
|
|
9c4e1b5250 | ||
|
|
a37b514af7 | ||
|
|
19f54e23f4 | ||
|
|
a2c11f2572 | ||
|
|
1a491351a8 | ||
|
|
5d28d619f9 | ||
|
|
15915ce7b2 | ||
|
|
1b53e5833e | ||
|
|
ef32988e36 | ||
|
|
4840d694ce | ||
|
|
f1acc0c6b9 | ||
|
|
a26c6127d0 | ||
|
|
241efe6a24 | ||
|
|
cb5e0503f7 | ||
|
|
1b1fbef655 | ||
|
|
65f982c5ad | ||
|
|
4957727877 | ||
|
|
0366082e8e | ||
|
|
5139e50cf2 | ||
|
|
459255f20f | ||
|
|
5eb890844d | ||
|
|
31d2adade7 | ||
|
|
c22d1eeeee | ||
|
|
6ee4aea794 | ||
|
|
eef792d148 | ||
|
|
247a41de23 | ||
|
|
1892fcec69 | ||
|
|
1d1423494b | ||
|
|
e1c1ce78eb | ||
|
|
e4880a6715 | ||
|
|
8716fff482 | ||
|
|
6c9e05b9eb | ||
|
|
c0a69f26a2 | ||
|
|
544ddff37f | ||
|
|
81ed77d4c9 | ||
|
|
c4f29bd2cf | ||
|
|
ee498f1ad0 | ||
|
|
a899a225a4 | ||
|
|
75d7ee1827 | ||
|
|
8d02819060 | ||
|
|
8eeca4a9ba | ||
|
|
45cfcd2c3d | ||
|
|
20ddc43a2e | ||
|
|
1325d388ca | ||
|
|
085f309e1f | ||
|
|
fa95ac6bfd | ||
|
|
b09ad7748f | ||
|
|
67ba1499f0 | ||
|
|
f277ea56dc | ||
|
|
d112880c58 | ||
|
|
e691a26eee | ||
|
|
184ceaa649 | ||
|
|
3c202e1597 | ||
|
|
f407e52703 | ||
|
|
34e22606be | ||
|
|
fee24542ee | ||
|
|
4900a73eeb | ||
|
|
33552c0720 | ||
|
|
401c657c0b | ||
|
|
ea899b57a0 | ||
|
|
63ef6b6457 | ||
|
|
a869598d5e | ||
|
|
082e68d975 | ||
|
|
6a70dd8357 | ||
|
|
3b335a8e73 | ||
|
|
f5354776f1 | ||
|
|
9413eb2206 | ||
|
|
e986f77e72 | ||
|
|
75095ce874 | ||
|
|
dd7db5661b | ||
|
|
4b8d725528 | ||
|
|
20b7b8d01a | ||
|
|
1e83af41d7 | ||
|
|
e852d83139 | ||
|
|
89b5610aa2 | ||
|
|
f721098ae0 | ||
|
|
bfff6454ef | ||
|
|
b90e5e4274 | ||
|
|
bacd4558e1 | ||
|
|
008e698766 | ||
|
|
ed7f066f20 | ||
|
|
db1b2a63af | ||
|
|
d7c81a08ef | ||
|
|
aadb6c86f3 | ||
|
|
6006a328fe | ||
|
|
5653a66a31 | ||
|
|
99b9c51ad6 | ||
|
|
fe92389eab | ||
|
|
1ca5b217fc | ||
|
|
db20433d44 | ||
|
|
4e4ab52032 | ||
|
|
4cc513f1d3 | ||
|
|
7904065333 | ||
|
|
961e06c31f | ||
|
|
4132bdaba4 | ||
|
|
1c75f5f530 | ||
|
|
d8e7dd7c3b | ||
|
|
d0377475bb | ||
|
|
7826ff17c0 | ||
|
|
a46982333b | ||
|
|
e2cebaa5c8 | ||
|
|
d03987591c | ||
|
|
e909c37f88 | ||
|
|
e998edc20f | ||
|
|
5cb48fb517 | ||
|
|
f2f8c258f9 | ||
|
|
1c1be2b334 | ||
|
|
5d89257643 | ||
|
|
897e134714 | ||
|
|
e6cb97028b | ||
|
|
6c0dc80d43 | ||
|
|
ce3a4562d9 | ||
|
|
217f0cd4cc | ||
|
|
1e649ccce6 | ||
|
|
cbc69ec5fd | ||
|
|
0d1eedc34a | ||
|
|
4e85bf4520 | ||
|
|
5d0023a80b | ||
|
|
e300de2425 | ||
|
|
569877752e | ||
|
|
4846cf7693 | ||
|
|
16a46955f4 | ||
|
|
cb8dac05d5 | ||
|
|
f6eb1e4745 | ||
|
|
16e8a6e26d | ||
|
|
13a6d68b60 | ||
|
|
1affd992de | ||
|
|
d951258891 | ||
|
|
f5dfb0264b | ||
|
|
9744f53d65 | ||
|
|
b231a66fc7 | ||
|
|
3b8f5c5093 | ||
|
|
253d213818 | ||
|
|
f89cb3d61e | ||
|
|
65b3d4fffa | ||
|
|
ba1b667ae8 | ||
|
|
e31b05042f | ||
|
|
eb6573399b | ||
|
|
8ba8ce76ce | ||
|
|
e65b8e9d4d | ||
|
|
2fc379fbef | ||
|
|
fb96a75a8e | ||
|
|
240d15af47 | ||
|
|
2530e43797 | ||
|
|
b05d85d6c3 | ||
|
|
52f2e77310 | ||
|
|
bd00c6de3e | ||
|
|
84e443ee4b | ||
|
|
87aea4812f | ||
|
|
21ae3b14c8 | ||
|
|
708ae185f1 | ||
|
|
5c6dbd7bd1 | ||
|
|
f19a280b7f | ||
|
|
3821dfb8ff | ||
|
|
47cf9e2fe6 | ||
|
|
01a8502b20 | ||
|
|
df75518389 | ||
|
|
90c371c72f | ||
|
|
edfa079326 | ||
|
|
9609f7e6cf | ||
|
|
316a617c65 | ||
|
|
30c83b7c72 | ||
|
|
382d8b468c | ||
|
|
cbe6d07a86 | ||
|
|
6d67700ee6 | ||
|
|
0e92a12e65 | ||
|
|
e9cf1db184 | ||
|
|
e4c98d947e | ||
|
|
ab9de97d6e | ||
|
|
4e425a3380 | ||
|
|
ac67c84ce7 | ||
|
|
79120ea7a5 | ||
|
|
a85a7f624a | ||
|
|
19e4da9936 | ||
|
|
1494c37cc7 | ||
|
|
af1fbaf3ed | ||
|
|
82a93e39f8 | ||
|
|
36684f8ee5 | ||
|
|
c8cd2ae1ad | ||
|
|
93afb5b602 | ||
|
|
429b0bb8f8 | ||
|
|
80ce5447e5 | ||
|
|
609b0dceb1 | ||
|
|
f9d276a908 | ||
|
|
5f4eb58db9 | ||
|
|
ea47042816 | ||
|
|
4b23e17534 | ||
|
|
f69301611a | ||
|
|
c04ac5cd48 | ||
|
|
aa39349fc4 | ||
|
|
d1c915fef3 | ||
|
|
69bdf07423 | ||
|
|
4115a6c790 | ||
|
|
8f8aae57de | ||
|
|
362f75c8b2 | ||
|
|
39e855e691 | ||
|
|
73303ccd33 | ||
|
|
f7cdb1f00b | ||
|
|
0caa6b6ee0 | ||
|
|
6826494898 | ||
|
|
69fcfbd6ce | ||
|
|
64973e1b51 | ||
|
|
c1d62a2ff6 | ||
|
|
f4c0f24e80 | ||
|
|
1fa419eae2 | ||
|
|
e3a3eb6d85 | ||
|
|
18a32f9a05 | ||
|
|
b7c127b2d8 | ||
|
|
f83f94fb07 | ||
|
|
23f76a1c80 | ||
|
|
cc0f6ea448 | ||
|
|
37b211532e | ||
|
|
cbb6936553 | ||
|
|
633433cab9 | ||
|
|
3ab7ed0b71 | ||
|
|
fc1adf8c28 | ||
|
|
79cf313b5b | ||
|
|
00834a668c | ||
|
|
32c9c23d7e | ||
|
|
5ac8d2ada6 | ||
|
|
54eb6ceaa9 | ||
|
|
540db91311 | ||
|
|
1ee2193c88 | ||
|
|
84877af947 | ||
|
|
388cf92a70 | ||
|
|
840441a749 | ||
|
|
c39a510d09 | ||
|
|
299d46aa2c | ||
|
|
345f6ed9fe | ||
|
|
31aa9d9bf6 | ||
|
|
0c063a3876 | ||
|
|
af253198d2 | ||
|
|
cc381d605c | ||
|
|
39a1252084 | ||
|
|
c2f954a79d | ||
|
|
6fb8fdc9f6 | ||
|
|
e79a73147a | ||
|
|
6a921ae900 | ||
|
|
b7fcd17ef0 | ||
|
|
0f13d27efe | ||
|
|
482ca6b484 | ||
|
|
d52347cd71 | ||
|
|
0ab628fb6c | ||
|
|
7ed3a977ab | ||
|
|
59cfe3dc8d | ||
|
|
1a8a492ef7 | ||
|
|
74066c0ffe | ||
|
|
0269d5fa86 | ||
|
|
84a5a11a9c | ||
|
|
a01e6df7fa | ||
|
|
d4caccdbfb | ||
|
|
4ede8a62ea | ||
|
|
ba11248ac9 | ||
|
|
23d817d474 | ||
|
|
22cde8aa0d | ||
|
|
3dba49e899 | ||
|
|
ff53cf8b4d | ||
|
|
74f1cab23d | ||
|
|
23d7a9e1ad | ||
|
|
7bd9d3f4ad | ||
|
|
87a03a3759 | ||
|
|
7740f64bfd | ||
|
|
7ccbb69f5c | ||
|
|
b4ba527c12 | ||
|
|
dc5d4cf50a | ||
|
|
03b4b2ec29 | ||
|
|
4b45ece2aa | ||
|
|
9b9192c7d4 | ||
|
|
a62a41a0aa | ||
|
|
f09f32b1a8 | ||
|
|
f70003c821 | ||
|
|
904023c5fa | ||
|
|
a9fc3d43a0 | ||
|
|
b7a10f2712 | ||
|
|
f37dc1eab2 | ||
|
|
4febc07aa0 | ||
|
|
198e76f722 | ||
|
|
836301e75f | ||
|
|
ad37a652c5 | ||
|
|
5bed54c953 | ||
|
|
5675c4e117 | ||
|
|
16f1e568a5 | ||
|
|
e375274cbe | ||
|
|
cf3ef9323a | ||
|
|
7e2e2bf1b0 | ||
|
|
781c9dcc86 | ||
|
|
a8a922160d | ||
|
|
367bd0e3b9 | ||
|
|
4215d96ba9 | ||
|
|
50d18eb10d | ||
|
|
b65fedc781 | ||
|
|
88c8663d29 | ||
|
|
8c7dd2460d | ||
|
|
f20ee21ce7 | ||
|
|
a61e21bd87 | ||
|
|
39014696f3 | ||
|
|
af278d2d87 | ||
|
|
636e170f44 | ||
|
|
3d4fe8f832 | ||
|
|
94285918bf | ||
|
|
2ba7f949cc | ||
|
|
fc82ccf221 | ||
|
|
e71b1518f0 | ||
|
|
7738da7d16 | ||
|
|
c91eedcca0 | ||
|
|
4f5f4c61f3 | ||
|
|
2f31fb6284 | ||
|
|
d18caa7ba1 | ||
|
|
ac88586c8f | ||
|
|
968dfbac2a | ||
|
|
0501418da6 | ||
|
|
4efd853c96 | ||
|
|
a384eb9f73 | ||
|
|
4747fe5535 | ||
|
|
94eb1e37cb | ||
|
|
fd1ae60e49 | ||
|
|
62d06180ea | ||
|
|
dba3b9371c | ||
|
|
aadfe2a677 | ||
|
|
c0c34efc47 | ||
|
|
b66fb02174 | ||
|
|
3cf026ff47 | ||
|
|
8619084fa7 | ||
|
|
f6df644ab1 | ||
|
|
1183dee418 | ||
|
|
ffaba90c96 | ||
|
|
9eb760b3c7 | ||
|
|
70fcb30c67 | ||
|
|
553ab7b858 | ||
|
|
e4c0e9d7b5 | ||
|
|
8232dcfe97 | ||
|
|
f9886cfd44 | ||
|
|
b9c25dab4b | ||
|
|
580a6c5604 | ||
|
|
48aa32a184 | ||
|
|
39b6a49b3d | ||
|
|
2d7b97cf98 | ||
|
|
7230550567 | ||
|
|
93a3ac55a1 | ||
|
|
7a586c075b | ||
|
|
797611331b | ||
|
|
c8c943f66e | ||
|
|
10b3558bea | ||
|
|
d9111520fc | ||
|
|
e58bbcc771 | ||
|
|
b7806a7193 | ||
|
|
0bbf58638e | ||
|
|
8dcf41e9cf | ||
|
|
173e44d992 | ||
|
|
3fe6260fd4 | ||
|
|
8d600e090f | ||
|
|
851459922f | ||
|
|
f8aa5ffeac | ||
|
|
a0bf0e93f4 | ||
|
|
5529e80e17 | ||
|
|
5df7cec55a | ||
|
|
8f705d42b3 | ||
|
|
fc2f85b842 | ||
|
|
ffe5eee584 | ||
|
|
b7897fc7ac | ||
|
|
b4d18a62c9 | ||
|
|
c6b450f45c | ||
|
|
6a46049610 | ||
|
|
21712711f7 | ||
|
|
9f1bb177b8 | ||
|
|
dcd2cc22e1 | ||
|
|
af91ee1512 | ||
|
|
b4d63aa3cc | ||
|
|
d0e32911ad | ||
|
|
75fa582774 | ||
|
|
69155162ca | ||
|
|
2c865c123a | ||
|
|
0ec955cc90 | ||
|
|
0c4e22537d | ||
|
|
601eb4ece1 | ||
|
|
b84a638199 | ||
|
|
34350fd469 | ||
|
|
a469da5cc6 | ||
|
|
8be8a67f18 | ||
|
|
abc2e55610 | ||
|
|
cfd15d3371 | ||
|
|
44369bae43 |
No files matched your search
@@ -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
+177
-91
@@ -1,83 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "(gdb) Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
|
||||
"args": ["run"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Client Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "SDK Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
|
||||
"args": ["md","GLBX","Future","CL", "CLZ9", "--user=bts", "--pass="],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Excel Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": ["test","test_filter"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
"request": "attach",
|
||||
"processId":"${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": "SparkVM Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["run"],
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
@@ -102,24 +33,113 @@
|
||||
"description": "LD LIBRARY",
|
||||
"text": "set env LD_LIBRARY_PATH=.",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "(gdb) Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Client Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "SDK Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
|
||||
"args": [
|
||||
"md",
|
||||
"GLBX",
|
||||
"Future",
|
||||
"CL",
|
||||
"CLZ9",
|
||||
"--user=bts",
|
||||
"--pass="
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Excel Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"test_filter"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test", "[spreader]"],
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
@@ -139,7 +159,9 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
@@ -151,7 +173,10 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test", "[admin_service]"],
|
||||
"args": [
|
||||
"test",
|
||||
"[admin_service]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
@@ -171,7 +196,9 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
@@ -183,7 +210,10 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test", "[limiter]"],
|
||||
"args": [
|
||||
"test",
|
||||
"[limiter]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
@@ -203,7 +233,9 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
@@ -215,7 +247,10 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["test", "[mbo]"],
|
||||
"args": [
|
||||
"test",
|
||||
"SingleWriterConcurrentVector"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
@@ -240,14 +275,16 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "SparkVM attach",
|
||||
"type": "cppdbg",
|
||||
"request": "attach",
|
||||
@@ -255,16 +292,18 @@
|
||||
"processId": "${command:pickRemoteProcess}",
|
||||
"MIMode": "gdb",
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
},
|
||||
{
|
||||
"name": "Python",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
@@ -274,12 +313,59 @@
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"envFile": "${workspaceRoot}/.env",
|
||||
"args": [ "DEBUG" ],
|
||||
"args": [
|
||||
"DEBUG"
|
||||
],
|
||||
"debugOptions": [
|
||||
"WaitOnAbnormalExit",
|
||||
"WaitOnNormalExit",
|
||||
"RedirectOutput"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Remote Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickRemoteProcess}",
|
||||
"pipeTransport": {
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/home/bts/vsdbg/vsdbg",
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"quoteArgs": true
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
},
|
||||
"justMyCode": true
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Remote Launch",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "/usr/bin/dotnet",
|
||||
"args": [
|
||||
"sparkcmd.dll",
|
||||
"test",
|
||||
"test_join_create_and_takeout_market"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server",
|
||||
"pipeTransport": {
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/home/bts/vsdbg/vsdbg",
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"quoteArgs": true
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
},
|
||||
"justMyCode": false
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_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/bts_spark_sdk/Client/Overview.md;content/bts_spark_sdk/Client/Threading.md;content/bts_spark_sdk/Client/_index.md;content/bts_spark_sdk/Server/_index.md;content/bts_spark_sdk/_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)
|
||||
@@ -7,7 +7,6 @@ zola_path=$(readlink -f "../../externals/linux/bin/zola/zola")
|
||||
|
||||
mkdir -p $temp_path
|
||||
cp -r ./* $temp_path
|
||||
cp -r ../../src/sdk/doc/* $temp_path/content/bts_spark_sdk/Server/
|
||||
|
||||
cd $temp_path
|
||||
$zola_path build -u '/docs/' -o $1 >$tmpfile 2>&1
|
||||
|
||||
@@ -4,6 +4,171 @@ 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
|
||||
|
||||
|
||||
## 2.0.2
|
||||
### User Visible Changes
|
||||
- Middle click the ladder price column to show the orders for that price level
|
||||
- Middle click the bid or ask column in the ladder no longer sends an order
|
||||
|
||||
### Internal changes
|
||||
- Improved market by order performance under heavy load
|
||||
- Fixed a concurrency bug in SIM
|
||||
|
||||
## 2.0.0
|
||||
### User Visible Changes
|
||||
- Support for attaching and detaching algos to existing orders
|
||||
- Support for display and update of all parameters in running algos
|
||||
- Streamlining of order badge decorations in ladder
|
||||
- New order search/filtering algorithm for order book
|
||||
- Simpler representation of complex algo chains in order book and mini order book
|
||||
- Visual and usability improvements to mini order book
|
||||
- Streamlining of algo parameter dialog in ladder
|
||||
- New spreader queue holder behavior
|
||||
- Visual improvements to the order book
|
||||
- New server side API
|
||||
- Improved admin UI performance
|
||||
- Optimized PIQ rendering
|
||||
### Internal changes
|
||||
- Exchange simulator no longer requires daily sequence number reset
|
||||
- Improved MD resilience to corrupted shared memory
|
||||
- Native orders can now buffer updates/cancels when disconnected or pending first acknowledgment
|
||||
- Improved settings manager tool output
|
||||
- Added diagnostics in the event MD memory corruption is detected
|
||||
- Added systems tests for join,lean, iceberg and spreader orders
|
||||
- Made exchange order recovery more resilient in the presence of database loss
|
||||
|
||||
## 1.6.2
|
||||
### User Visible Changes
|
||||
### Exchange simulator
|
||||
- Exchange simulator now now produce position in queue data
|
||||
|
||||
## 1.6.0
|
||||
### User Visible Changes
|
||||
#### New Features
|
||||
@@ -253,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 ../..
|
||||
|
||||
+1
-1
Submodule docker/linux updated: ce08a5129f...e13bb5d8df.
@@ -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;
|
||||
|
||||
@@ -22,10 +22,15 @@ namespace mbts.ui {
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
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),
|
||||
@@ -35,10 +40,14 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
LetterDKey = nameof(LetterDKey),
|
||||
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PencilGeometryKey = nameof(PencilGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
|
||||
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
@@ -115,6 +124,7 @@ namespace mbts.ui {
|
||||
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
|
||||
public static ImageSource AttachAlgos => create_icon(AttachAlgosKey);
|
||||
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
|
||||
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
|
||||
|
||||
@@ -138,10 +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);
|
||||
@@ -158,6 +172,8 @@ namespace mbts.ui {
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
|
||||
public static ImageSource RubberStamp => create_icon(RubberStampGeometryKey);
|
||||
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
|
||||
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
|
||||
public static ImageSource Star => create_icon(StarGeometryKey);
|
||||
@@ -186,6 +202,19 @@ namespace mbts.ui {
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static ImageSource create_icon_and_rotate(string geometric_path_key, Brush brush = null, Pen pen = null, Transform transform = null, double degrees = 90) {
|
||||
if (brush == null) {
|
||||
brush = Brushes.Black;
|
||||
}
|
||||
var drawing = create_drawing(geometric_path_key, brush, pen, transform);
|
||||
var dg = new DrawingGroup();
|
||||
dg.Children.Add(drawing);
|
||||
dg.Transform = new RotateTransform(degrees);
|
||||
var rv = new DrawingImage(dg);
|
||||
rv.Freeze();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static Drawing create_drawing(string geometric_path_key) =>
|
||||
create_drawing(geometric_path_key, Brushes.Black);
|
||||
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.AttachAlgosKey}">
|
||||
M10.519881,14.945064C11.300896,14.955074,12.079896,15.102078,12.819896,15.386075L10.878893,17.326085C9.6458764,17.201085,8.3688531,17.591071,7.4258485,18.533091L3.5977991,22.360102C1.9327704,24.026093 1.9317786,26.736124 3.5977991,28.402117 5.2638197,30.068108 7.9748359,30.068108 9.6398644,28.402117L13.467914,24.575106C14.410919,23.632109,14.800938,22.355097,14.675937,21.121083L16.615963,19.180096C17.523962,21.546103,17.034943,24.325104,15.126931,26.234108L11.298882,30.061119C8.7138591,32.64612 4.5238051,32.64612 1.9387672,30.062127 -0.64625573,27.477126 -0.64625573,23.287106 1.9387672,20.701098L5.7668166,16.875065C7.077837,15.564055,8.8018727,14.923061,10.519881,14.945064z M19.825993,10.418039L21.580012,12.17305 12.089907,21.665091 10.333873,19.911087z M25.383069,0C27.077088,3.0538786E-08 28.771109,0.64599859 30.063117,1.9390028 32.647133,4.5230274 32.647133,8.7120385 30.062109,11.298047L26.235068,15.125058C24.327055,17.033085,21.547022,17.52207,19.181973,16.614077L21.121998,14.674067C22.356022,14.799068,23.633046,14.409051,24.57605,13.466054L28.403094,9.6400513C30.068121,7.9740295 30.069129,5.2630205 28.403094,3.5980362 26.738064,1.9320143 24.027033,1.9320143 22.362034,3.5980362L18.533986,7.4240398C17.591958,8.3670363,17.201968,9.643042,17.32697,10.877056L15.385937,12.817066C14.478913,10.452035,14.968939,7.6730337,16.875945,5.7660436L20.703019,1.9390028C21.995026,0.64599859,23.689047,3.0538786E-08,25.383069,0z
|
||||
</sys:String>
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DetachAlgosKey}">
|
||||
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
|
||||
</sys:String>
|
||||
|
||||
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/gregor-cresnar
|
||||
Ttile: Add free icon
|
||||
@@ -40,6 +48,25 @@
|
||||
</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
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
|
||||
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
|
||||
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
|
||||
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
|
||||
C59.518,47.514,47.514,59.518,32.759,59.518z
|
||||
</sys:String>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/smashicons
|
||||
Ttile: File free icon
|
||||
@@ -88,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
|
||||
@@ -271,6 +301,10 @@
|
||||
M0,0L25.194,16 0,32z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.RubberStampGeometryKey}">
|
||||
M1.8950181,29.607025L24.423994,29.607025 24.423994,31.103027C24.423994,31.601013,24.025009,32,23.526993,32L2.7920208,32C2.2929976,32,1.8950181,31.601013,1.8950181,31.103027z M0,19.838013L26.218001,19.838013 26.218001,27.813019 0,27.813019z M12.960014,0C16.549,0 19.538988,2.9909973 19.538988,6.57901 19.538988,8.0750122 19.041002,9.3710022 18.243,10.46701L18.043995,10.766022C17.345997,11.863007 16.647999,12.76001 16.647999,12.76001 15.052999,15.651001 15.253011,17.745026 15.253011,17.745026L10.667015,17.745026C10.667015,17.745026 10.866997,15.651001 9.2719967,12.76001 9.2719967,12.76001 8.5739996,11.863007 7.8760016,10.766022L7.6759898,10.46701 7.5769908,10.368011C6.7790176,9.3710022 6.3810074,7.9750061 6.3810074,6.57901 6.3810074,2.9909973 9.2719967,0 12.960014,0z
|
||||
</sys:String>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/smashicons
|
||||
Ttile: File free icon
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
@@ -204,13 +204,15 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) =>
|
||||
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.OrderBookWindowFactory.OrderBookStartupData(
|
||||
new bts.shell.OrderBookWindowFactory.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
}));
|
||||
void _show_order_book(Side side) {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_order_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
@@ -7,7 +9,6 @@ using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
@@ -22,13 +23,12 @@ namespace mbts.spark {
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
|
||||
: 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));
|
||||
@@ -40,7 +40,7 @@ namespace mbts.spark {
|
||||
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -58,12 +58,12 @@ namespace mbts.spark {
|
||||
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.md.security.HasIce ? r.md.security.Ice.MarketDescription : string.Empty, nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.md.security.HasIce ? r.md.security.Ice.ClearedAlias : string.Empty, nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.md.security.HasIce ? r.md.security.Ice.ProductName : string.Empty, nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.md.security.HasIce ? r.md.security.Ice.HubAlias : string.Empty, nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.md.security.HasIce ? r.md.security.Ice.StripName : string.Empty, nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.md.security.HasIce ? r.md.security.Ice.MicCode : string.Empty, nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
|
||||
//
|
||||
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
|
||||
@@ -81,7 +81,7 @@ namespace mbts.spark {
|
||||
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, DefaultSelectionHighlight, h_alignment);
|
||||
|
||||
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UShortMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
|
||||
|
||||
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
|
||||
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null, h_alignment);
|
||||
@@ -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)
|
||||
);
|
||||
@@ -180,6 +179,40 @@ namespace mbts.spark {
|
||||
return grid_settings_manager.create();
|
||||
}
|
||||
|
||||
public static IComparer<Row> get_column_comparer(Columns column) {
|
||||
switch (column) {
|
||||
case Columns.Security: return wrap(r => r.security);
|
||||
case Columns.SecurityKey: return wrap(r => r.security.Id);
|
||||
case Columns.Volume: return wrap(r => r.volume);
|
||||
case Columns.Last: return wrap(r => r.last_price);
|
||||
case Columns.LastSize: return wrap(r => r.last_size);
|
||||
case Columns.Position: return wrap(r => r.position);
|
||||
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
|
||||
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
|
||||
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
|
||||
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
|
||||
case Columns.Bid: return wrap(r => r.bid_price);
|
||||
case Columns.Ask: return wrap(r => r.ask_price);
|
||||
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
|
||||
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
|
||||
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
|
||||
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
|
||||
case Columns.Settlement: return wrap(r => r.settlement);
|
||||
case Columns.DayChange: return wrap(r => r.day_change);
|
||||
case Columns.SessionLow: return wrap(r => r.session_low);
|
||||
case Columns.SessionHigh: return wrap(r => r.session_high);
|
||||
case Columns.MarketDescription: return wrap(r => r.market_description);
|
||||
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
|
||||
case Columns.ProductDescription: return wrap(r => r.security.product_name());
|
||||
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
|
||||
case Columns.StripName: return wrap(r => r.security.strip_name());
|
||||
case Columns.MicCode: return wrap(r => r.security.mic_code());
|
||||
default: return null;
|
||||
}
|
||||
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
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;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -25,6 +29,9 @@ namespace mbts.spark {
|
||||
MarketViewWindowFactory.StartupData _state;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _subscriptions;
|
||||
SecurityCriteria
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
@@ -32,6 +39,7 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
@@ -42,12 +50,53 @@ namespace mbts.spark {
|
||||
public override MarketViewWindowFactory.StartupData SaveState =>
|
||||
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
|
||||
|
||||
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
SecurityCriteria _selected_security_criteria {
|
||||
get {
|
||||
if (_selected_security_criteria_backing == null) {
|
||||
if (_sorted_selected_security_criteria_backing != null) {
|
||||
_selected_security_criteria_backing = _sorted_selected_security_criteria_backing;
|
||||
} else if (_vm != null)
|
||||
_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Select(r => r.security));
|
||||
}
|
||||
return _selected_security_criteria_backing;
|
||||
}
|
||||
}
|
||||
SecurityCriteria _sorted_selected_security_criteria {
|
||||
get {
|
||||
if (_sorted_selected_security_criteria_backing == null) {
|
||||
if (_vm != null) {
|
||||
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
|
||||
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
|
||||
foreach (var sc in sorting_criteria) {
|
||||
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
|
||||
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
|
||||
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
|
||||
if (col_comp != null) {
|
||||
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
|
||||
}
|
||||
} else {
|
||||
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
var comp = comp_builder.to_comparer();
|
||||
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
}
|
||||
}
|
||||
return _sorted_selected_security_criteria_backing;
|
||||
}
|
||||
}
|
||||
|
||||
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
@@ -55,39 +104,85 @@ namespace mbts.spark {
|
||||
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
|
||||
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
|
||||
_view = new MarketViewView { DataContext = _vm };
|
||||
_view._grid.Sorting += on_grid_sorting;
|
||||
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
|
||||
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
|
||||
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
|
||||
// show time and sales
|
||||
var show_tas_cmd = new DelegateCommand(() => {
|
||||
var sc = _selected_security_criteria;
|
||||
if (sc.is_defined())
|
||||
_bus.post(SparkTags.ShowTimeAndSalesTag, sc);
|
||||
}, _has_selected_rows);
|
||||
var show_tas_actual_cmd = SparkCommands.ShowTimeAndSales;
|
||||
var show_tas_cmd_wrapper = new SparkCommandCommand(show_tas_actual_cmd.title, show_tas_actual_cmd.icon, show_tas_cmd);
|
||||
// show fills
|
||||
var show_fills_cmd = new DelegateCommand(() => {
|
||||
var sc = _selected_security_criteria;
|
||||
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
|
||||
new FillsFilterStates {
|
||||
security = sc.is_defined() ? sc : _state.security_criteria
|
||||
}));
|
||||
});
|
||||
var show_fills_actual_cmd = SparkCommands.ShowFills();
|
||||
var show_fills_cmd_wrapper = new SparkCommandCommand(show_fills_actual_cmd.title, show_fills_actual_cmd.icon, show_fills_cmd, SparkCommands.KeyGestures.ShowFills);
|
||||
// show synthetic create
|
||||
var show_synth_create_cmd = new DelegateCommand(() => {
|
||||
var sc = _sorted_selected_security_criteria;
|
||||
if (sc.is_defined())
|
||||
_bus.post(SparkTags.ShowSyntheticCreateTag, sc);
|
||||
}, () => _vm.selected_rows.Count > 0 && _vm.selected_rows.Count <= 24);
|
||||
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
|
||||
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.complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(_vm.selected_securities_observable(), _ => true,
|
||||
secs => new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
})
|
||||
),
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
|
||||
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),
|
||||
show_fills_cmd_wrapper,
|
||||
show_tas_cmd_wrapper,
|
||||
show_synth_create_cmd_wrapper,
|
||||
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;
|
||||
}))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())));
|
||||
//
|
||||
_vm.selection_changed += on_selection_changed;
|
||||
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
|
||||
() => {
|
||||
_vm.selection_changed -= on_selection_changed;
|
||||
_view._grid.Sorting -= on_grid_sorting;
|
||||
}
|
||||
};
|
||||
|
||||
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
|
||||
// invalidate selected security criteria
|
||||
_selected_security_criteria_backing = null;
|
||||
_sorted_selected_security_criteria_backing = null;
|
||||
show_tas_cmd.RaiseCanExecuteChanged();
|
||||
show_fills_cmd.RaiseCanExecuteChanged();
|
||||
show_synth_create_cmd.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) =>
|
||||
_sorted_selected_security_criteria_backing = null;
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using mbts.co2;
|
||||
using mbts.collections.extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using mbts.persist;
|
||||
using mbts.ui.settings;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
|
||||
@@ -48,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,10 +18,9 @@ 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>, ISlowWindow {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
@@ -48,7 +49,7 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id { get; }
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
|
||||
public override void Dispose() =>
|
||||
@@ -62,26 +63,25 @@ namespace mbts.spark {
|
||||
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
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}"/>
|
||||
@@ -420,7 +431,6 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<c:HelpButton Grid.Column="8" Margin="3,0,0,0" ManPage="bts_spark/spreader/#creating-a-synthetic" />
|
||||
</Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="PART_DataGrid"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui.adorners;
|
||||
@@ -7,7 +8,6 @@ using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<Style TargetType="ScrollViewer">
|
||||
<Setter Property="u:ScrollViewerCorrector.FixNestedScrolling" Value="False"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
@@ -33,8 +33,20 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
Log.notnull(_state);
|
||||
|
||||
@@ -54,27 +66,27 @@ namespace mbts.spark {
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
current_row_connectable_builder.create(observe_row_strategy),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder.complete(
|
||||
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
|
||||
filters = new OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
|
||||
}
|
||||
}
|
||||
), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
), SparkCommands.ShowOrders.complete(() => {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
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;
|
||||
}))
|
||||
@@ -101,16 +113,6 @@ namespace mbts.spark {
|
||||
else _view.DataContext = _vm.value;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
+4
-6
@@ -1,12 +1,10 @@
|
||||
using mbts.ui.spark;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using PriceType = bts.spark.ConditionalQuoting.Types.PriceType;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using bts.shell;
|
||||
using PriceType = bts.spark.ConditionalQuoting.Types.PriceType;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM
|
||||
{
|
||||
class ConditionalQuotingPriceTypeModel : RowModelBase<PriceType>
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using bts.spark;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
partial class SyntheticEditorVM
|
||||
{
|
||||
class QuoteQueueHolderModel : RowModelBase<uint>, IHaveFontParams
|
||||
{
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
public QuoteQueueHolderModel()
|
||||
{
|
||||
}
|
||||
public override string display_name { get; } = "Queue Holder Length";
|
||||
public override string help_text { get; } = "How many levels of queue to hold";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
|
||||
public FontWeight font_weight => FontWeights.SemiBold;
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = _value_from_store(leg_tup);
|
||||
|
||||
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup).NQueueHolderOrders;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value)
|
||||
{
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = value ;
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) != null;
|
||||
|
||||
QuoteParams _get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
@@ -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 " +
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
@@ -72,11 +73,11 @@ namespace mbts.spark {
|
||||
_has_working_orders = backing(nameof(has_working_orders), false);
|
||||
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
|
||||
new SecurityCriteriaClause() {
|
||||
exchanges = EnumHelper.get_values_except(Venue.Bts).ToLArray()
|
||||
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),
|
||||
@@ -133,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);
|
||||
@@ -176,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();
|
||||
|
||||
@@ -199,15 +202,19 @@ namespace mbts.spark {
|
||||
|
||||
var post_hedge_opposite_market_based_cross_threshold = new PostHedgeOppositeMarketVolumeBasedCrossThresholdModel();
|
||||
var post_hedge_opposite_market_based_cross_cross_ticks = new PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel();
|
||||
var is_quote_model = new IsQuoteModel();
|
||||
rows = new IRowModel[] {
|
||||
_quantity_model,
|
||||
_price_mult_model,
|
||||
new IsQuoteModel(),
|
||||
is_quote_model,
|
||||
//new EnableHedgeModel(), //TAHA_SPREADER
|
||||
new HedgeRatioModel(),
|
||||
//new MinLeanQtyModel(), //TAHA_SPREADER
|
||||
//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,
|
||||
@@ -318,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,10 +1,9 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -169,12 +168,13 @@ namespace mbts.spark {
|
||||
void _show_ladder() =>
|
||||
_bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book() =>
|
||||
_bus.post(SparkTags.ShowOrderBookTag, new OrderBookWindowFactory.OrderBookStartupData {
|
||||
filters = new OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
}
|
||||
});
|
||||
void _show_order_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,6 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
@@ -559,85 +557,27 @@
|
||||
</Grid>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding content_source}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
Grid.Column="3"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
SelectedItem="{Binding selected_definition.algo_root}"
|
||||
ItemsSource="{Binding selected_definition.algos_view}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Width="140"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding selected_definition.algo_root}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:ArrayExtension Type="{x:Type Button}">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
</Button>
|
||||
</x:ArrayExtension>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
/>
|
||||
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<StackPanel.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
|
||||
</StackPanel.Margin>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -1,8 +1,6 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
@@ -18,26 +16,11 @@ namespace mbts.ui.spark {
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
set_controller();
|
||||
// local methods
|
||||
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Windows;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string
|
||||
NAME = "Algo Studio",
|
||||
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
|
||||
@@ -37,7 +37,7 @@ namespace mbts.spark {
|
||||
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
|
||||
_model = model;
|
||||
_state = state;
|
||||
thread_init();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
|
||||
}
|
||||
|
||||
public override StartupData SaveState => _state = _vm.create();
|
||||
@@ -45,12 +45,12 @@ namespace mbts.spark {
|
||||
public override string window_type => NAME;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public string thread_id => NAME;
|
||||
public override double MinWidth =>
|
||||
SPARK_MENU_COLLAPSED_WIDTH +
|
||||
LAUNCHER_BUTTON_BORDER_WIDTH +
|
||||
DEFAULT_CONTENT_WIDTH;
|
||||
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
|
||||
public string thread_id { get; }
|
||||
|
||||
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
struct AlgoData : IEquatable<AlgoData> {
|
||||
@@ -36,4 +37,31 @@ namespace mbts.spark {
|
||||
public static bool operator ==(AlgoData a, AlgoData b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoData a, AlgoData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct AlgoDataVM : IEquatable<AlgoDataVM> {
|
||||
public readonly AlgoData data;
|
||||
public readonly AlgoDefinitionMetadata? meta;
|
||||
|
||||
public AlgoDataVM(AlgoData data, AlgoDefinitionMetadata? meta = null) {
|
||||
this.data = data;
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
public string name => data.name;
|
||||
public Brush badge_fill => OrderUtils.badge_fill(meta, OrderDataHelper.get_metadata(data.value.CustomCase));
|
||||
public AlgoDisplayGroup group => data.key.display_group();
|
||||
|
||||
public override string ToString() =>
|
||||
data.name;
|
||||
|
||||
public static AlgoDataVM from_data(AlgoData data, AlgoDefinitionMetadata? meta) =>
|
||||
new AlgoDataVM(data, meta);
|
||||
|
||||
public static AlgoDataVM from_data((AlgoData algo, AlgoDefinitionMetadata? meta) tup) =>
|
||||
new AlgoDataVM(tup.algo, tup.meta);
|
||||
|
||||
public bool Equals(AlgoDataVM other) => data == other.data && meta == other.meta;
|
||||
public override bool Equals(object obj) => obj is AlgoDataVM other && Equals(other);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(data.GetHashCode(), meta.GetHashCode());
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,91 @@
|
||||
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]
|
||||
struct AlgoID : IEquatable<AlgoID> {
|
||||
[ProtoMember(1)]
|
||||
readonly CustomOrderDefinition.CustomOneofCase? _factory_key;
|
||||
[ProtoMember(2)]
|
||||
readonly string _dynamic_key;
|
||||
[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;
|
||||
_persisted_key = null;
|
||||
}
|
||||
|
||||
public AlgoID(string dynamic_key) {
|
||||
_factory_key = null;
|
||||
_dynamic_key = dynamic_key;
|
||||
_persisted_key = null;
|
||||
}
|
||||
|
||||
public AlgoID(uint persisted_key) {
|
||||
_factory_key = null;
|
||||
_dynamic_key = null;
|
||||
_persisted_key = persisted_key;
|
||||
}
|
||||
|
||||
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, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
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 other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
_factory_key?.GetHashCode() ?? 0,
|
||||
_dynamic_key?.GetHashCode() ?? 0,
|
||||
_persisted_key?.GetHashCode() ?? 0);
|
||||
|
||||
public static bool operator ==(AlgoID a, AlgoID b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
|
||||
struct AlgoKey : IEquatable<AlgoKey> {
|
||||
readonly AlgoPersistedKey? _persisted_key;
|
||||
readonly AlgoNameStructureKey? _factory_key;
|
||||
@@ -83,4 +165,4 @@ namespace mbts.spark {
|
||||
|
||||
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -10,12 +11,6 @@ namespace mbts.spark {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
|
||||
: this(definition.CustomCase.type_id(), definition) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
|
||||
: this(name, new AlgoStructureKey(definition)) { }
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
@@ -31,71 +26,149 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
[ProtoMember(1)]
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
|
||||
int[] s = new[] { (int)definition.CustomCase };
|
||||
_build_structure(definition, ref s, 1);
|
||||
_structure = s;
|
||||
}
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public CustomOrderDefinitionImmutable build_def() {
|
||||
int pos = 0;
|
||||
var mdef = new CustomOrderDefinition();
|
||||
_build_def(_structure, ref pos, ref mdef);
|
||||
return new CustomOrderDefinitionImmutable(mdef);
|
||||
}
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) =>
|
||||
_structure.Length == other._structure.Length &&
|
||||
_structure.SequenceEqual(other._structure);
|
||||
public bool Equals(AlgoStructureKey other) {
|
||||
if (_structure == null) {
|
||||
return other._structure == null;
|
||||
} else {
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
return other._structure != null && _structure.Length == other._structure.Length && _structure.SequenceEqual(other._structure);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoStructureKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(_structure);
|
||||
_structure == null ? 0 : EqualityComparer.aggregate_hash(_structure);
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
|
||||
_build_structure(definition.CloneToMutable(), ref structure, pos);
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
int del_mask = 1 << (31 - di);
|
||||
structure[pos++] = (int)child_algo.CustomCase | del_mask;
|
||||
_build_structure(child_algo, ref structure, pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ArrayHelper.trim(ref structure, pos);
|
||||
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static 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));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
|
||||
for (int i = pos; i < structure.Length; i++) {
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
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];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
int mask = 1 << (31 - di);
|
||||
if ((structure[i] & mask) == mask) {
|
||||
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
|
||||
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
|
||||
pos++;
|
||||
_build_def(structure, ref pos, ref child_algo);
|
||||
cad[di].assign_child(algo, child_algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -70,7 +70,7 @@ namespace mbts.spark {
|
||||
is_hidden.GetHashCode(),
|
||||
abbreviation == null ? 0 : abbreviation.GetHashCode(),
|
||||
badge_color?.GetHashCode() ?? 0,
|
||||
EqualityComparer.aggregate_hash_limited(_favorite_templates));
|
||||
_favorite_templates == null ? 0 : EqualityComparer.aggregate_hash_limited(_favorite_templates));
|
||||
|
||||
public static bool operator ==(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => !a.Equals(b);
|
||||
|
||||
@@ -1,24 +1,37 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class AlgoCollectionHelper {
|
||||
public static readonly IComparer<AlgoDefinitionVM> algo_definition_vm_comparer =
|
||||
new ComparerBuilder<AlgoDefinitionVM>(
|
||||
new [] {
|
||||
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_modes),
|
||||
new[] {
|
||||
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_groups),
|
||||
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_names)
|
||||
}).to_comparer().to_safe();
|
||||
|
||||
public static readonly IComparer<AlgoData> algo_data_comparer =
|
||||
new ComparerBuilder<AlgoData>(
|
||||
new[] {
|
||||
Comparer<AlgoData>.Create(_CompareUtils.compare_groups),
|
||||
Comparer<AlgoData>.Create(_CompareUtils.compare_names)
|
||||
}
|
||||
).to_comparer();
|
||||
|
||||
public static readonly IComparer<AlgoDataVM> algo_data_vm_comparer =
|
||||
algo_data_comparer.wrap<AlgoData, AlgoDataVM>(vm => vm.data);
|
||||
|
||||
public static readonly IComparer<IAlgoVM> algo_vm_comparer =
|
||||
new ComparerBuilder<IAlgoVM>(
|
||||
new [] {
|
||||
Comparer<IAlgoVM>.Create(_CompareUtils.compare_modes),
|
||||
new[] {
|
||||
Comparer<IAlgoVM>.Create(_CompareUtils.compare_groups),
|
||||
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
|
||||
}).to_comparer().to_safe();
|
||||
|
||||
@@ -34,29 +47,75 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
static class _CompareUtils {
|
||||
public static int compare_modes(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
|
||||
_compare_modes(a.key.mode, b.key.mode);
|
||||
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
|
||||
foreach (var d in update.removed)
|
||||
target.Remove(d.key);
|
||||
foreach (var d in update.changed) {
|
||||
var vm = target[d.key];
|
||||
if (vm is AlgoVMWrapper wrapper) {
|
||||
wrapper.value.load(d);
|
||||
} else vm.load_from(d.value);
|
||||
}
|
||||
foreach (var d in update.added) {
|
||||
if (target.TryGetValue(d.key, out IAlgoVM def)) {
|
||||
def.load_from(d.value);
|
||||
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
|
||||
}
|
||||
}
|
||||
|
||||
public static int compare_modes(IAlgoVM a, IAlgoVM b) =>
|
||||
_compare_modes(a.id.mode, b.id.mode);
|
||||
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
|
||||
algo_param_rows.Select(r => r.param).commit_all();
|
||||
|
||||
public static void commit_all(this IEnumerable<IAlgoParamVM> algo_params) {
|
||||
foreach (var p in algo_params)
|
||||
p.mark_committed();
|
||||
}
|
||||
|
||||
public static void revert_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
|
||||
algo_param_rows.Select(r => r.param).revert_all();
|
||||
|
||||
public static void revert_all(this IEnumerable<IAlgoParamVM> algo_params) {
|
||||
foreach (var p in algo_params)
|
||||
p.revert();
|
||||
}
|
||||
|
||||
public static AlgoDisplayGroup display_group(CustomOrderDefinition.CustomOneofCase order_type) => order_type.is_bts_algo_type() ? AlgoDisplayGroup.BTS : AlgoDisplayGroup.Exchange;
|
||||
|
||||
public static AlgoDisplayGroup display_group(this AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory: return display_group(key.factory.structure.order_type());
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
}
|
||||
return AlgoDisplayGroup.BTS;
|
||||
}
|
||||
|
||||
static class _CompareUtils {
|
||||
static AlgoDisplayGroup[] _sorted_groups = new[] {
|
||||
AlgoDisplayGroup.Exchange,
|
||||
AlgoDisplayGroup.BTS,
|
||||
AlgoDisplayGroup.Studio,
|
||||
AlgoDisplayGroup.API
|
||||
};
|
||||
|
||||
public static int compare_groups(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
|
||||
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
|
||||
|
||||
public static int compare_groups(AlgoData a, AlgoData b) =>
|
||||
Array.IndexOf(_sorted_groups, a.key.display_group()).CompareTo(Array.IndexOf(_sorted_groups, b.key.display_group()));
|
||||
|
||||
public static int compare_groups(IAlgoVM a, IAlgoVM b) =>
|
||||
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
|
||||
|
||||
public static int compare_names(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
|
||||
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
|
||||
|
||||
public static int compare_names(AlgoData a, AlgoData b) =>
|
||||
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
|
||||
|
||||
public static int compare_names(IAlgoVM a, IAlgoVM b) =>
|
||||
DisplayNameHelper.compare_alpha_numeric(a.display_name, b.display_name);
|
||||
|
||||
static int _compare_modes(AlgoKey.Mode a, AlgoKey.Mode b) {
|
||||
// Factory mode is 'lowest', consider all other modes equivalent.
|
||||
if (a == AlgoKey.Mode.Factory) {
|
||||
if (b != AlgoKey.Mode.Factory)
|
||||
return -1;
|
||||
} else if (b == AlgoKey.Mode.Factory) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.color_picker;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
@@ -25,7 +27,8 @@ namespace mbts.spark {
|
||||
None = 0,
|
||||
Exchange = 1,
|
||||
Spreader = 1 << 1,
|
||||
DefaultPlaceholder = 1 << 2
|
||||
Attachable = 1 << 2,
|
||||
DefaultPlaceholder = 1 << 3
|
||||
}
|
||||
|
||||
class AlgoDefinitionVM : DisposableViewModelBase, IAlgoStudioContentVM, IAlgoSelector, IEquatable<AlgoDefinitionVM> {
|
||||
@@ -46,7 +49,8 @@ namespace mbts.spark {
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
@@ -54,29 +58,43 @@ namespace mbts.spark {
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format);
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
if (connect)
|
||||
rv.connect();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
_model = model;
|
||||
_permissions_mask.mask = init_permissions;
|
||||
_permissions_mask.raw = init_permissions;
|
||||
var resources = new CompositeDisposable();
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_values)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
else if (model.algo_model.templates_enabled()) {
|
||||
resources.Add(_templates.Subscribe((s, e) => {
|
||||
if (_templates.Count == 0) {
|
||||
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
} else {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
}
|
||||
}));
|
||||
}
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
|
||||
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
|
||||
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
_editing_permissions = _get_permissions();
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
var resources = new CompositeDisposable();
|
||||
// init backing props
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
@@ -87,7 +105,7 @@ namespace mbts.spark {
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
@@ -113,28 +131,16 @@ namespace mbts.spark {
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
|
||||
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
|
||||
if (vm == null)
|
||||
return Disposable.empty;
|
||||
if (vm is ILazyAlgo la) {
|
||||
bool synced_with_loaded = false;
|
||||
return vm.subscribe_definition().Subscribe(_ => {
|
||||
if (la.is_loaded) {
|
||||
if (!synced_with_loaded) {
|
||||
synced_with_loaded = true;
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
} else {
|
||||
_refresh_on_local_changes.execute();
|
||||
}
|
||||
} else {
|
||||
synced_with_loaded = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
|
||||
}),
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
ObservableAutoWrapper.connect_item_hooks(templates, t => new CompositeDisposable(_any_templates_dirty_monitor.join(t), t), true),
|
||||
Disposable.Create(() => {
|
||||
@@ -152,16 +158,23 @@ namespace mbts.spark {
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(f);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}));
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
|
||||
_dispose = resources;
|
||||
on_editing_permissions(_get_permissions());
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
@@ -169,11 +182,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void on_editing_permissions(AlgoEditingPermissions eps) {
|
||||
_editing_permissions = eps;
|
||||
foreach (var vm in algo_roots)
|
||||
vm.set_permissions(eps);
|
||||
}
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||
void on_local_changed() {
|
||||
@@ -194,6 +202,7 @@ namespace mbts.spark {
|
||||
public string name { get => _name.Value; set => _name.set(value); }
|
||||
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
|
||||
public ObservableCollection<IAlgoVM> algo_roots => _algo_roots;
|
||||
public IDictionary<AlgoKey, IAlgoVM> algo_roots_index => _algo_roots;
|
||||
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
|
||||
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
|
||||
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
|
||||
@@ -210,6 +219,20 @@ namespace mbts.spark {
|
||||
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_factory_definition => key.mode == AlgoKey.Mode.Factory;
|
||||
public AlgoDisplayGroup group {
|
||||
get {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (algo_root != null)
|
||||
return algo_root.group;
|
||||
break;
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
}
|
||||
return AlgoDisplayGroup.BTS;
|
||||
}
|
||||
}
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
||||
@@ -230,13 +253,51 @@ namespace mbts.spark {
|
||||
// visual states
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
|
||||
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
|
||||
|
||||
#region IAlgoSelector
|
||||
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
|
||||
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed {
|
||||
add => algo_root_changed += value;
|
||||
remove => algo_root_changed -= value;
|
||||
}
|
||||
event PropertyChange<IAlgoVM> IAlgoParamVM<IAlgoVM>.value_changed {
|
||||
add => algo_root_changed += value;
|
||||
remove => algo_root_changed -= value;
|
||||
}
|
||||
event PropertyChange<AlgoParamPermissions> IAlgoParamVM.permissions_changed { add { } remove { } }
|
||||
event Action<bool> IAlgoParamVM.is_dirty_changed { add { } remove { } }
|
||||
|
||||
string IAlgoParamVM.id => key.ToString();
|
||||
string IAlgoParamVM.display_name => "Algo";
|
||||
|
||||
ObservableKeyedCollection<IAlgoVM, AlgoKey> IAlgoSelector.algos => _algo_roots;
|
||||
IAlgoVM IAlgoSelector.selected_algo { get => algo_root; set => algo_root = value; }
|
||||
IAlgoVM IAlgoSelector.selected_algo {
|
||||
get => algo_root;
|
||||
set => algo_root = value;
|
||||
}
|
||||
IAlgoVM IAlgoParamVM<IAlgoVM>.value {
|
||||
get => algo_root;
|
||||
set => algo_root = value;
|
||||
}
|
||||
bool IAlgoParamVM.has_value => _algo_root != null;
|
||||
bool IAlgoParamVM.is_enabled { get; set; } = true;
|
||||
object IAlgoParamVM.value {
|
||||
get => algo_root;
|
||||
set => algo_root = (IAlgoVM)value;
|
||||
}
|
||||
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
|
||||
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
|
||||
|
||||
void IAlgoSelector.load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) =>
|
||||
_load_algo_data(algo_data, default_algo);
|
||||
_load_algo_data(algo_data, default_algo);
|
||||
|
||||
void IAlgoParamVM.revert() { }
|
||||
void IAlgoParamVM.mark_committed() { }
|
||||
|
||||
IObservable<IAlgoVM> IAlgoSelector.subscribe_selected_algo() =>
|
||||
this.observe_algo_root();
|
||||
#endregion
|
||||
|
||||
#region IObserver (algo data...)
|
||||
@@ -248,7 +309,7 @@ namespace mbts.spark {
|
||||
|
||||
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
var orig_algo = algo_root?.id;
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
|
||||
if (!try_select(orig_algo) && !try_select(default_algo))
|
||||
algo_root = algo_roots.FirstOrDefault();
|
||||
//
|
||||
@@ -298,6 +359,47 @@ namespace mbts.spark {
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_from(def);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_from(def);
|
||||
algo_root = vm;
|
||||
} else {
|
||||
algo_roots.Add(vm = _mk_algo_root(def));
|
||||
algo_root = vm;
|
||||
}
|
||||
}
|
||||
|
||||
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> defs) {
|
||||
/// don't load composite algos; we don't support recursive composites (<see cref="OrderUtils.actual(CustomOrderDefinitionImmutable)"/>)
|
||||
if (defs is IList<CustomOrderDefinitionImmutable> def_list) {
|
||||
def_list = def_list.WrapSelector(d => d.actual(), null);
|
||||
} else {
|
||||
def_list = defs.Select(d => d.actual()).ToList();
|
||||
}
|
||||
switch (def_list.Count) {
|
||||
case 0: algo_root = null; break;
|
||||
case 1: load_def(def_list[0]); break;
|
||||
default:
|
||||
var def = def_list[0];
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_many(def_list);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_many(def_list);
|
||||
algo_root = vm;
|
||||
} else {
|
||||
OrderTypeVMFactory.create(key, def.display_name(), def.CustomCase, is_expanded: true);
|
||||
algo_roots.Add(vm = _mk_algo_root(def));
|
||||
vm.load_many(def_list);
|
||||
algo_root = vm;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(AlgoData data) {
|
||||
_assert_can_load(data.key);
|
||||
if (algo_root != null)
|
||||
@@ -309,7 +411,7 @@ namespace mbts.spark {
|
||||
|
||||
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
|
||||
public AlgoDefinitionVM to_persisted(AlgoData def) =>
|
||||
new AlgoDefinitionVM(_model, def);
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
@@ -322,16 +424,6 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
|
||||
|
||||
AlgoEditingPermissions _get_permissions() {
|
||||
var permissions = AlgoEditingPermissions.None;
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
|
||||
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
|
||||
}
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
|
||||
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
return permissions;
|
||||
}
|
||||
|
||||
void _assert_can_load(AlgoKey data_key) {
|
||||
if (key.mode != data_key.mode)
|
||||
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
|
||||
@@ -359,10 +451,14 @@ namespace mbts.spark {
|
||||
_committed = _version.value;
|
||||
_is_dirty.refresh();
|
||||
_refresh_undo_redo();
|
||||
RaisePropertyChanged(nameof(committed_value), nameof(current_value));
|
||||
}
|
||||
|
||||
public Snapshot? committed_value => _committed;
|
||||
public Snapshot current_value => _version.value;
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
@@ -463,5 +559,17 @@ namespace mbts.spark {
|
||||
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
|
||||
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct SnapshotTemplateEqualityComparer : IEqualityComparer<Snapshot> {
|
||||
public bool Equals(Snapshot x, Snapshot y) =>
|
||||
x.abbreviation == y.abbreviation &&
|
||||
x.custom_badge_color == y.custom_badge_color &&
|
||||
x.format == y.format &&
|
||||
x.data.key == y.data.key &&
|
||||
x.data.name == y.data.name &&
|
||||
x.data.value.Equals(y.data.value);
|
||||
|
||||
public int GetHashCode(Snapshot obj) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoParamRowVM : DisposableViewModelBase, DIYGrid.IRowVM, IEditableObject, ITemplateSelectorKey, IEquatable<AlgoParamRowVM> {
|
||||
bool _is_enabled;
|
||||
string _template_key;
|
||||
ICommand _set_value_cmd;
|
||||
|
||||
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
|
||||
this.param = param;
|
||||
this.algo = algo;
|
||||
this.parent = parent;
|
||||
_template_key = template_key;
|
||||
param.permissions_changed += on_permissions_changed;
|
||||
_dispose = Disposable.Create(() => param.permissions_changed -= on_permissions_changed);
|
||||
void on_permissions_changed(AlgoParamPermissions oldval, AlgoParamPermissions newval) => RaisePropertyChanged(nameof(permissions));
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public IAlgoVM algo { get; set; }
|
||||
public IAlgoParamVM param { get; }
|
||||
public AlgoParamPermissions permissions => param.permissions;
|
||||
public AlgoParamRowVM parent { get; }
|
||||
public int level => parent == null ? 0 : parent.level + 1;
|
||||
public string path_disambiguation {
|
||||
get {
|
||||
if (parent == null)
|
||||
return string.Empty;
|
||||
string pd = parent.path_disambiguation;
|
||||
if (parent.algo.order_type == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Cover) {
|
||||
if (pd.Length > 0)
|
||||
pd += ".";
|
||||
pd += parent.param.display_name;
|
||||
}
|
||||
return pd;
|
||||
}
|
||||
}
|
||||
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
|
||||
public ICommand set_value_cmd {
|
||||
get {
|
||||
if (_set_value_cmd == null) {
|
||||
var cmd = new DelegateCommand(v => {
|
||||
var orig_val = param.value;
|
||||
try {
|
||||
param.value = v;
|
||||
} catch (InvalidCastException) {
|
||||
param.value = orig_val;
|
||||
}
|
||||
}, _ => param.permissions == AlgoParamPermissions.ReadWrite);
|
||||
concated_disposable(param.subscribe_permissions().Subscribe(_ => cmd.RaiseCanExecuteChanged()));
|
||||
_set_value_cmd = cmd;
|
||||
}
|
||||
return _set_value_cmd;
|
||||
}
|
||||
}
|
||||
string ITemplateSelectorKey.key => _template_key;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public bool Equals(AlgoParamRowVM other) =>
|
||||
other != null &&
|
||||
param.id == other.param.id &&
|
||||
parent.safe_equals(other.parent);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as AlgoParamRowVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(param.id.GetHashCode(), parent?.GetHashCode() ?? 0);
|
||||
|
||||
#region IEditableObject
|
||||
object _committed_value;
|
||||
void IEditableObject.BeginEdit() => _committed_value = param.value;
|
||||
void IEditableObject.EndEdit() => _committed_value = null;
|
||||
void IEditableObject.CancelEdit() {
|
||||
if (_committed_value != null)
|
||||
param.value = _committed_value;
|
||||
_committed_value = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
class AlgoSelectorParamRowVM : AlgoParamRowVM {
|
||||
readonly IAlgoSelector _selector;
|
||||
|
||||
public AlgoSelectorParamRowVM(IAlgoVM algo_vm, IAlgoSelector selector, AlgoParamRowVM parent) : base(algo_vm, selector, parent) =>
|
||||
_selector = selector;
|
||||
|
||||
public ObservableKeyedCollection<IAlgoVM, AlgoKey> algos => _selector.algos;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
|
||||
IObserver<SecurityMD?>,
|
||||
IObserver<SecurityImmutable?>,
|
||||
IObserver<OrdersModel>,
|
||||
IObserver<AlgoEditingPermissions> {
|
||||
static readonly HashSet<string>
|
||||
_algo_root_echo_props = new HashSet<string> {
|
||||
nameof(is_expanded),
|
||||
nameof(editing_permissions),
|
||||
nameof(definition),
|
||||
nameof(display_name)
|
||||
};
|
||||
static readonly IReadOnlyDictionary<string, string>
|
||||
_value_echo_props = new Dictionary<string, string> {
|
||||
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
|
||||
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
|
||||
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
|
||||
};
|
||||
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
|
||||
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
|
||||
readonly DisposableContainer _value_hooks = new DisposableContainer();
|
||||
Action _definition_changed = () => { };
|
||||
_ObservedData? _data_cache;
|
||||
AlgoDefinitionVM _value;
|
||||
|
||||
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
|
||||
value = algo_definition_vm;
|
||||
_dispose = new CompositeDisposable(_value_hooks) {
|
||||
_child_algo_selectors.clear_collections,
|
||||
_algo_params.clear_collections,
|
||||
() => _value?.Dispose()
|
||||
};
|
||||
}
|
||||
|
||||
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
|
||||
|
||||
public AlgoDefinitionVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (_value != value) {
|
||||
_value?.Dispose();
|
||||
_value = value;
|
||||
_value_hooks.replace(_set_hooks(value));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
public AlgoKey id => _value.key;
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
|
||||
public OrderMetadata meta {
|
||||
get {
|
||||
if (_value != null) {
|
||||
if (_value.custom_badge_color.enabled)
|
||||
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
|
||||
else if (_value.algo_root != null)
|
||||
return _value.algo_root.meta;
|
||||
}
|
||||
return OrderDataHelper.get_metadata(order_type);
|
||||
}
|
||||
}
|
||||
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
|
||||
public bool can_expand => _algo_root?.can_expand ?? false;
|
||||
public bool is_expanded {
|
||||
get => _value.algo_root?.is_expanded ?? false;
|
||||
set {
|
||||
if (_value != null)
|
||||
_value.is_expanded = value;
|
||||
}
|
||||
}
|
||||
public string display_name => _value?.name ?? string.Empty;
|
||||
public string abbreviation => _value?.abbreviation;
|
||||
public AlgoDisplayGroup group => _value?.group ?? AlgoDisplayGroup.BTS;
|
||||
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors_view {
|
||||
get {
|
||||
_value?.algo_root?.ensure_init();
|
||||
return _child_algo_selectors;
|
||||
}
|
||||
}
|
||||
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public dynamic children_vm => _algo_root?.children_vm;
|
||||
|
||||
IAlgoVM _algo_root => _value?.algo_root;
|
||||
|
||||
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
|
||||
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
|
||||
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
|
||||
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
|
||||
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
|
||||
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
|
||||
|
||||
IDisposable _set_hooks(AlgoDefinitionVM vm) {
|
||||
var algo_root_subs = new DisposableContainer();
|
||||
vm.PropertyChanged += on_value_prop_changed;
|
||||
return new CompositeDisposable(
|
||||
vm.observe_algo_root().Subscribe(r => {
|
||||
algo_root_subs.replace(null);
|
||||
_child_algo_selectors.clear_collections();
|
||||
_algo_params.clear_collections();
|
||||
if (r != null) {
|
||||
if (_data_cache.HasValue) {
|
||||
_data_cache.Value.forward_to(r);
|
||||
}
|
||||
_child_algo_selectors.add_collection(r.child_algo_selectors);
|
||||
_algo_params.add_collection(r.algo_params);
|
||||
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
|
||||
algo_root_subs.replace(new CompositeDisposable(
|
||||
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
|
||||
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
|
||||
});
|
||||
}
|
||||
RaisePropertyChanged(nameof(group));
|
||||
}),
|
||||
algo_root_subs) {
|
||||
() => vm.PropertyChanged -= on_value_prop_changed
|
||||
};
|
||||
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_algo_root_echo_props.Contains(e.PropertyName))
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
|
||||
RaisePropertyChanged(wrapper_prop);
|
||||
}
|
||||
}
|
||||
|
||||
// permissions
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
|
||||
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
// model
|
||||
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
|
||||
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<OrdersModel>.OnError(Exception error) { }
|
||||
void IObserver<OrdersModel>.OnCompleted() { }
|
||||
// security
|
||||
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
|
||||
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityImmutable?>.OnCompleted() { }
|
||||
// security md
|
||||
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
|
||||
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<SecurityMD?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityMD?>.OnCompleted() { }
|
||||
|
||||
struct _ObservedData {
|
||||
public readonly AlgoEditingPermissions permissions;
|
||||
public readonly OrdersModel model;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly SecurityMD? security_md;
|
||||
|
||||
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
|
||||
this.permissions = permissions;
|
||||
this.model = model;
|
||||
this.security = security;
|
||||
this.security_md = security_md;
|
||||
}
|
||||
|
||||
public void forward_to(IAlgoVM recipient) {
|
||||
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
|
||||
if (model != null)
|
||||
(recipient as IObserver<OrdersModel>)?.OnNext(model);
|
||||
if (security_md.HasValue)
|
||||
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
|
||||
if (security.HasValue)
|
||||
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
|
||||
}
|
||||
|
||||
public _ObservedData with(AlgoEditingPermissions? permissions) =>
|
||||
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(OrdersModel model) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(SecurityImmutable? security) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(SecurityMD? security_md) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
}
|
||||
}
|
||||
}
|
||||
+622
@@ -0,0 +1,622 @@
|
||||
<UserControl
|
||||
x:Name="me"
|
||||
x:Class="mbts.ui.spark.AlgoEditorGridView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/DataGrid.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<sys:String x:Key="NonUniformValueText">-</sys:String>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<Thickness x:Key="ComboBox.CellPadding" Top="2" Bottom="2"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
|
||||
<cv:TimeConverter x:Key="TimeConverter"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" Multiplier="18" UnitType="Pixel"/>
|
||||
<cv:EnumItemsSourceConverter x:Key="EnumItemsSourceConverter"/>
|
||||
<cv:NullSubstitutionConverterWrapper x:Key="SimpleValueTextConverter" ValueForNull="{StaticResource NonUniformValueText}"/>
|
||||
<cv:NullSubstitutionConverterWrapper x:Key="NullableEnumDescriptionConverter" Converter="{StaticResource EnumDescriptionConverter}" ValueForNull="{StaticResource NonUniformValueText}"/>
|
||||
<cv:NullSubstitutionConverterWrapper x:Key="QuantityMeasureTextConverter" Converter="{x:Static cv:ValueConverters.QuantityMeasureTextConverter}" ValueForNull="{StaticResource NonUniformValueText}"/>
|
||||
<cv:DoubleTextConverter x:Key="DoubleTextConverter" NaNText="{StaticResource NonUniformValueText}"/>
|
||||
<cv:ValueSubstitutionConverterWrapper x:Key="StringTextConverter">
|
||||
<cv:KeyValue Key="{x:Null}" Value="{StaticResource NonUniformValueText}"/>
|
||||
<cv:KeyValue Key="{x:Static sys:String.Empty}" Value="{StaticResource NonUniformValueText}"/>
|
||||
</cv:ValueSubstitutionConverterWrapper>
|
||||
<cv:EnumHasFlagsConverter x:Key="ParamPermissionsToIsReadOnlyConverter">
|
||||
<sp:AlgoParamPermissions>None</sp:AlgoParamPermissions>
|
||||
<sp:AlgoParamPermissions>ReadOnly</sp:AlgoParamPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
<!--Default Styles-->
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
</Style>
|
||||
<!--Common Templates-->
|
||||
<DataTemplate x:Key="AlgoVMItemTemplate">
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ExecutionStrategyTemplate">
|
||||
<TextBlock>
|
||||
<Hyperlink Click="_on_execution_strategy_toggle_click">
|
||||
Show Strategy Params
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="IndentedParamNameAndBadgeCellTemplate">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding level, Converter={StaticResource GridLengthPixelConverter}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderTypeItem Grid.Column="1" Focusable="False" IsTabStop="False" BadgeFill="{Binding Path='(sp:AlgoParamRowVM.algo).(sp:IAlgoVM.meta).Color', Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding Path='param.(sp:IAlgoParamVM.display_name)'}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="NullableEnumDescriptionTemplate">
|
||||
<TextBlock Text="{Binding Converter={StaticResource NullableEnumDescriptionConverter}}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ParamNameAndDisambiguationCellTemplate">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
|
||||
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
|
||||
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
|
||||
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="SimpleValueTextTemplate">
|
||||
<TextBlock x:Name="value_text" Text="{Binding param.value, Converter={StaticResource SimpleValueTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<!--Param Name Templates-->
|
||||
<u:KeyedTemplateSelector x:Key="ParamNameTemplateSelector" Default="{StaticResource ParamNameAndDisambiguationCellTemplate}">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col1"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
|
||||
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
|
||||
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="bid_text" Grid.Column="1" HorizontalAlignment="Center">
|
||||
<TextBlock.ToolTip>
|
||||
<TextBlock>
|
||||
Best Bid Price (click to assign)
|
||||
<LineBreak/>
|
||||
<Italic>
|
||||
Hold a number key to cross the market by 'n' ticks.
|
||||
</Italic>
|
||||
</TextBlock>
|
||||
</TextBlock.ToolTip>
|
||||
<Hyperlink
|
||||
Focusable="False"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
Command="{Binding DataContext.payup_bid_cmd, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
PreviewMouseLeftButtonDown="{x:Static usp:AlgoEditorGridView.PayupTicksMouseButtonHandler}"
|
||||
>
|
||||
<Run>
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="DataContext.bbo.bid" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
|
||||
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="ask_text" Grid.Column="2" HorizontalAlignment="Center" Margin="4,0">
|
||||
<TextBlock.ToolTip>
|
||||
<TextBlock>
|
||||
Best Ask Price (click to assign)
|
||||
<LineBreak/>
|
||||
<Italic>
|
||||
Hold a number key to cross the market by 'n' ticks.
|
||||
</Italic>
|
||||
</TextBlock>
|
||||
</TextBlock.ToolTip>
|
||||
<Hyperlink
|
||||
Focusable="False"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
Command="{Binding DataContext.payup_ask_cmd, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
PreviewMouseLeftButtonDown="{x:Static usp:AlgoEditorGridView.PayupTicksMouseButtonHandler}"
|
||||
>
|
||||
<Run>
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="DataContext.bbo.ask" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
|
||||
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
|
||||
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding DataContext.security, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{x:Null}">
|
||||
<Setter TargetName="bid_text" Property="Visibility" Value="Collapsed"/>
|
||||
<Setter TargetName="ask_text" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding param.permissions, Converter={StaticResource ParamPermissionsToIsReadOnlyConverter}}" Value="{u:True}">
|
||||
<Setter TargetName="bid_text" Property="ToolTip" Value="Best Bid Price"/>
|
||||
<Setter TargetName="ask_text" Property="ToolTip" Value="Best Ask Price"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col1"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
|
||||
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
|
||||
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
x:Name="bid_text"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
Text="{Binding DataContext.bbo.bid_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
ToolTip="Best Bid Size"
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="ask_text"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="4,0"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
Text="{Binding DataContext.bbo.ask_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
ToolTip="Best Ask Size"
|
||||
/>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
|
||||
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding DataContext.security, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{x:Null}">
|
||||
<Setter TargetName="bid_text" Property="Visibility" Value="Collapsed"/>
|
||||
<Setter TargetName="ask_text" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
|
||||
<!--Read Templates-->
|
||||
<usp:AlgoParamTemplateSelector
|
||||
x:Key="ParamTemplateSelector"
|
||||
IntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
NullableIntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
UIntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
NullableUIntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ExecutionStrategyTemplate="{StaticResource ExecutionStrategyTemplate}"
|
||||
>
|
||||
<usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="param.value"/>
|
||||
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Focusable="False" VerticalAlignment="Center" IsChecked="{Binding param.value, Mode=TwoWay}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
<!--Double-->
|
||||
<usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Enum-->
|
||||
<usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource EnumDescriptionConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<!--Nullable Enum-->
|
||||
<usp:AlgoParamTemplateSelector.NullableEnumTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource NullableEnumDescriptionConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableEnumTemplate>
|
||||
<!--Order Def-->
|
||||
<usp:AlgoParamTemplateSelector.OrderDefTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem BadgeFill="{Binding Path='(sp:AlgoParamRowVM.param).(sp:IAlgoSelector.selected_algo).meta.Color', Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding Path='(sp:AlgoParamRowVM.param).(sp:IAlgoSelector.selected_algo).(sp:IAlgoVM.display_name)'}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.OrderDefTemplate>
|
||||
<!--Quantity Measure-->
|
||||
<usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource QuantityMeasureTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<!--String-->
|
||||
<usp:AlgoParamTemplateSelector.StringTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource StringTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.StringTemplate>
|
||||
<!--Timestamp-->
|
||||
<usp:AlgoParamTemplateSelector.TimestampTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource TimeConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.TimestampTemplate>
|
||||
</usp:AlgoParamTemplateSelector>
|
||||
|
||||
<!--Write Templates-->
|
||||
<usp:AlgoParamTemplateSelector x:Key="ParamEditingTemplateSelector" ExecutionStrategyTemplate="{StaticResource ExecutionStrategyTemplate}">
|
||||
<usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl
|
||||
Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
|
||||
ContractSpec="{Binding DataContext.spec, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
Focusable="False"
|
||||
IsTabStop="False"
|
||||
ArrowKeyTickModifiers="Ctrl"
|
||||
>
|
||||
<usp:PriceEntryControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</usp:PriceEntryControl.CommandBindings>
|
||||
<usp:PriceEntryControl.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</usp:PriceEntryControl.InputBindings>
|
||||
</usp:PriceEntryControl>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Focusable="False" VerticalAlignment="Center" IsChecked="{Binding param.value, Mode=TwoWay}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
<!--Double-->
|
||||
<usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownDouble.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownDouble.CommandBindings>
|
||||
<c:TickUpDownDouble.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownDouble.InputBindings>
|
||||
</c:TickUpDownDouble>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Enum-->
|
||||
<usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<!--Int-->
|
||||
<usp:AlgoParamTemplateSelector.IntTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownInt.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownInt.CommandBindings>
|
||||
<c:TickUpDownInt.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownInt.InputBindings>
|
||||
</c:TickUpDownInt>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.IntTemplate>
|
||||
<!--Nullable Bool-->
|
||||
<usp:AlgoParamTemplateSelector.NullableBoolTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox Focusable="False" VerticalAlignment="Center" IsThreeState="True" IsChecked="{Binding param.value, Mode=TwoWay}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableBoolTemplate>
|
||||
<!--Nullable Enum-->
|
||||
<usp:AlgoParamTemplateSelector.NullableEnumTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Converter={x:Static usp:AlgoEditorGridView.NullableEnumParamRowItemsSourceConverter}}"
|
||||
ItemTemplate="{StaticResource NullableEnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableEnumTemplate>
|
||||
<!--Nullable Int-->
|
||||
<usp:AlgoParamTemplateSelector.NullableIntTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownNullableInt.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownNullableInt.CommandBindings>
|
||||
<c:TickUpDownNullableInt.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownNullableInt.InputBindings>
|
||||
</c:TickUpDownNullableInt>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableIntTemplate>
|
||||
<!--Nullable UInt-->
|
||||
<usp:AlgoParamTemplateSelector.NullableUIntTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownNullableUInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownNullableUInt.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownNullableUInt.CommandBindings>
|
||||
<c:TickUpDownNullableUInt.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownNullableUInt.InputBindings>
|
||||
</c:TickUpDownNullableUInt>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableUIntTemplate>
|
||||
<!--Order Def-->
|
||||
<usp:AlgoParamTemplateSelector.OrderDefTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox HorizontalAlignment="Stretch" DataContext="{Binding param}" ItemsSource="{Binding Path='(sp:IAlgoSelector.algos)'}" ItemTemplate="{StaticResource AlgoVMItemTemplate}" SelectedItem="{Binding Path='(sp:IAlgoSelector.selected_algo)', UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.OrderDefTemplate>
|
||||
<!--QuantityMeasure-->
|
||||
<usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<uag:QuantityMeasureControl Focusable="False" HorizontalAlignment="Stretch" QuantityMeasure="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<uag:QuantityMeasureControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</uag:QuantityMeasureControl.CommandBindings>
|
||||
<uag:QuantityMeasureControl.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</uag:QuantityMeasureControl.InputBindings>
|
||||
</uag:QuantityMeasureControl>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
|
||||
<!--Timestamp-->
|
||||
<usp:AlgoParamTemplateSelector.TimestampTemplate>
|
||||
<DataTemplate>
|
||||
<Grid DataContext="{Binding algo}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox
|
||||
x:Name="PART_StartMode_ComboBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding start_modes}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding start_mode}"
|
||||
/>
|
||||
<c:DateTimePicker
|
||||
x:Name="PART_AbsoluteStart_DateTimePicker"
|
||||
Grid.Column="2"
|
||||
TimeTuple="{Binding absolute_start}"
|
||||
Visibility="{Binding show_absolute_start, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<c:TimeSpanControl
|
||||
x:Name="PART_RelativeStart_TimeSpanControl"
|
||||
Grid.Column="2"
|
||||
TimeSpan="{Binding relative_start, Mode=TwoWay}"
|
||||
Visibility="{Binding show_relative_start, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.TimestampTemplate>
|
||||
<!--UInt-->
|
||||
<usp:AlgoParamTemplateSelector.UIntTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}">
|
||||
<c:TickUpDownUInt.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownUInt.CommandBindings>
|
||||
<c:TickUpDownUInt.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownUInt.InputBindings>
|
||||
</c:TickUpDownUInt>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.UIntTemplate>
|
||||
</usp:AlgoParamTemplateSelector>
|
||||
<usp:AlgoEditorGridRowStyleSelector x:Key="RowStyleSelector">
|
||||
<usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding param.is_dirty}" Value="{u:True}"/>
|
||||
<Condition Binding="{Binding AnimateDirty, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{u:True}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Background" Value="#8098FB98"/>
|
||||
<Setter Property="ToolTip" Value="Value has been modified."/>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
|
||||
<usp:AlgoEditorGridRowStyleSelector.SpreaderStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="DetailsVisibility" Value="Collapsed"/>
|
||||
<Setter Property="DetailsTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<AdornerDecorator>
|
||||
<usp:ExecutionStrategyEditor
|
||||
DataContext="{Binding algo.strategy_editor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="6"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
IsNew="{Binding is_new}"
|
||||
CanUserAddNewLeg="{Binding can_add_leg}"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
PriceCalc="{Binding price_calc}"
|
||||
ParameterRows="{Binding rows}"
|
||||
LegTuples="{Binding leg_tuples}"
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
/>
|
||||
</AdornerDecorator>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</usp:AlgoEditorGridRowStyleSelector.SpreaderStyle>
|
||||
</usp:AlgoEditorGridRowStyleSelector>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager>
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding IsReadOnly, Source={u:DirectReference me}, Converter={x:Static usp:AlgoEditorGridView.GeneralVisualStateConverter}}"/>
|
||||
<u:StateDefinition State="{Binding CanUserChangeAlgoRoot, Source={u:DirectReference me}, Converter={x:Static usp:AlgoEditorGridView.AlgoRootVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}">
|
||||
<VisualState Name="{x:Static Normal, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}"/>
|
||||
<VisualState Name="{x:Static ReadOnly, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(DataGrid.IsReadOnly)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ColorAnimation Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="Gray"/>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="root_algo_combo_box" Storyboard.TargetProperty="(Control.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}">
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}"/>
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="root_algo_label" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="root_algo_combo_box" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ThicknessAnimation Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(Control.Margin)" BeginTime="0" Duration="0" To="0,4,0,0"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="root_algo_label" VerticalAlignment="Center" Margin="{StaticResource Margin.LP2}" Visibility="Collapsed">Algo</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="root_algo_combo_box"
|
||||
Margin="40,0,0,0"
|
||||
ItemsSource="{Binding AlgoRootsViewSource.View, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
SelectedItem="{Binding SelectedAlgoRoot, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding display_name}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<DataGrid
|
||||
x:Name="_param_grid"
|
||||
Grid.Row="1"
|
||||
Grid.IsSharedSizeScope="True"
|
||||
VirtualizingPanel.ScrollUnit="Pixel"
|
||||
AlternatingRowBackground="{x:Null}"
|
||||
IsReadOnly="False"
|
||||
Margin="0,2,0,0"
|
||||
MinRowHeight="18"
|
||||
ColumnHeaderHeight="0"
|
||||
ItemsSource="{Binding AlgoParamRowsViewSource.View, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
|
||||
SelectionMode="Single"
|
||||
RowStyleSelector="{StaticResource RowStyleSelector}"
|
||||
>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn IsReadOnly="True" Header="Param" CellTemplateSelector="{StaticResource ParamNameTemplateSelector}" MinWidth="120" Width="*">
|
||||
<DataGridTemplateColumn.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
</Style>
|
||||
</DataGridTemplateColumn.CellStyle>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ParamTemplateSelector}" CellEditingTemplateSelector="{StaticResource ParamEditingTemplateSelector}" MinWidth="120" Width="*">
|
||||
<DataGridTemplateColumn.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=DataGrid}}" Value="{u:False}">
|
||||
<Setter Property="ToolTip" Value="Click to edit"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGridTemplateColumn.CellStyle>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoEditorGridView : UserControl {
|
||||
public static readonly IValueConverter
|
||||
GeneralVisualStateConverter = ValueConverter.create<bool, string>(is_read_only => is_read_only ? VisualStateNames.General.ReadOnly : VisualStateNames.General.Normal),
|
||||
AlgoRootVisualStateConverter = ValueConverter.create<bool, string>(show_bbo => show_bbo ? VisualStateNames.AlgoRoot.Active : VisualStateNames.AlgoRoot.Inactive),
|
||||
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0)),
|
||||
NullableEnumParamRowItemsSourceConverter = ValueConverter.create<AlgoParamRowVM, IEnumerable>(param_row => {
|
||||
if (param_row?.param != null) {
|
||||
var tenum = param_row.param.GetType().GetInterfaceGenericArgument(AlgoParamTemplateSelector.generic_algo_param_itype);
|
||||
if (tenum.IsNullable()) {
|
||||
// .First() should be safe, since we know this is a Nullable<T>.
|
||||
tenum = tenum.GetGenericArguments().First();
|
||||
}
|
||||
if (tenum.IsEnum)
|
||||
return Enum.GetValues(tenum);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
public static readonly MouseButtonEventHandler PayupTicksMouseButtonHandler = (s, e) => {
|
||||
if ((s as Hyperlink)?.Command is ICommand cmd) {
|
||||
cmd.ExecuteIfCan(KeyboardExtensions.GetPressedNumber() ?? 0);
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
|
||||
public AlgoEditorGridView() {
|
||||
InitializeComponent();
|
||||
_set_value_column_width();
|
||||
// AlgoRoots
|
||||
AlgoRootsViewSource = new CollectionViewSource<_AlgoRootsCollectionView>();
|
||||
BindingOperations.SetBinding(AlgoRootsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoRoots)) { Source = this });
|
||||
AlgoRootsViewSource.GroupDescriptions.Add(new PropertyGroupDescription(nameof(IAlgoVM.group)));
|
||||
// AlgoParamRows
|
||||
AlgoParamRowsViewSource = new CollectionViewSource<ListCollectionView<AlgoParamRowVM>>();
|
||||
BindingOperations.SetBinding(AlgoParamRowsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoParamRows)) { Source = this });
|
||||
AlgoParamRowsViewSource.IsLiveFilteringRequested = true;
|
||||
AlgoParamRowsViewSource.LiveFilteringProperties.Add(nameof(AlgoParamRowVM.permissions));
|
||||
AlgoParamRowsViewSource.Filter += param_rows_filter;
|
||||
DataGridEditHelper.set_hooks(this);
|
||||
void param_rows_filter(object sender, FilterEventArgs e) =>
|
||||
e.Accepted = e.Item is AlgoParamRowVM row && row.param.permissions != AlgoParamPermissions.None;
|
||||
}
|
||||
|
||||
public enum Columns { ParamName, Value }
|
||||
|
||||
event Action CommitEditRequested, CancelEditRequested;
|
||||
|
||||
public CollectionViewSource AlgoRootsViewSource { get; }
|
||||
public CollectionViewSource<ListCollectionView<AlgoParamRowVM>> AlgoParamRowsViewSource { get; }
|
||||
|
||||
bool _stretch_value_column = true;
|
||||
public bool StretchValueColumn {
|
||||
get => _stretch_value_column;
|
||||
set {
|
||||
if (_stretch_value_column != value) {
|
||||
_stretch_value_column = value;
|
||||
_set_value_column_width();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region AnimateDirtyParams
|
||||
public static readonly DependencyProperty AnimateDirtyProperty =
|
||||
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(AnimateDirty), true);
|
||||
|
||||
public bool AnimateDirty {
|
||||
get => (bool)GetValue(AnimateDirtyProperty);
|
||||
set => SetValue(AnimateDirtyProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IsReadOnly
|
||||
public static readonly DependencyProperty IsReadOnlyProperty =
|
||||
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(IsReadOnly));
|
||||
|
||||
public bool IsReadOnly {
|
||||
get => (bool)GetValue(IsReadOnlyProperty);
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CanUserChangeAlgoRoot
|
||||
public static readonly DependencyProperty CanUserChangeAlgoRootProperty =
|
||||
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(CanUserChangeAlgoRoot));
|
||||
|
||||
public bool CanUserChangeAlgoRoot {
|
||||
get => (bool)GetValue(CanUserChangeAlgoRootProperty);
|
||||
set => SetValue(CanUserChangeAlgoRootProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AlgoRoots
|
||||
public static readonly DependencyProperty AlgoRootsProperty =
|
||||
WPFHelper.create_dp<IReadOnlyObservableCollection<IAlgoVM>, AlgoEditorGridView>(nameof(AlgoRoots));
|
||||
|
||||
public IReadOnlyObservableCollection<IAlgoVM> AlgoRoots {
|
||||
get => GetValue(AlgoRootsProperty) as IReadOnlyObservableCollection<IAlgoVM>;
|
||||
set => SetValue(AlgoRootsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedAlgoRoot
|
||||
public static readonly DependencyProperty SelectedAlgoRootProperty =
|
||||
WPFHelper.create_dp<IAlgoVM, AlgoEditorGridView>(nameof(SelectedAlgoRoot), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public IAlgoVM SelectedAlgoRoot {
|
||||
get => GetValue(SelectedAlgoRootProperty) as IAlgoVM;
|
||||
set => SetValue(SelectedAlgoRootProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AlgoParamRows
|
||||
public static readonly DependencyProperty AlgoParamRowsProperty =
|
||||
WPFHelper.create_dp<IReadOnlyObservableCollection<AlgoParamRowVM>, AlgoEditorGridView>(nameof(AlgoParamRows));
|
||||
|
||||
public IReadOnlyObservableCollection<AlgoParamRowVM> AlgoParamRows {
|
||||
get => GetValue(AlgoParamRowsProperty) as IReadOnlyObservableCollection<AlgoParamRowVM>;
|
||||
set => SetValue(AlgoParamRowsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static AlgoEditorGridView create_attach_view() =>
|
||||
new AlgoEditorGridView {
|
||||
Margin = new Thickness(6),
|
||||
CanUserChangeAlgoRoot = true,
|
||||
};
|
||||
|
||||
public void commit_edit() => CommitEditRequested?.Invoke();
|
||||
public void cancel_edit() => CancelEditRequested?.Invoke();
|
||||
|
||||
void _set_value_column_width() {
|
||||
foreach (var c in _param_grid?.Columns.empty_if_null())
|
||||
c.Width = _stretch_value_column ? new DataGridLength(1, DataGridLengthUnitType.Star) : DataGridLength.Auto;
|
||||
}
|
||||
|
||||
void _on_execution_strategy_toggle_click(object sender, RoutedEventArgs e) {
|
||||
if (sender is Hyperlink hl && hl.try_find_ancestor_of_type(out DataGridRow row_view)) {
|
||||
Visibility vis; string text;
|
||||
if (row_view.DetailsVisibility == Visibility.Visible) {
|
||||
vis = Visibility.Collapsed;
|
||||
text = "Show Strategy Params";
|
||||
} else {
|
||||
vis = Visibility.Visible;
|
||||
text = "Hide Strategy Params";
|
||||
}
|
||||
hl.Inlines.Clear();
|
||||
hl.Inlines.Add(text);
|
||||
row_view.SetCurrentValue(DataGridRow.DetailsVisibilityProperty, vis);
|
||||
}
|
||||
}
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class General {
|
||||
public const string GroupName = nameof(General);
|
||||
|
||||
public static readonly string
|
||||
Normal = $"{GroupName}{nameof(Normal)}",
|
||||
ReadOnly = $"{GroupName}{nameof(ReadOnly)}";
|
||||
}
|
||||
|
||||
public static class AlgoRoot {
|
||||
public const string GroupName = nameof(AlgoRoot);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
|
||||
class _AlgoRootsCollectionView : ListCollectionView<IAlgoVM> {
|
||||
public _AlgoRootsCollectionView(IList<IAlgoVM> list) : base(list) =>
|
||||
CustomSort = AlgoCollectionHelper.algo_vm_comparer.to_non_generic();
|
||||
}
|
||||
}
|
||||
|
||||
class AlgoEditorGridRowStyleSelector : StyleSelector {
|
||||
public Style DefaultStyle { get; set; }
|
||||
public Style SpreaderStyle { get; set; }
|
||||
|
||||
public override Style SelectStyle(object item, DependencyObject container) {
|
||||
if (item is AlgoParamRowVM row) {
|
||||
if (row.param.id == nameof(bts.api.orderman.SpreaderOrderDefinition.ExecutionStrategy))
|
||||
return SpreaderStyle;
|
||||
}
|
||||
return DefaultStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
using bts.collections;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class AlgoParamRowsHelper {
|
||||
public static IDisposable connect(AlgoDefinitionVM algo, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) =>
|
||||
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().SubscribeEach(a => {
|
||||
rows.clear_collections();
|
||||
if (a == null)
|
||||
return Disposable.empty;
|
||||
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
/// Note: the order of operations here matters
|
||||
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
|
||||
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
|
||||
var hook = _wrap_rows(a, null, child_rows, include_algo_type_rows);
|
||||
rows.add_collection(child_rows.AsReadOnly());
|
||||
return hook;
|
||||
})) {
|
||||
rows.clear_collections
|
||||
};
|
||||
|
||||
public static IDisposable negate_is_dirty(IReadOnlyObservableCollection<AlgoParamRowVM> rows) {
|
||||
return ObservableAutoWrapper.connect_item_hooks(rows, hook);
|
||||
IDisposable hook(AlgoParamRowVM r) {
|
||||
r.param.is_dirty_changed += negate;
|
||||
return Disposable.Create(unhook);
|
||||
void negate(bool is_dirty) {
|
||||
if (is_dirty)
|
||||
r.param.mark_committed();
|
||||
}
|
||||
void unhook() =>
|
||||
r.param.is_dirty_changed -= negate;
|
||||
}
|
||||
}
|
||||
|
||||
static IDisposable _wrap_rows(AlgoDefinitionVM algo_def, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) {
|
||||
AlgoParamRowVM root = null;
|
||||
ObservableAggregateCollection<AlgoParamRowVM> algo_params;
|
||||
if (include_algo_type_rows) {
|
||||
// while normal params live and die with the selected algo-type (when the algo-type changes, they are removed, and replaced with params for the new algo type),
|
||||
// the algo-type row is constant (it is the row with the algo-type dropdown, which the user may use to select different algos).
|
||||
algo_params = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
rows.add_collection(new ObservableAppendCollection<AlgoParamRowVM>(algo_params, root = new AlgoSelectorParamRowVM(new AlgoVMWrapper(algo_def), algo_def, null)).AsReadOnly());
|
||||
} else {
|
||||
algo_params = rows;
|
||||
}
|
||||
return algo_def.observe_algo_root().SubscribeEach(a => {
|
||||
algo_params.clear_collections();
|
||||
if (a == null)
|
||||
return Disposable.empty;
|
||||
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
/// Note: the order of operations here matters
|
||||
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
|
||||
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
|
||||
var hook = _wrap_rows(a, root, child_rows, include_algo_type_rows);
|
||||
algo_params.add_collection(child_rows.AsReadOnly());
|
||||
return hook;
|
||||
});
|
||||
}
|
||||
|
||||
static IDisposable _wrap_rows(IAlgoVM algo_root, AlgoParamRowVM parent, ObservableAggregateCollection<AlgoParamRowVM> all_rows, bool include_algo_type_rows) {
|
||||
var wrapped_params = new ObservableAutoWrapper<AlgoParamRowVM, IAlgoParamVM>(algo_root.algo_params, pvm => new AlgoParamRowVM(algo_root, pvm, parent));
|
||||
var selector_params = new ObservableCollection<AlgoParamRowVM>();
|
||||
all_rows.add_collection(wrapped_params);
|
||||
all_rows.add_collection(selector_params);
|
||||
return ObservableAggregateCollection.connect(selector_params, algo_root.child_algo_selectors, new Func<IAlgoSelector, (ObservableCollection<AlgoParamRowVM>, IDisposable)>(hook_children));
|
||||
(ObservableCollection<AlgoParamRowVM>, IDisposable) hook_children(IAlgoSelector s) {
|
||||
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
|
||||
var selector_wrapper = new AlgoSelectorParamRowVM(algo_root, s, parent);
|
||||
return (
|
||||
include_algo_type_rows ?
|
||||
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
|
||||
child_rows,
|
||||
s.subscribe_selected_algo().SubscribeEach(a => {
|
||||
s.value = a;
|
||||
child_rows.clear_collections();
|
||||
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
static class AlgoParamTemplateKeys {
|
||||
public const string
|
||||
Price = nameof(Price),
|
||||
Size = nameof(Size);
|
||||
}
|
||||
|
||||
/// TODO: remove this class and just use a <see cref="KeyedTemplateSelector"/>.
|
||||
class AlgoParamTemplateSelector : DataTemplateSelector {
|
||||
public static readonly Type generic_algo_param_itype = typeof(IAlgoParamVM<>);
|
||||
|
||||
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key);
|
||||
|
||||
public DataTemplate BoolTemplate { get; set; }
|
||||
public DataTemplate NullableBoolTemplate { get; set; }
|
||||
public DataTemplate IntTemplate { get; set; }
|
||||
public DataTemplate NullableIntTemplate { get; set; }
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate NullableUIntTemplate { get; set; }
|
||||
public DataTemplate DoubleTemplate { get; set; }
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
public DataTemplate NullableEnumTemplate { get; set; }
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
public DataTemplate ExecutionStrategyTemplate { get; set; }
|
||||
public DataTemplate TimestampTemplate { get; set; }
|
||||
|
||||
/// <see cref="_keyed_templates"/> is needed for the 'price' param. Price was formerly edited in a separate control, however now it is just another param row.
|
||||
/// Before this, the generic type argument in the <see cref="IAlgoParamVM{T}"/> implementation was enough to determine the template.
|
||||
/// Since 'price' is just a double, further specification was needed to associate it with a 'PriceEntryControl' template.
|
||||
public IList KeyedTemplates => _keyed_templates;
|
||||
|
||||
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
|
||||
if (item is ITemplateSelectorKey tsk && tsk.key is string template_key && _keyed_templates.TryGetValue(template_key, out var tkv))
|
||||
return tkv.Value;
|
||||
IAlgoParamVM pvm = item as IAlgoParamVM ?? (item as AlgoParamRowVM)?.param;
|
||||
if (pvm != null) {
|
||||
if (pvm is IAlgoParamVM<double>) return DoubleTemplate;
|
||||
if (pvm is IAlgoParamVM<int>) return IntTemplate;
|
||||
if (pvm is IAlgoParamVM<int?>) return NullableIntTemplate;
|
||||
if (pvm is IAlgoParamVM<uint>) return UIntTemplate;
|
||||
if (pvm is IAlgoParamVM<uint?>) return NullableUIntTemplate;
|
||||
if (pvm is IAlgoParamVM<bool>) return NullableBoolTemplate;
|
||||
if (pvm is IAlgoParamVM<bool?>) return BoolTemplate;
|
||||
if (pvm is IAlgoParamVM<string>) return StringTemplate;
|
||||
if (pvm is IAlgoParamVM<IAlgoVM>) return OrderDefTemplate;
|
||||
if (pvm is IAlgoParamVM<QuantityMeasureImmutable?>) return QuantityMeasureTemplate;
|
||||
if (pvm is IAlgoParamVM<ExecutionStrategy>) return ExecutionStrategyTemplate;
|
||||
if (pvm is IAlgoParamVM<Google.Protobuf.WellKnownTypes.Timestamp>) return TimestampTemplate;
|
||||
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), generic_algo_param_itype).FirstOrDefault();
|
||||
if (targ != null) {
|
||||
if (targ.IsEnum)
|
||||
return EnumTemplate;
|
||||
else if (targ.IsNullable()) {
|
||||
targ = targ.GetGenericArguments()[0];
|
||||
if (targ.IsEnum)
|
||||
return NullableEnumTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
#define ENTER_BEHAVES_LIKE_TAB
|
||||
using bts;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark.algo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoEditorGridView {
|
||||
static class DataGridEditHelper {
|
||||
public static IDisposable set_hooks(AlgoEditorGridView algo_editor) {
|
||||
var grid = algo_editor._param_grid;
|
||||
grid.PreviewKeyDown += on_preview_key_down;
|
||||
grid.KeyDown += on_key_down;
|
||||
grid.SelectionChanged += on_selection_changed;
|
||||
grid.PreparingCellForEdit += on_preparing_cell_for_edit;
|
||||
grid.CellEditEnding += on_cell_edit_ending;
|
||||
grid.GotFocus += on_got_focus;
|
||||
algo_editor.CommitEditRequested += on_commit_edit_requested;
|
||||
algo_editor.CancelEditRequested += on_cancel_edit_requested;
|
||||
return Disposable.Create(() => {
|
||||
grid.PreviewKeyDown += on_preview_key_down;
|
||||
grid.KeyDown -= on_key_down;
|
||||
grid.SelectionChanged -= on_selection_changed;
|
||||
grid.PreparingCellForEdit -= on_preparing_cell_for_edit;
|
||||
grid.CellEditEnding -= on_cell_edit_ending;
|
||||
grid.GotFocus -= on_got_focus;
|
||||
algo_editor.CommitEditRequested -= on_commit_edit_requested;
|
||||
algo_editor.CancelEditRequested -= on_cancel_edit_requested;
|
||||
});
|
||||
/// Handlers
|
||||
// hooked up to PreviewKeyDown, instead of KeyDown, so the cells can't override this behavior
|
||||
void on_preview_key_down(object sender, KeyEventArgs e) {
|
||||
switch (e.Key) {
|
||||
case Key.Escape: e.Handled = cancel_edit(); break;
|
||||
case Key.Enter:
|
||||
if (grid.TryGetCurrentRowView(out var row_view)) {
|
||||
if (row_view.IsEditing) {
|
||||
commit_edit();
|
||||
#if ENTER_BEHAVES_LIKE_TAB
|
||||
NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Down);
|
||||
#endif
|
||||
} else grid.BeginEdit();
|
||||
}
|
||||
e.Handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// hooked up to KeyDown, instead of PreviewKeyDown, so cells can implement custom Key/CommandBindings, or other key-based behavior without the DataGrid handling it in the PreviewKeyDown event.
|
||||
// for ex: TickUpDown cells can do things like TickUp/Down when Ctrl+Up/Down arrow-key is pressed.
|
||||
void on_key_down(object sender, KeyEventArgs e) {
|
||||
switch (e.Key) {
|
||||
case Key.Up: NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Up); break;
|
||||
case Key.Down: NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Down); break;
|
||||
default: return;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
void on_selection_changed(object sender, SelectionChangedEventArgs e) {
|
||||
_commit(e.RemovedItems?.Cast<object>()?.SafeSingleOrDefault());
|
||||
_commit(e.AddedItems?.Cast<object>()?.SafeSingleOrDefault());
|
||||
void _commit(object row_item) {
|
||||
if (grid.TryGetRowView(row_item, out var row_view) && row_view.IsEditing)
|
||||
grid.CommitEdit();
|
||||
}
|
||||
}
|
||||
|
||||
void on_preparing_cell_for_edit(object sender, DataGridPreparingCellForEditEventArgs e) {
|
||||
if (e.EditingElement is ContentPresenter presenter) {
|
||||
OneOf.Match(get<TextBox>, get<ComboBox>, get<QuantityMeasureControl>, get<ToggleButton>, text_box, combo_box, quantity_measure_control, toggle_button);
|
||||
T get<T>() => presenter.descendants_of_type<T>().FirstOrDefault();
|
||||
// TextBox
|
||||
void text_box(TextBox tb) => Keyboard.Focus(tb);
|
||||
// ComboBox
|
||||
void combo_box(ComboBox cb) { }
|
||||
// QuantityMeasureControl
|
||||
async void quantity_measure_control(QuantityMeasureControl qmc) {
|
||||
if (!focus_text()) {
|
||||
await qmc.LoadedAsync();
|
||||
focus_text();
|
||||
}
|
||||
bool focus_text() =>
|
||||
qmc.try_find_descendent_of_type(out TextBox tb) && Keyboard.Focus(tb) == tb;
|
||||
}
|
||||
// ToggleButton
|
||||
void toggle_button(ToggleButton tb) {
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed && !tb.IsLoaded) {
|
||||
// this is a hack to toggle IsChecked when the user clicks a checkbox cell that is not already in edit mode.
|
||||
IDisposable dispose = null;
|
||||
dispose = Disposable.Create(
|
||||
tb.SubscribePreviewMouseUp(on_mouse_up),
|
||||
tb.SubscribeMouseLeave(on_mouse_leave));
|
||||
void on_mouse_up(object s, MouseButtonEventArgs mbe) {
|
||||
if (tb.IsMouseOver)
|
||||
tb.SetCurrentValue(ToggleButton.IsCheckedProperty, !tb.IsChecked);
|
||||
Disposable.dispose(ref dispose);
|
||||
}
|
||||
void on_mouse_leave(object s, MouseEventArgs me) =>
|
||||
Disposable.dispose(ref dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
e.EditingElement.Focus();
|
||||
}
|
||||
|
||||
void on_cell_edit_ending(object sender, DataGridCellEditEndingEventArgs e) {
|
||||
if (e.EditingElement is ContentPresenter presenter) {
|
||||
switch (e.EditAction) {
|
||||
case DataGridEditAction.Cancel: OneOf.Match(get<TextBox>, get<ComboBox>, get<ToggleButton>, cancel_text_box, cancel_combo_box, cancel_toggle_button); break;
|
||||
case DataGridEditAction.Commit: OneOf.Match(get<TextBox>, get<ComboBox>, get<ToggleButton>, commit_text_box, commit_combo_box, commit_toggle_button); break;
|
||||
}
|
||||
T get<T>() => presenter.descendants_of_type<T>().FirstOrDefault();
|
||||
// TextBox
|
||||
void commit_text_box(TextBox tb) => tb.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
void cancel_text_box(TextBox tb) => tb.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
|
||||
// ComboBox
|
||||
void commit_combo_box(ComboBox cb) { }
|
||||
void cancel_combo_box(ComboBox cb) { }
|
||||
// ToggleButton
|
||||
void commit_toggle_button(ToggleButton tb) { }
|
||||
void cancel_toggle_button(ToggleButton tb) { }
|
||||
}
|
||||
}
|
||||
|
||||
void on_got_focus(object sender, RoutedEventArgs e) {
|
||||
if (e.OriginalSource is DataGridCell cell && cell.try_find_ancestor_of_type(out DataGridRow row)) {
|
||||
if (row.IsEditing) {
|
||||
if (!cell.IsEditing) {
|
||||
grid.CancelEdit(DataGridEditingUnit.Row);
|
||||
if (try_set_current())
|
||||
grid.BeginEdit(e);
|
||||
}
|
||||
} else {
|
||||
try_set_current();
|
||||
if (!row.IsEditing)
|
||||
grid.BeginEdit(e);
|
||||
}
|
||||
bool try_set_current() {
|
||||
grid.SetCurrentValue(DataGrid.CurrentItemProperty, row.Item);
|
||||
if (grid.TryGetCurrentRowView(out var row_view)) {
|
||||
row_view.SetCurrentValue(DataGridRow.IsSelectedProperty, true);
|
||||
grid.SetCurrentValue(DataGrid.CurrentCellProperty, new DataGridCellInfo(row.Item, grid.Columns[1]));
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_commit_edit_requested() {
|
||||
if (grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing)
|
||||
commit_edit();
|
||||
}
|
||||
|
||||
void commit_edit() {
|
||||
using (grid.SuspendGotFocus(on_got_focus))
|
||||
grid.CommitEdit(DataGridEditingUnit.Row, true);
|
||||
}
|
||||
|
||||
void on_cancel_edit_requested() =>
|
||||
cancel_edit();
|
||||
|
||||
bool cancel_edit() {
|
||||
if (grid.CurrentCell != null && grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing) {
|
||||
using (grid.SuspendGotFocus(on_got_focus))
|
||||
grid.CancelEdit(DataGridEditingUnit.Row);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.AlgoEditorView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
|
||||
<!--Default Styles-->
|
||||
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
|
||||
<Setter Property="Padding" Value="8"/>
|
||||
</Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
|
||||
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
Grid.Row="2"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Width="140"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
ItemsSource="{Binding algos_view}"
|
||||
SelectedItem="{Binding algo_root}"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.IsEnabled>
|
||||
<Binding Path="editing_permissions">
|
||||
<Binding.Converter>
|
||||
<cv:EnumHasFlagsConverter>
|
||||
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</ComboBox.IsEnabled>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
IsTabStop="False"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoEditorView : UserControl {
|
||||
public AlgoEditorView() {
|
||||
InitializeComponent();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
// local methods
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
@@ -16,10 +18,10 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
@@ -31,22 +33,19 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
namespace mbts.spark {
|
||||
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
|
||||
readonly OrdersModel _model;
|
||||
readonly RXOperableCollection<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) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<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>(
|
||||
@@ -61,6 +60,14 @@ namespace mbts.spark {
|
||||
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
|
||||
comparer: Comparer<SecurityImmutable>.Default
|
||||
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
|
||||
// symbol
|
||||
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
|
||||
DiscreteValuesFilterVM.Create<FillTuple, string>(
|
||||
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
ft => ft.security?.Symbol ?? string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
DefaultColumnWidths.Symbol);
|
||||
// order type
|
||||
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
|
||||
add_col_expl(Columns.OrderType,
|
||||
@@ -83,45 +90,45 @@ 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 RXOperableCollection<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.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
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) {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
}
|
||||
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.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
fills_source.Connect()
|
||||
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) =>
|
||||
@@ -131,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; }
|
||||
@@ -150,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) =>
|
||||
_rows_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
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) {
|
||||
@@ -186,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;
|
||||
@@ -208,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;
|
||||
@@ -217,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;
|
||||
@@ -227,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(_order, _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;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
|
||||
/// </summary>
|
||||
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
|
||||
bool _disposed;
|
||||
FillsVM _value;
|
||||
IDisposable _columns_duplex;
|
||||
|
||||
public event Action<bool> OnDisposed;
|
||||
|
||||
public FillsVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (value != _value) {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
Disposable.dispose(ref _columns_duplex);
|
||||
if (_value != null) {
|
||||
columns.Clear();
|
||||
rows.clear_collections();
|
||||
}
|
||||
Disposable.dispose(ref _value);
|
||||
_value = value;
|
||||
if (_value != null) {
|
||||
_columns_duplex = ObservableAutoWrapper.connect_two_way(_value.columns, columns);
|
||||
rows.add_collection(_value.rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
value = null;
|
||||
OnDisposed?.Invoke(true);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
if (!_disposed)
|
||||
_value?.on_sorting(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,73 +15,71 @@ 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";
|
||||
const string _thread_id = "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)
|
||||
: base("Fills", bus, factory, SparkCommands.ShowFills.icon) {
|
||||
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;
|
||||
_settings_manager = settings_manager;
|
||||
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;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
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,10 +17,31 @@ 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;
|
||||
/// Fills 'sorting-criterion' settings were compromised a while back (mid-late 2018).
|
||||
/// The issue was that, when a column was sorted, its 'sort member path' was assigned to the SortingCriteria.col_name,
|
||||
/// rather than the <see cref="Columns"/> enum/id string.
|
||||
/// This dictionary serves as a reverse-lookup so the sort member path can be converted to the enum/id string.
|
||||
static readonly IReadOnlyDictionary<string, Columns> _sort_member_to_id =
|
||||
new Dictionary<string, Columns> {
|
||||
{ nameof(FillsVM.Row.account), Columns.Account },
|
||||
{ nameof(FillsVM.Row.exchange), Columns.Exchange },
|
||||
{ nameof(FillsVM.Row.fill_key), Columns.FillKey },
|
||||
{ nameof(FillsVM.Row.fill_kind), Columns.FillKind },
|
||||
{ nameof(FillsVM.Row.fill_time), Columns.FillTime },
|
||||
{ nameof(FillsVM.Row.order_key), Columns.OrderKey },
|
||||
{ nameof(FillsVM.Row.order_type), Columns.OrderType },
|
||||
{ nameof(FillsVM.Row.price), Columns.Price },
|
||||
{ nameof(FillsVM.Row.security), Columns.Security },
|
||||
{ nameof(FillsVM.Row.security_key), Columns.SecurityKey },
|
||||
{ nameof(FillsVM.Row.side), Columns.Side },
|
||||
{ nameof(FillsVM.Row.size), Columns.Size },
|
||||
{ nameof(FillsVM.Row.time_in_force), Columns.TimeInForce },
|
||||
{ nameof(FillsVM.Row.user), Columns.User }
|
||||
};
|
||||
|
||||
public FillsWindowFactory(TradingShellModule module, IBus bus, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
: base(module, "Fills") {
|
||||
@@ -31,148 +53,19 @@ 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,
|
||||
get: us => us.fills_grid,
|
||||
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 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.Side, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -1,17 +1,24 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.md;
|
||||
using mbts.co2;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderModel {
|
||||
readonly OrdersModel _order_gateway;
|
||||
readonly MarketDataAuxiliaryModel _md_auxiliary_model;
|
||||
|
||||
public LadderModel(SecurityMD source, OrdersModel ordergw, MarketDataAuxiliaryModel md_auxiliary_model) {
|
||||
public LadderModel(SecurityMD source, OrdersModel ordergw, MBOService mbo, Func<OrderSecurity, bool> order_filter = null) {
|
||||
security_md = source;
|
||||
_order_gateway = ordergw;
|
||||
_md_auxiliary_model = md_auxiliary_model;
|
||||
orders = ordergw.on_security_working_orders_with_piq(source.security.Id);
|
||||
this.mbo_service = mbo;
|
||||
if (order_filter != null) {
|
||||
orders = orders.Filter(order_filter, o => o.order.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CancelType { All, Bids, Asks }
|
||||
@@ -20,7 +27,9 @@ namespace mbts.spark {
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public MDConsumerHandle md => security_md.md;
|
||||
public OrdersModel order_gateway => _order_gateway;
|
||||
public MarketDataAuxiliaryModel md_auxiliary_model => _md_auxiliary_model;
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> orders { get; }
|
||||
|
||||
public MBOService mbo_service { get; }
|
||||
|
||||
public Task cancel_orders(CancelType ct) {
|
||||
switch (ct) {
|
||||
+114
-12
@@ -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!
|
||||
@@ -38,6 +39,14 @@ namespace mbts.spark {
|
||||
font_weight = FontWeights.SemiBold.ToOpenTypeWeight()
|
||||
};
|
||||
|
||||
public static LadderColumnSettings DefaultPiqColumnSettings => new LadderColumnSettings {
|
||||
show = false,
|
||||
width = 50,
|
||||
font_family = "Segoe UI",
|
||||
font_size = 10,
|
||||
font_weight = FontWeights.SemiBold.ToOpenTypeWeight()
|
||||
};
|
||||
|
||||
[ProtoMember(1)]
|
||||
public bool show { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -111,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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -126,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 &&
|
||||
@@ -133,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);
|
||||
|
||||
@@ -143,7 +156,8 @@ namespace mbts.spark {
|
||||
buy.GetHashCode(),
|
||||
sell.GetHashCode(),
|
||||
passive.GetHashCode(),
|
||||
total.GetHashCode());
|
||||
total.GetHashCode(),
|
||||
show_ltq.GetHashCode());
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -207,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.
|
||||
@@ -215,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;
|
||||
@@ -230,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;
|
||||
@@ -240,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 {
|
||||
@@ -250,7 +285,7 @@ namespace mbts.spark {
|
||||
ask_column = LadderColumnSettings.Default,
|
||||
price_column = LadderColumnSettings.Default,
|
||||
last_column = LadderColumnSettings.Default,
|
||||
piq_column = LadderColumnSettings.Default;
|
||||
piq_column = LadderColumnSettings.DefaultPiqColumnSettings;
|
||||
orders_column.background = BTSColors.LadderSpecial.default_column_background;
|
||||
bid_column.foreground = 0xFFFFFF;
|
||||
bid_column.background = BTSColors.MarketData.bid;
|
||||
@@ -259,8 +294,8 @@ namespace mbts.spark {
|
||||
price_column.background = BTSColors.LadderSpecial.default_column_background;
|
||||
last_column.background = 0xFFFFFF;
|
||||
piq_column.background = BTSColors.LadderSpecial.default_column_background;
|
||||
return new LadderSettings {
|
||||
row_height = 20,
|
||||
var rv = new LadderSettings {
|
||||
row_height = 22,
|
||||
grid_line_color = 0x535353,
|
||||
orders_column = orders_column,
|
||||
bid_column = bid_column,
|
||||
@@ -271,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 },
|
||||
@@ -293,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,
|
||||
@@ -303,13 +339,24 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
@@ -336,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)]
|
||||
@@ -346,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; }
|
||||
@@ -387,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)]
|
||||
@@ -426,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 &&
|
||||
@@ -439,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) &&
|
||||
@@ -463,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 &&
|
||||
@@ -474,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(),
|
||||
@@ -490,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(),
|
||||
@@ -525,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;
|
||||
@@ -0,0 +1,324 @@
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
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;
|
||||
using LadderSettings = mbts.spark.LadderSettings;
|
||||
|
||||
namespace bts.shell {
|
||||
class LadderWindowFactory : ShellWindowFactory<LadderSaveState> {
|
||||
const string _window_type = "ladder";
|
||||
readonly LadderConfig _ladder_conf;
|
||||
readonly IBus _bus;
|
||||
readonly IWindowManager _wm;
|
||||
readonly MBOService _mbo;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly OrdersModel _orders_model;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public LadderWindowFactory(
|
||||
IShellModule owner,
|
||||
IBus bus,
|
||||
IWindowManager wm,
|
||||
UserSettingsHub settings_hub,
|
||||
OrdersModel orders_model,
|
||||
MBOService mbo)
|
||||
|
||||
: base(owner, "SparkLadder") {
|
||||
_bus = bus;
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
}
|
||||
|
||||
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, state.settings_id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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, uint settings_id = 0) {
|
||||
IDisposable active_settings_dialog = null;
|
||||
var thread_id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
// on_shell_window_vm should behave like an event (do not set to null)
|
||||
Action<ShellWindowVM> on_shell_window_vm = _ => { };
|
||||
ShellWindowVM shell_window_vm = null;
|
||||
IShellWindow shell_window = null;
|
||||
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
// resources
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
var profile_service = new SettingsProfileService<LadderSettings>(_settings_hub,
|
||||
us => ImmutableDictionary<uint, SettingsProfile<LadderSettings>>.Empty.AddRange(us.ladder_profiles.ToDictionary(
|
||||
lp => lp.id,
|
||||
lp => new SettingsProfile<LadderSettings>(lp.id, lp.name, 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{thread_id:00}" : "";
|
||||
return shell_window = ShellWindow.create(
|
||||
module, md.security.ToString(), _window_type, view,
|
||||
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: thread,
|
||||
vm => {
|
||||
shell_window_vm = vm;
|
||||
title_source.connect();
|
||||
});
|
||||
}
|
||||
|
||||
void show_windows(IReadOnlyCollection<SecurityMD> securities) {
|
||||
int n_allowed = (int)_ladder_conf.max_open_ladders - _wm.list_windows().Where(w => w.window_type == _window_type).Count();
|
||||
int n_opened = 0;
|
||||
foreach (var s in securities) {
|
||||
if (++n_opened > n_allowed) {
|
||||
BTSMessageBox.Show($"The maximum number of ladders are already opened ({_ladder_conf.max_open_ladders}).", "Ladders",
|
||||
BTSMessageBoxButtons.OK, MessageBoxImage.Exclamation, true, false, true);
|
||||
break;
|
||||
} else {
|
||||
_bus.post(SparkTags.ShowWindow, create_ladder_window(s, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
public readonly bool ladders_on_threads, open_algo_popup_on_select, use_algo_param_grid;
|
||||
public readonly TimeSpan security_timeout;
|
||||
|
||||
public LadderConfig(bts.config.LadderKnobs knobs, bool ladders_on_threads) {
|
||||
max_open_ladders = knobs.max_open_ladders;
|
||||
open_algo_popup_on_select = knobs.open_algo_popup_on_select;
|
||||
use_algo_param_grid = knobs.use_algo_param_grid;
|
||||
security_timeout = TimeSpan.FromSeconds(Math.Max(15, knobs.security_timeout_secs));
|
||||
this.ladders_on_threads = ladders_on_threads;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class LadderSaveState {
|
||||
[ProtoMember(1)]
|
||||
public UInt32 security_key;
|
||||
[ProtoMember(2)]
|
||||
public mbts.spark.LadderSettings settings;
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
+135
-93
@@ -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;
|
||||
@@ -29,45 +31,46 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
|
||||
readonly LadderVM _parent;
|
||||
readonly LadderVM _owner;
|
||||
readonly IBus _bus;
|
||||
readonly Property<FontFamily> _font_family;
|
||||
readonly Property<FontWeight> _font_weight;
|
||||
readonly Property<double> _font_size;
|
||||
readonly Property<Brush> _foreground, _background, _hoverbackground, _border, _parent_algo_type_brush;
|
||||
readonly Property<bool> _is_exchange_order, _has_multiple_orders_at_same_level, _is_hedge_order;
|
||||
readonly Property<CustomOrderDefinition.CustomOneofCase> _parent_algo_type;
|
||||
readonly Property<string> _summary;
|
||||
readonly Property<Brush> _foreground, _background, _hoverbackground, _border;
|
||||
readonly Property<bool> _has_multiple_orders_at_same_level, _is_hedge_order;
|
||||
readonly Property<CustomOrderDefinition.CustomOneofCase> _algo_type;
|
||||
readonly Property<string> _algo_studio_abbreviation;
|
||||
IObservable<LadderSettings.BadgeStyle> _bs;
|
||||
LadderSettings.BadgeStyle _current_badge_style;
|
||||
int _n_active_price_change_ops = 0;
|
||||
double _real_price = double.NaN, _phantom_price = double.NaN;
|
||||
uint filled, quantity;
|
||||
uint filled, quantity, num_root_orders;
|
||||
int working_quantity;
|
||||
uint? _min_piq = UInt32.MaxValue;
|
||||
|
||||
LadderModel model => _parent.model;
|
||||
Dictionary<uint, (OrderRecordImmutable order, uint? p)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p)>();
|
||||
public event Action piq_changed;
|
||||
|
||||
public LadderOrderVM((OrderRecordImmutable order, uint? p) op, double real_price, LadderVM parent, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
|
||||
_parent = parent;
|
||||
LadderModel model => _owner.model;
|
||||
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)>();
|
||||
|
||||
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoData?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
|
||||
_owner = owner;
|
||||
_bus = bus;
|
||||
_bs = bs;
|
||||
_real_price = real_price;
|
||||
// settings
|
||||
var fs = UserSettings.factory_defaults.ladder;
|
||||
_font_family = backing(nameof(font_family), parent.settings, get_font_family, get_font_family(fs));
|
||||
_font_weight = backing(nameof(font_weight), parent.settings, get_font_weight, get_font_weight(fs));
|
||||
_font_size = backing(nameof(font_size), parent.settings, get_font_size, get_font_size(fs));
|
||||
_foreground = backing(nameof(foreground), parent.settings, get_foreground, get_foreground(fs));
|
||||
_font_family = backing(nameof(font_family), owner.settings, get_font_family, get_font_family(fs));
|
||||
_font_weight = backing(nameof(font_weight), owner.settings, get_font_weight, get_font_weight(fs));
|
||||
_font_size = backing(nameof(font_size), owner.settings, get_font_size, get_font_size(fs));
|
||||
_foreground = backing(nameof(foreground), owner.settings, get_foreground, get_foreground(fs));
|
||||
_background = backing(nameof(background), _color_bg_brush);
|
||||
_hoverbackground = backing(nameof(hoverbackground), _color_bg_brush);
|
||||
_border = backing(nameof(border), _color_bg_brush);
|
||||
_summary = backing(nameof(summary), string.Empty);
|
||||
_is_exchange_order = backing(nameof(is_exchange_order), false);
|
||||
_has_multiple_orders_at_same_level = backing(nameof(has_multiple_orders_at_same_level), false);
|
||||
_is_hedge_order = backing(nameof(is_hedge_order), false);
|
||||
_parent_algo_type = backing(nameof(parent_algo_type), CustomOrderDefinition.CustomOneofCase.None);
|
||||
_parent_algo_type_brush = backing(nameof(parent_algo_type_brush), _color_bg_brush);
|
||||
_algo_type = backing(nameof(algo_type), CustomOrderDefinition.CustomOneofCase.None);
|
||||
_algo_studio_abbreviation = backing(nameof(algo_studio_abbreviation), string.Empty);
|
||||
FontFamily get_font_family(LadderSettings s) => new FontFamily(s.orders_column.font_family);
|
||||
FontWeight get_font_weight(LadderSettings s) => ui.utility.text.TextUtils.from_open_type_weight_safe(s.orders_column.font_weight);
|
||||
double get_font_size(LadderSettings s) => s.orders_column.font_size;
|
||||
@@ -95,36 +98,72 @@ namespace mbts.spark {
|
||||
}
|
||||
var darker = BTSColor.darken2(color.R, color.G, color.B, 0.75);
|
||||
Color dark = new Color() { A = 255, R = darker.r, G = darker.g, B = darker.b };
|
||||
//var completion = _parent_and_child_not_sharing_slot().Sum(order => OrderUtils.filled(order)) / _parent_and_child_not_sharing_slot().Sum(order => OrderUtils.quantity(order));
|
||||
bts.spark.OrderMetadata parent_algo_metadata = _parent.model.order_gateway.factory.get_metadata(parent_algo_type);
|
||||
parent_algo_type_brush = parent_algo_metadata.Color.rgb_brush();
|
||||
var buy_sell_background_fill_color = new SolidColorBrush(color);
|
||||
buy_sell_background_fill_color.Freeze();
|
||||
background = buy_sell_background_fill_color;
|
||||
hoverbackground = mkbrush(dark, _darkbg, 0);
|
||||
filled = _parent_and_child_not_sharing_slot().Select(order => OrderUtils.filled(order)).Sum();
|
||||
working_quantity = _parent_and_child_not_sharing_slot().Select(order => order.working_quantity()).Sum();
|
||||
quantity = _parent_and_child_not_sharing_slot().Select(order => order.State.Quantity).Sum();
|
||||
|
||||
filled = quantity = num_root_orders = 0;
|
||||
working_quantity = 0;
|
||||
foreach(var n in _root_nodes()) {
|
||||
num_root_orders++;
|
||||
filled += n.filled();
|
||||
working_quantity += n.working_quantity();
|
||||
quantity += n.quantity();
|
||||
}
|
||||
|
||||
// if this badge only represents a single order, then is_exchange_order and the border (= algo color)
|
||||
// should be set appropriately
|
||||
if (_orders.Count == 1) {
|
||||
var single_order = _orders.Values.First().order;
|
||||
var single_order_metadata = _parent.model.order_gateway.factory.get_metadata(single_order);
|
||||
if (num_root_orders == 1) {
|
||||
var single_order = _root_nodes().First();
|
||||
var single_order_metadata = _owner.model.order_gateway.factory.get_metadata(single_order);
|
||||
border = single_order_metadata.Color.rgb_brush();
|
||||
is_exchange_order = OrderUtils.is_exchange_order(single_order);
|
||||
if (_orders.TryGetValue(single_order.Key, out var opm)) {
|
||||
algo_studio_abbreviation = _sanitize_abbreviation(string.IsNullOrEmpty(opm.meta?.abbreviation) ? _generate_abbreviation_for_algo_studio_algo(opm.algo) : opm.meta?.abbreviation);
|
||||
border = opm.meta?.badge_color?.rgb_color().rgb_brush() ?? border;
|
||||
}
|
||||
if (single_order.order_type().is_bts_algo_type()) {
|
||||
algo_type = single_order.order_type();
|
||||
} else {
|
||||
algo_type = CustomOrderDefinition.CustomOneofCase.None;
|
||||
}
|
||||
|
||||
} else {
|
||||
border = new SolidColorBrush(color);
|
||||
is_exchange_order = false;
|
||||
}
|
||||
has_multiple_orders_at_same_level = _orders.Count > 1;
|
||||
is_hedge_order = _orders.Any(op => op.Value.order.sub_type() == OrderSubType.HedgingLeg);
|
||||
has_multiple_orders_at_same_level = num_root_orders > 1;
|
||||
is_hedge_order = _root_nodes().Any(o => o.sub_type() == OrderSubType.HedgingLeg);
|
||||
|
||||
if (_orders.Any()) {
|
||||
summary = _orders.Values.Select(op => op.order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
|
||||
}
|
||||
_set_badge_text();
|
||||
|
||||
string _generate_abbreviation_for_algo_studio_algo(AlgoData? a) {
|
||||
char[] delim = { ' ' };
|
||||
string candidate = string.Empty;
|
||||
if (a.HasValue) {
|
||||
var algo_name = a.Value.name;
|
||||
var words = algo_name.Split(delim);
|
||||
if (words.Length == 1) {
|
||||
var single_word = words[0];
|
||||
if (single_word.Length > 1) {
|
||||
candidate = words[0];
|
||||
} else {
|
||||
candidate = single_word;
|
||||
}
|
||||
} else {
|
||||
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
|
||||
}
|
||||
}
|
||||
return candidate.ToUpper();
|
||||
}
|
||||
|
||||
string _sanitize_abbreviation(string ab) {
|
||||
string candidate = ab;
|
||||
if (ab.Length > 2) {
|
||||
candidate = candidate.Substring(0, 2);
|
||||
}
|
||||
return candidate.ToUpper();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void _set_badge_text() {
|
||||
@@ -132,12 +171,32 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(text_lines));
|
||||
}
|
||||
|
||||
public void add_order((OrderRecordImmutable order, uint? p) new_op) {
|
||||
/// <summary>
|
||||
/// Add Order performs an add the first time it's called via the constructor. When it's called, directly
|
||||
/// it really serves as an update/replace. Also note that since the min_piq is a separate piece of state representing
|
||||
/// the lowest piq of the orders the badge represents, we must reevaluate if that has changed on said update.
|
||||
/// </summary>
|
||||
public void add_order((OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta) new_op) {
|
||||
_orders[new_op.order.Key] = new_op;
|
||||
// reevaluate min piq if this order's piq is less
|
||||
if (new_op.p.HasValue) {
|
||||
// if this lovm only represents 1 order and the piq is now different, regardless of what it is that's our piq
|
||||
if (_orders.Count == 1 && new_op.p.Value != min_piq) {
|
||||
min_piq = new_op.p.Value;
|
||||
} else if (new_op.p.Value < min_piq) {
|
||||
min_piq = new_op.p.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_order(uint order_key) {
|
||||
_orders.Remove(order_key);
|
||||
// on a remove, potentially the order w/ the lowest piq has been removed, so
|
||||
// if the min() is now > than the current min_piq, update the min_piq
|
||||
var prospective_piq = _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
|
||||
if (prospective_piq > min_piq) {
|
||||
min_piq = prospective_piq;
|
||||
}
|
||||
}
|
||||
|
||||
//public void update(OrderRecordImmutable neworder) {
|
||||
@@ -147,13 +206,22 @@ namespace mbts.spark {
|
||||
// }
|
||||
//}
|
||||
|
||||
public uint security_key => _parent.security.Id;
|
||||
public uint security_key => _owner.security.Id;
|
||||
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
|
||||
public uint? min_piq => _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
|
||||
public uint? min_piq {
|
||||
get {
|
||||
return _min_piq;
|
||||
}
|
||||
set {
|
||||
if (Set(ref _min_piq, value)) {
|
||||
piq_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
public double real_price => _real_price;
|
||||
public Side side {
|
||||
get {
|
||||
var distinct_sides = _orders.Values.Select(op => op.order.side()).Distinct();
|
||||
var distinct_sides = _root_nodes().Select(o => o.side()).Distinct();
|
||||
if (distinct_sides.Count() > 1) {
|
||||
return Side.Undefined;
|
||||
}
|
||||
@@ -161,16 +229,14 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<OrderRecordImmutable> _parent_and_child_not_sharing_slot() => _orders.Values.Select(op => op.order).Where(o => !_orders.ContainsKey(o.ParentKey));
|
||||
IEnumerable<OrderRecordImmutable> _root_nodes() => _orders.Values.Select(_owner.get_root_at_price).DistinctOn(OrderUtils.RecordKeySelector);
|
||||
|
||||
public IReadOnlyList<string> text_lines { get; private set; }
|
||||
|
||||
public bool has_parent => _orders.Values.Any( op => op.order.has_parent());
|
||||
public bool has_phantom_price => !double.IsNaN(_phantom_price);
|
||||
|
||||
public ICommand detach_command => new DelegateCommand(() => _execute_command(_crack_order), () => !has_parent);
|
||||
public ICommand cancel_command => new DelegateCommand(() => _execute_command(_cancel_order));
|
||||
//public ICommand modify_command => new DelegateCommand(() => _execute_command(() => _bus.post(SparkTags.ShowModifyOrderTag, _order.Key)), () => !has_parent);
|
||||
|
||||
public event Action command_executed;
|
||||
void _execute_command(Action action) {
|
||||
action();
|
||||
@@ -184,27 +250,21 @@ namespace mbts.spark {
|
||||
public Brush background { get => _background.Value; private set => _background.Value = value; }
|
||||
public Brush hoverbackground { get => _hoverbackground.Value; private set => _hoverbackground.Value = value; }
|
||||
public Brush border { get => _border.Value; private set => _border.Value = value; }
|
||||
public string summary { get => _summary.Value; private set => _summary.Value = value; }
|
||||
public bool is_exchange_order { get => _is_exchange_order.Value; private set => _is_exchange_order.Value = value; }
|
||||
public string summary => _root_nodes().Any() ? $"Badge containing root orders: {string.Join(",", _root_nodes().Select(o => o.Key))}" : string.Empty;
|
||||
public bool has_multiple_orders_at_same_level { get => _has_multiple_orders_at_same_level.Value; private set => _has_multiple_orders_at_same_level.Value = value; }
|
||||
public bool is_hedge_order { get => _is_hedge_order.Value; private set => _is_hedge_order.Value = value; }
|
||||
public Brush parent_algo_type_brush { get => _parent_algo_type_brush.Value; private set => _parent_algo_type_brush.Value = value; }
|
||||
// the setter is public so that the LadderVM can set this field
|
||||
public CustomOrderDefinition.CustomOneofCase parent_algo_type { get => _parent_algo_type.Value; set => _parent_algo_type.Value = value; }
|
||||
public CustomOrderDefinition.CustomOneofCase algo_type { get => _algo_type.Value; private set => _algo_type.Value = value; }
|
||||
public string algo_studio_abbreviation { get => _algo_studio_abbreviation.Value; private set => _algo_studio_abbreviation.Value = value; }
|
||||
|
||||
public IEnumerable<OrderRecordImmutable> orders => _orders.Values.Select(o => o.order);
|
||||
public IReadOnlyList<uint> order_keys => _orders.Keys.ToReadOnlyList();
|
||||
public IEnumerable<OrderRecordImmutable> root_orders => _root_nodes();
|
||||
|
||||
void _cancel_order() {
|
||||
foreach (var op in _orders.Values) {
|
||||
model.order_gateway.cancel_order(op.order).ignore();
|
||||
foreach (var order in _root_nodes()) {
|
||||
model.order_gateway.cancel_order(order).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void _crack_order() {
|
||||
var distinct_parent_keys = _orders.Values.Where(op => op.order.has_parent()).Select(x => x.order.ParentKey).Distinct();
|
||||
model.order_gateway.crack_orders(distinct_parent_keys).ignore();
|
||||
}
|
||||
|
||||
public async void try_set_price(OrderRecordImmutable order, double new_price) {
|
||||
// don't change stop or stop_limits => server crash
|
||||
if (order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Stop || order.order_type() == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.StopLimit) return;
|
||||
@@ -212,7 +272,7 @@ namespace mbts.spark {
|
||||
_phantom_price = new_price;
|
||||
RaisePropertyChanged(nameof(price));
|
||||
++_n_active_price_change_ops;
|
||||
if (await _parent.model.order_gateway.update_order(order, new_price, order.State.Quantity)) {
|
||||
if (await _owner.model.order_gateway.update_order(order, new_price, order.State.Quantity)) {
|
||||
// wait 3 secs, then give up if price change still hasn't taken effect
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
@@ -234,53 +294,35 @@ namespace mbts.spark {
|
||||
price_filter.minimum.value = real_price;
|
||||
price_filter.maximum.value = real_price;
|
||||
|
||||
var obvm = new OrderBookVM(null, _parent.bus, null, _parent.model.order_gateway, null, true);
|
||||
var state = new bts.shell.OrderBookWindowFactory.OrderBookStartupData(
|
||||
new bts.shell.OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_parent.security),
|
||||
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.OrderBookWindowFactory.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 | Qty | Price | ...
|
||||
// ------------------------
|
||||
//
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.Price.ToString());
|
||||
var price_index = obvm.columns.indexOf(price_col);
|
||||
if (price_index > -1) {
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.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 == OrderBookView.Columns.Size.ToString());
|
||||
var qty_index = obvm.columns.indexOf(qty_col);
|
||||
if (qty_index > -1) {
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.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());
|
||||
}
|
||||
}
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"Price: {this.price} Order Count: {this.orders.Count()} PIQ: {(this.min_piq.HasValue ? this.min_piq.Value.ToString() : "")}";
|
||||
return $"Price: {price} Order Count: {_orders.Values.Count()} Root Order Count: {_root_nodes().Count()} PIQ: {(this.min_piq.HasValue ? this.min_piq.Value.ToString() : "")}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+293
-124
@@ -1,4 +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;
|
||||
@@ -7,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;
|
||||
@@ -18,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"),
|
||||
@@ -51,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;
|
||||
@@ -84,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;
|
||||
@@ -108,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;
|
||||
@@ -163,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;
|
||||
@@ -172,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 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 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 => GridOrLadderSettingsSelectorKeys.Ladder;
|
||||
|
||||
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();
|
||||
@@ -260,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;
|
||||
@@ -277,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) {
|
||||
@@ -309,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;
|
||||
@@ -318,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);
|
||||
@@ -333,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(
|
||||
@@ -365,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() {
|
||||
@@ -390,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 {
|
||||
@@ -448,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;
|
||||
@@ -457,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 {
|
||||
@@ -480,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() =>
|
||||
@@ -489,6 +653,7 @@ namespace mbts.spark {
|
||||
sell = sell.get_settings(),
|
||||
passive = passive.get_settings(),
|
||||
total = total.get_settings(),
|
||||
show_ltq = show_last_trade
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,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; }
|
||||
@@ -585,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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -600,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;
|
||||
}
|
||||
@@ -613,4 +782,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderVM : DisposableViewModelBase, IWillLetYouKnowWhenIDispose, IKeyListener {
|
||||
static readonly Brush _transparent_status_brush = BTSColors.UI.transparent;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly LadderModel _model;
|
||||
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;
|
||||
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 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, 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, IObservable<LadderSettings> settings) =>
|
||||
create_readonly(bus, model, LadderSettings.Default, settings);
|
||||
|
||||
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, 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);
|
||||
this.order_properties_vm = order_properties_vm;
|
||||
_high = backing(nameof(high), double.NaN);
|
||||
_low = backing(nameof(low), double.NaN);
|
||||
_change = backing(nameof(change), double.NaN);
|
||||
_open = backing(nameof(open), double.NaN);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
// init window-specific settings: these properties should not observe settings updates; they are independent from the settings dialog, so it doesn't make sense that the settings dialog should cause them to change.
|
||||
var ws = init.get_window_specifics();
|
||||
_orders_column_width = backing(nameof(orders_column_width), ws.orders_column_width);
|
||||
_bid_column_width = backing(nameof(bid_column_width), ws.bid_column_width);
|
||||
_price_column_width = backing(nameof(price_column_width), ws.price_column_width);
|
||||
_ask_column_width = backing(nameof(ask_column_width), ws.ask_column_width);
|
||||
_last_column_width = backing(nameof(last_column_width), ws.last_column_width);
|
||||
_piq_column_width = backing(nameof(piq_column_width), ws.piq_column_width);
|
||||
_volume_column_width = backing(nameof(volume_column_width), ws.volume_column_width);
|
||||
_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
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), init.auto_clear);
|
||||
_status = backing(nameof(status_message), "");
|
||||
_status_brush = backing(nameof(status_background), _transparent_status_brush);
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
/// first things first: assign our <see cref="current_settings"/>
|
||||
(s => current_settings = s),
|
||||
// brush props
|
||||
(_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background)).set,
|
||||
(_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground)).set,
|
||||
(_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(_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 DelegateCommand(() => request_settings_dialog?.Invoke());
|
||||
|
||||
orders = new ObservableCollection<LadderOrderVM>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(settings_observer),
|
||||
get_order_properties_editor_hooks(),
|
||||
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 => {
|
||||
// Here we manage N orders to 1 price slot in the ladder
|
||||
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
|
||||
_cache = working.state;
|
||||
|
||||
foreach (var os in _cache.Values) {
|
||||
var wo = os.order;
|
||||
var slot_price_target = wo.price();
|
||||
var order_type = wo.order_type();
|
||||
var filled = wo.filled();
|
||||
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
|
||||
// if the stop order has been submitted but hasn't been triggered
|
||||
// we draw the badge at the stop/trigger price, else the price price
|
||||
if (filled == 0) { slot_price_target = wo.stop_price(); }
|
||||
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
|
||||
// consistent behavior as Stop order above.
|
||||
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
|
||||
}
|
||||
|
||||
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.Select(s => s.badge_style).OnDispatcher());
|
||||
_slot_price_vs_orders[slot_price_target] = slot_vm;
|
||||
}
|
||||
|
||||
// keep track of slot prices vs orders; we use this below to remove orders from the LadderOrderVMs
|
||||
// that are not found in this callback run
|
||||
if (!orders_used_in_this_update.TryGetValue(slot_price_target, out HashSet<uint> orders_at_level)) {
|
||||
orders_at_level = new HashSet<uint>();
|
||||
}
|
||||
orders_at_level.Add(wo.Key);
|
||||
orders_used_in_this_update[slot_price_target] = orders_at_level;
|
||||
|
||||
}
|
||||
|
||||
// remove any orders from the lovm that aren't in here
|
||||
foreach(var kvp in orders_used_in_this_update) {
|
||||
var slot_vm = _slot_price_vs_orders[kvp.Key];
|
||||
foreach(var existing_order_key in slot_vm.order_keys) {
|
||||
if (!kvp.Value.Contains(existing_order_key)) {
|
||||
slot_vm.remove_order(existing_order_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update dirty brushes and text
|
||||
foreach (var lovm in _slot_price_vs_orders.Values) {
|
||||
lovm.update();
|
||||
}
|
||||
|
||||
// complete removal of slots that were not updated
|
||||
// orders_used_in_this_update.Keys are the price levels in the update
|
||||
foreach (var slot_price in _slot_price_vs_orders.Keys.ToList()) {
|
||||
if (!orders_used_in_this_update.Keys.Contains(slot_price)) {
|
||||
_slot_price_vs_orders.TryGetValue(slot_price, out LadderOrderVM removal_candidate);
|
||||
if (removal_candidate != null) {
|
||||
removal_candidate.Dispose();
|
||||
}
|
||||
_slot_price_vs_orders.Remove(slot_price);
|
||||
}
|
||||
}
|
||||
|
||||
orders.sync_with(_slot_price_vs_orders.Values);
|
||||
}),
|
||||
WorkLoop.Start($"Ladder '{security}'", _tick),
|
||||
Disposable.Create(() => _on_disposed_impl?.Invoke(true))
|
||||
);
|
||||
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, request_settings_dialog, settings_invalidated;
|
||||
|
||||
public MDConsumerHandle md => _model.md;
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
// uint.Max happens when the server is restarted but the client is still up
|
||||
// maybe long term we don't show this data while the exchange connection is down
|
||||
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
|
||||
public bool is_synthetic => _model.security.ContractCase == Security.ContractOneofCase.Synthetic;
|
||||
|
||||
Property<string> _status;
|
||||
public string status_message => _status.Value;
|
||||
Property<Brush> _status_brush;
|
||||
public Brush status_background => _status_brush.Value;
|
||||
|
||||
public ICommand position_click_command { get; }
|
||||
public ICommand status_click_command => SparkCommands.ShowActivityLog.ICommand(_bus);
|
||||
|
||||
public async void status_update(Activity a) {
|
||||
if (a.security == _model.security && a.category != ActivityLogCategory.modify) {
|
||||
_status.set(a.message);
|
||||
_status_brush.set(a.category.fill);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
if (_status.Value == a.message) {
|
||||
_status.set("");
|
||||
_status_brush.set(_transparent_status_brush);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event Action<bool> _on_disposed_impl;
|
||||
event Action<bool> IWillLetYouKnowWhenIDispose.OnDisposed {
|
||||
add => _on_disposed_impl += value;
|
||||
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;
|
||||
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; } = new CalculatorLogic(0);
|
||||
/// Important: <see cref="order_properties_vm"/> can be null.
|
||||
public OrderPropertiesEditorVM order_properties_vm { get; }
|
||||
public ObservableCollection<LadderOrderVM> orders { get; }
|
||||
public IAsyncCommand cancel_command { get; }
|
||||
public ICommand send_order_command { get; }
|
||||
public ICommand show_book_command { get; }
|
||||
public ICommand show_ticket_command { get; }
|
||||
public ICommand settings_command { get; }
|
||||
public Brush bid_background => _bid_background.view;
|
||||
public Brush bid_foreground => _bid_foreground.view;
|
||||
public Brush ask_background => _ask_background.view;
|
||||
public Brush ask_foreground => _ask_foreground.view;
|
||||
public 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); }
|
||||
public uint bid_column_width { get => _bid_column_width.Value; set => _bid_column_width.set(value); }
|
||||
public uint price_column_width { get => _price_column_width.Value; set => _price_column_width.set(value); }
|
||||
public uint ask_column_width { get => _ask_column_width.Value; set => _ask_column_width.set(value); }
|
||||
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
|
||||
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
|
||||
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings { get; }
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
var order = op.order;
|
||||
double p = order.price();
|
||||
foreach (var o in lineage(order.ParentKey)) {
|
||||
if (_equality.Equals(p, o.price()))
|
||||
order = o;
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
internal IEnumerable<OrderRecordImmutable> lineage(uint key) {
|
||||
while (key != 0 && _cache.TryGetValue(key, out var op)) {
|
||||
yield return op.order;
|
||||
key = op.order.ParentKey;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderSettings get_save_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) {
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
|
||||
if (!KeyboardExtensions.IsCtrlDown()) return;
|
||||
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
|
||||
if (new_algo != null) {
|
||||
order_properties_vm.selected_algo_definition = new_algo;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void on_mini_orderbook_closed(bts.shell.orderbook.OrderBookStartupData ob_state) {
|
||||
current_settings = SparkUtils.clone_proto(current_settings);
|
||||
current_settings.mini_ob = new LadderMiniOrderBookSettings(ob_state.orderbook_grid.general);
|
||||
settings_invalidated?.Invoke();
|
||||
}
|
||||
|
||||
public void send_order((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm == null || args.button == MouseButton.Middle)
|
||||
return;
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && current_settings.alternate_order_size == 0)) {
|
||||
return;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
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() {
|
||||
bool last_updated = md.get_last_trade(ref _last_trade_time, out NativePriceSize last, out DateTime time, out ulong volume);
|
||||
if (_misc_time != _model.md.misc_timestamp || last_updated) {
|
||||
_high.set(md.session_high);
|
||||
_low.set(md.session_low);
|
||||
_open.set(md.open_price);
|
||||
_settlement.set(md.settlement_price);
|
||||
if (!double.IsNaN(last.price)) {
|
||||
_change.set(last.price - md.settlement_price);
|
||||
} else {
|
||||
ulong unused = 0;
|
||||
_model.md.get_bbo(ref unused, out NativePriceSize bid, out NativePriceSize ask);
|
||||
double mid = TickUtils.mid_tick(security.ContractSpec, bid.price, ask.price);
|
||||
_change.set(mid - md.settlement_price);
|
||||
}
|
||||
_volume.set(volume);
|
||||
_misc_time = _model.md.misc_timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _show_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
void _show_ticket() {
|
||||
if (order_properties_vm != null)
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (new OrderDefinitionImmutable(order_properties_vm.create(Side.Buy)), 0d, 0u));
|
||||
}
|
||||
|
||||
void _tick() {
|
||||
_update_header();
|
||||
on_tick?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+365
-29
@@ -1,12 +1,14 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.spark.algo;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -16,8 +18,10 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
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;
|
||||
@@ -54,14 +58,20 @@ namespace mbts.spark {
|
||||
LadderVM _vm;
|
||||
LadderTactileAdorner _tactile_adorner;
|
||||
ulong _prices_updated;
|
||||
NativePriceSize[] _bids, _asks;
|
||||
bool _price_dirty = false;
|
||||
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
bool _price_dirty = false, _piq_dirty = false;
|
||||
double[] _price_list = null;
|
||||
IComparer<double> _price_comparer;
|
||||
double _last_origin_price;
|
||||
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;
|
||||
@@ -82,6 +92,7 @@ namespace mbts.spark {
|
||||
};
|
||||
RecenterCommand = new DelegateCommand(_scroll_to_center);
|
||||
DataContextChanged += (s, e) => _connect_vm();
|
||||
IsVisibleChanged += (s, e) => _connect_vm();
|
||||
Unloaded += (s, e) => {
|
||||
Disposable.dispose(ref _settings_sub);
|
||||
Disposable.dispose(ref _tick_sub);
|
||||
@@ -97,7 +108,7 @@ namespace mbts.spark {
|
||||
|
||||
public event Action visible_columns_changed;
|
||||
|
||||
public IEnumerable<ColumnType> visible_columns =>
|
||||
public IEnumerable<ColumnType> visible_column_types =>
|
||||
columns.Where(c => c.settings.show)
|
||||
.Select(c => c.column_type);
|
||||
|
||||
@@ -205,6 +216,26 @@ namespace mbts.spark {
|
||||
set => SetValue(PIQColumnWidthProperty, value);
|
||||
}
|
||||
|
||||
|
||||
#region CanUserSendOrders
|
||||
public static readonly DependencyProperty CanUserSendOrdersProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(CanUserSendOrders),
|
||||
typeof(bool),
|
||||
typeof(LadderGridHost),
|
||||
new PropertyMetadata(true, (d, e) => ((LadderGridHost)d).OnCanUserSendOrdersChanged(e)));
|
||||
|
||||
public bool CanUserSendOrders {
|
||||
get => (bool)GetValue(CanUserSendOrdersProperty);
|
||||
set => SetValue(CanUserSendOrdersProperty, value);
|
||||
}
|
||||
|
||||
event Action<bool> CanUserSendOrdersChanged;
|
||||
|
||||
void OnCanUserSendOrdersChanged(DependencyPropertyChangedEventArgs e) =>
|
||||
CanUserSendOrdersChanged?.Invoke((bool)e.NewValue);
|
||||
#endregion
|
||||
|
||||
#region Command
|
||||
public ICommand Command {
|
||||
get => (ICommand)GetValue(CommandProperty);
|
||||
@@ -235,11 +266,15 @@ namespace mbts.spark {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (LadderOrderVM item in e.NewItems) {
|
||||
_ordershost.add_order(item);
|
||||
item.piq_changed += _set_piq_dirty;
|
||||
_set_piq_dirty();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (LadderOrderVM item in e.OldItems) {
|
||||
_ordershost.remove_order(item);
|
||||
item.piq_changed -= _set_piq_dirty;
|
||||
_set_piq_dirty();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
@@ -250,13 +285,37 @@ namespace mbts.spark {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
void _set_piq_dirty() { _piq_dirty = true; }
|
||||
|
||||
#endregion
|
||||
|
||||
Column[] columns { get; set; } = new Column[0];
|
||||
IEnumerable<Column> _visible_columns => columns.Where(c => c.settings.show);
|
||||
|
||||
public void drag_out_of_bounds() =>
|
||||
_tactile_adorner?.drag_out_of_bounds();
|
||||
|
||||
public void scroll_to_price(double price) {
|
||||
if (_scrollViewer == null || _price_list.is_null_or_empty())
|
||||
return;
|
||||
if (security.ContractSpec.tickdiff(_price_list[0], price) is int target_row) {
|
||||
int visible_row_count = (int)(_scrollViewer.ActualHeight / _settings.row_height);
|
||||
int curr_row = visible_row_count + (int)(_scrollViewer.VerticalOffset / _settings.row_height);
|
||||
if ((target_row > curr_row && target_row < _row_count) || (target_row < (curr_row - visible_row_count) && target_row >= 0))
|
||||
_scroll_to_row(target_row);
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<ColumnType> subscribe_hover_column() =>
|
||||
Observable.Create<ColumnType>(o => {
|
||||
notify(Mouse.GetPosition(this));
|
||||
return this.SubscribePreviewMouseMove((s, e) => notify(e.GetPosition(this)));
|
||||
void notify(Point pt) =>
|
||||
o.OnNext(_column_for_point(pt)?.column_type ?? ColumnType.None);
|
||||
});
|
||||
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
base.OnApplyTemplate();
|
||||
_scrollViewer = this.descendants_of_type<ScrollViewer>().First();
|
||||
@@ -266,27 +325,156 @@ 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)
|
||||
_scroll_to_center();
|
||||
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 == MouseButton.Right);
|
||||
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);
|
||||
} else
|
||||
Command.Execute(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();
|
||||
}
|
||||
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) {
|
||||
var query = MBOPopupModel.default_query.with(
|
||||
security_id: _vm.model.security.Id,
|
||||
price_level: price_for_point(point)
|
||||
);
|
||||
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
|
||||
// Load the first set of orders so we know if there are any before showing the popup
|
||||
var result = await model.load_orders();
|
||||
if (result.success && result.value.orders.Count > 0) {
|
||||
var view = new MBOPopupView(model, result.value);
|
||||
var popup = new AutoClosePopup {
|
||||
PlacementTarget = this,
|
||||
PlacementRectangle = new Rect(point, point),
|
||||
IsOpen = true,
|
||||
Child = new Border {
|
||||
BorderBrush = Brushes.Black,
|
||||
BorderThickness = new Thickness(0.5),
|
||||
Child = new ScrollViewer {
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
|
||||
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
|
||||
Content = view,
|
||||
}
|
||||
}
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
@@ -325,12 +513,24 @@ 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
|
||||
|
||||
#region Column Utility Methods
|
||||
Column _column_for_point(Point point) =>
|
||||
columns.FirstOrDefault(c => c.x_right > point.X, columns.Length > 0 ? columns[columns.Length - 1] : Column.Empty);
|
||||
_visible_columns.FirstOrDefault(c => c.x_right > point.X, columns.Length > 0 ? columns[columns.Length - 1] : Column.Empty);
|
||||
|
||||
double _column_start(ColumnType col_type) =>
|
||||
columns.TakeWhile(c => c.column_type != col_type).Sum(c => c.settings.width);
|
||||
@@ -433,18 +633,89 @@ namespace mbts.spark {
|
||||
void _connect_vm() {
|
||||
_dispose_resources();
|
||||
_vm = DataContext as LadderVM;
|
||||
if (_vm != null) {
|
||||
if (!IsVisible)
|
||||
return;
|
||||
else if (_vm == null) {
|
||||
_clear_visuals();
|
||||
} else {
|
||||
_vm.OnDisposed += _on_vm_disposed;
|
||||
_prices_updated = 0;
|
||||
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
|
||||
_price_dirty = true;
|
||||
_piq_dirty = true;
|
||||
_vm.on_tick += _tick;
|
||||
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
|
||||
_volume_sub = _vm.model.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
|
||||
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
|
||||
_tick_sub = Disposable.Create(() => _vm.on_tick -= _tick);
|
||||
}
|
||||
}
|
||||
|
||||
#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();
|
||||
|
||||
@@ -473,11 +744,21 @@ namespace mbts.spark {
|
||||
Storyboard.SetTargetProperty(anim, new PropertyPath(Behavior.AnimatedScrollViewerVerticalOffsetProperty));
|
||||
sb.Begin();
|
||||
} else {
|
||||
_scrollViewer.ScrollToVerticalOffset(_row_count * (_settings.row_height / 2) - (ActualHeight / 2));
|
||||
_scroll_to_row(_row_count / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _scroll_to_row(int row) =>
|
||||
_scrollViewer.ScrollToVerticalOffset((row * _settings.row_height) - (ActualHeight / 2));
|
||||
|
||||
IObservable<bool> _subscribe_can_send_orders() =>
|
||||
Observable.Create<bool>(o => {
|
||||
o.OnNext(CanUserSendOrders);
|
||||
CanUserSendOrdersChanged += o.OnNext;
|
||||
return Disposable.Create(() => CanUserSendOrdersChanged -= o.OnNext);
|
||||
});
|
||||
|
||||
#region Timer.Tick, Refresh Methods
|
||||
void _tick() {
|
||||
if (_settings != null) {
|
||||
@@ -523,8 +804,10 @@ namespace mbts.spark {
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
}
|
||||
// TODO - detect changes in PIQ to selectively redraw
|
||||
_refresh_piq();
|
||||
if (_piq_dirty) {
|
||||
_refresh_piq();
|
||||
_piq_dirty = false;
|
||||
}
|
||||
_refresh_volume_at_price();
|
||||
_last_origin_price = _mkt_break_lines.origin;
|
||||
_price_dirty = false;
|
||||
@@ -565,8 +848,7 @@ namespace mbts.spark {
|
||||
if (!_settings.piq_column.show)
|
||||
return;
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
var piq_list = _vm.price_vs_piq;
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, piq_list.ToArray(), _settings.row_height, security.ContractSpec, _settings.piq_column);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
@@ -582,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} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +975,26 @@ namespace mbts.spark {
|
||||
_ordershost.Height = height;
|
||||
_render_grid_background();
|
||||
}
|
||||
|
||||
void _clear_visuals() {
|
||||
if (_settings == null)
|
||||
return;
|
||||
_price_list = new double[_row_count];
|
||||
for (int i = 0; i < _price_list.Length; i++)
|
||||
_price_list[i] = double.NaN;
|
||||
Array.Clear(_bids, 0, _bids.Length);
|
||||
Array.Clear(_asks, 0, _asks.Length);
|
||||
_price_dirty = true;
|
||||
LadderRender.clear_prices(_price_vis_tup.visual, _row_count, _settings.row_height, _settings.price_column, _price_vis_tup.glyph_context);
|
||||
_refresh_bid_mkt_break();
|
||||
_refresh_ask_mkt_break();
|
||||
_refresh_mid_mkt_break();
|
||||
_refresh_session_low_line();
|
||||
_refresh_session_high_line();
|
||||
_refresh_session_range();
|
||||
_refresh_last_trade();
|
||||
_refresh_sizes();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Market Break, Session Low/High Utils
|
||||
@@ -748,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;
|
||||
}
|
||||
@@ -913,4 +1249,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -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 {
|
||||
@@ -72,8 +70,7 @@ namespace mbts.spark {
|
||||
panel.Children.Remove(item);
|
||||
}
|
||||
}
|
||||
} //else
|
||||
//Log.Ui.warn($"Failed to remove order ({order.id}): order not in {nameof(LadderOrdersHost)}'s {nameof(_orders)}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void clear_orders() {
|
||||
@@ -140,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,
|
||||
@@ -156,7 +154,15 @@ 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;
|
||||
}
|
||||
};
|
||||
// This is useful for troubleshooting focus issues
|
||||
//popup.LostKeyboardFocus += (ss, ee) => {
|
||||
// System.Diagnostics.Debug.WriteLine($"Lost Focus Event - Current Focus: {mbts.utils.MiscUtils.who_has_keyboard_focus()}");
|
||||
@@ -187,11 +193,8 @@ namespace mbts.spark {
|
||||
Margin = Badge.DefaultMargin,
|
||||
Orientation = Orientation.Horizontal,
|
||||
MaxWidth = _col_width
|
||||
//MaxHeight = _row_height,
|
||||
//ClipToBounds = true
|
||||
};
|
||||
|
||||
//var view_box = new Viewbox { Child = panel, Width = _col_width, Height = _row_height };
|
||||
_position_panel(panel, price);
|
||||
_stacks[price] = new PriceCellComponents(panel);
|
||||
Children.Add(panel);
|
||||
@@ -214,7 +217,7 @@ namespace mbts.spark {
|
||||
Log.Ui.info("Drop - " + vm.summary);
|
||||
double price = _parent.price_for_point(e.GetPosition(_parent));
|
||||
if (price != vm.price) {
|
||||
foreach (var order in vm.orders) {
|
||||
foreach (var order in vm.root_orders) {
|
||||
vm.try_set_price(order, price);
|
||||
}
|
||||
}
|
||||
+52
-12
@@ -1,10 +1,10 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.api.md.auxiliary;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -86,6 +86,16 @@ namespace mbts.spark {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void clear_prices(DrawingVisual price, int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
var fprices = _format_empty_prices(row_count, row_height, settings, glyph_context);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, row_count)) {
|
||||
for (int x = 0; x < row_count; ++x)
|
||||
dc.DrawGlyphRun(foreground, fprices[x]);
|
||||
}
|
||||
}
|
||||
|
||||
public static double[] render_prices(DrawingVisual price, GlyphContext glyph_context, double mid, double wrt_price, bool format_in_tick_terms, int row_count, double row_height,
|
||||
LadderColumnSettings settings, ContractSpecificationImmutable spec) {
|
||||
var dprices = gen_prices(mid, row_count, spec);
|
||||
@@ -184,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);
|
||||
@@ -203,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);
|
||||
@@ -243,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,22 +312,22 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
// this is basically a copy of redner_sizes up above
|
||||
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, (double price, uint piq)[] piqs, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
|
||||
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
|
||||
if (!prices.within_range(origin_index) || piqs.is_null_or_empty())
|
||||
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
|
||||
return;
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
for (int i = 0; i < piqs.Length; i++) {
|
||||
double price = piqs[i].price;
|
||||
foreach (var entry in pvp) {
|
||||
double price = entry.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
|
||||
curr_index += tick_diff;
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Length) {
|
||||
var gr = format_text(piqs[i].piq.ToString(), curr_index, row_height, settings, glyph_context);
|
||||
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
@@ -317,6 +351,12 @@ namespace mbts.spark {
|
||||
dc.DrawRectangle(new SolidColorBrush(color.rgb_color()) { Opacity = 0.2 }, null, new Rect(0, high_index * row_height, settings.width, (low_index - high_index) * row_height));
|
||||
}
|
||||
|
||||
static GlyphRun[] _format_empty_prices(int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
|
||||
var result = new GlyphRun[row_count];
|
||||
Parallel.For(0, row_count, i => result[i] = format_text("-", i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
|
||||
static IDisposable _draw_with_clip(DrawingContext dc, LadderColumnSettings settings, double row_height, int row_count) {
|
||||
dc.PushClip(new RectangleGeometry(new Rect(0, 0, settings.width, row_height * row_count)));
|
||||
return Disposable.Create(dc.Pop);
|
||||
File renamed without changes.
File renamed without changes.
File diff suppressed because it is too large.
Load diff
+7
-2
@@ -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();
|
||||
|
||||
+36
-4
@@ -1,9 +1,9 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
@@ -30,7 +30,7 @@ namespace mbts.spark {
|
||||
readonly LadderGridHost _host;
|
||||
readonly IList<IDisposable> _hooks;
|
||||
|
||||
bool _is_mouse_over, _is_mouse_down, _is_mouse_dragging;
|
||||
bool _is_mouse_over, _is_mouse_down, _is_mouse_dragging, _can_send_orders;
|
||||
int _curr_row;
|
||||
Column _curr_column = Column.Empty;
|
||||
|
||||
@@ -41,6 +41,10 @@ namespace mbts.spark {
|
||||
_bid_mouse_down_pen = new Pen(new SolidColorBrush(Color.FromRgb(102, 178, 255)), 1).frozen();
|
||||
_ask_mouse_down_pen = new Pen(new SolidColorBrush(Color.FromRgb(255, 178, 102)), 1).frozen();
|
||||
_hooks = new List<IDisposable> {
|
||||
_host._subscribe_can_send_orders().Subscribe(v => {
|
||||
_can_send_orders = v;
|
||||
_host.Cursor = _evaluate_position();
|
||||
}),
|
||||
_host.SubscribeMouseEnter(_host_MouseEnter),
|
||||
_host.SubscribeMouseMove(_host_MouseMove),
|
||||
_host.SubscribeMouseLeave(_host_MouseLeave),
|
||||
@@ -88,7 +92,7 @@ namespace mbts.spark {
|
||||
_draw_cell(drawingContext, _navy_pen, x, y, _curr_column.settings.width);
|
||||
}
|
||||
}
|
||||
else if (_is_mouse_over && (_curr_column.column_type == ColumnType.Bid || _curr_column.column_type == ColumnType.Ask)) {
|
||||
else if (_is_mouse_over && _can_send_orders && (_curr_column.column_type == ColumnType.Bid || _curr_column.column_type == ColumnType.Ask)) {
|
||||
double y = _curr_row * _host._settings.row_height;
|
||||
double x = _curr_column.x_left;
|
||||
Pen style = default;
|
||||
@@ -112,12 +116,17 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
Cursor _evaluate_position() {
|
||||
var pos = Mouse.GetPosition(_host);
|
||||
return _evaluate_position(_host._column_for_point(pos), pos, _host.columns);
|
||||
}
|
||||
|
||||
Cursor _evaluate_position(Column column, Point point, Column[] columns) {
|
||||
if (columns.Length == 0)
|
||||
return Cursors.Arrow;
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Bid:
|
||||
case ColumnType.Ask: return Cursors.Hand;
|
||||
case ColumnType.Ask: return _can_send_orders ? Cursors.Hand : Cursors.No;
|
||||
default: return Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
@@ -140,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();
|
||||
Loaded 100 of 929 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user