Compare commits
1397
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
189157b1c7 | ||
|
|
64fd8addb2 | ||
|
|
4490ac303e | ||
|
|
666bd07411 | ||
|
|
0ff4624778 | ||
|
|
ab04b4afab | ||
|
|
ac226ddd3b | ||
|
|
0223e43aa2 | ||
|
|
73e5cfdada | ||
|
|
33b4b88af1 | ||
|
|
12f4b4b25b | ||
|
|
96cacb8bb9 | ||
|
|
300a1ed17f | ||
|
|
1a0836ca6d | ||
|
|
88908543e1 | ||
|
|
80c9b6a534 | ||
|
|
f6282c0676 | ||
|
|
02fe83f20b | ||
|
|
84d666c29c | ||
|
|
147e43b464 | ||
|
|
b909a6062e | ||
|
|
421b3af6a7 | ||
|
|
46a340d493 | ||
|
|
3cc7d76a1b | ||
|
|
5123b928f2 | ||
|
|
c2631e3257 | ||
|
|
685181ae28 | ||
|
|
929492c2f0 | ||
|
|
be023ff6cb | ||
|
|
a4d63a1421 | ||
|
|
2fd8c39888 | ||
|
|
eb97fd045a | ||
|
|
a07bc152d1 | ||
|
|
aef8e2dbb1 | ||
|
|
baac6a893d | ||
|
|
df681ca209 | ||
|
|
21b34434ef | ||
|
|
0c17dbb55b | ||
|
|
fd6c6a3055 | ||
|
|
259355d629 | ||
|
|
c6c2d3448d | ||
|
|
f860649996 | ||
|
|
05c3c97713 | ||
|
|
f3be277484 | ||
|
|
0ba311788c | ||
|
|
341eda5962 | ||
|
|
3398bb5b86 | ||
|
|
dc7cacc126 | ||
|
|
6d3d4e1f56 | ||
|
|
433b1abecd | ||
|
|
4544180c5f | ||
|
|
5722fcffc3 | ||
|
|
f1880615b1 | ||
|
|
e60f5678ec | ||
|
|
e9a70e51e4 | ||
|
|
e98d18a5aa | ||
|
|
513cc248b8 | ||
|
|
c024095f75 | ||
|
|
3e5eb80a7c | ||
|
|
942dc1640e | ||
|
|
b5c3abb782 | ||
|
|
3478d1ad52 | ||
|
|
d54e9233d1 | ||
|
|
e94759cd89 | ||
|
|
b58da4a20e | ||
|
|
8426399a23 | ||
|
|
9b2f41c805 | ||
|
|
15f96dda3e | ||
|
|
a9d5c17d63 | ||
|
|
715b629778 | ||
|
|
dd82d127ea | ||
|
|
f97e22d6eb | ||
|
|
f9e04ee28c | ||
|
|
1ce00ca13d | ||
|
|
3b9c2f5d70 | ||
|
|
5cd2dfec18 | ||
|
|
31b2ba9165 | ||
|
|
2b1b1f1681 | ||
|
|
4427b02d8d | ||
|
|
dcd3921379 | ||
|
|
bd156ecbed | ||
|
|
02f5731e1d | ||
|
|
7e75a71898 | ||
|
|
798c4dcd49 | ||
|
|
7e47d7f8a5 | ||
|
|
260ca06cde | ||
|
|
8615adb564 | ||
|
|
ab15d9d348 | ||
|
|
9540a15585 | ||
|
|
e8a467d5f1 | ||
|
|
567c18f325 | ||
|
|
270ab9c3f1 | ||
|
|
9a9024c2c0 | ||
|
|
45a5049a2d | ||
|
|
8b17911da0 | ||
|
|
61556eb534 | ||
|
|
b691dbae80 | ||
|
|
f6be20ce8d | ||
|
|
68428ab89d | ||
|
|
12d54fdb31 | ||
|
|
366e13ef05 | ||
|
|
e94ef6a602 | ||
|
|
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
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-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": "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;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,104 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 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
|
||||
## 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
|
||||
|
||||
+1
-1
Submodule docker/linux updated: ce08a5129f...e13bb5d8df.
@@ -73,6 +73,7 @@ namespace bts {
|
||||
window.Close();
|
||||
message.complete(select_result(vm)).execute(_bus);
|
||||
};
|
||||
vm.cancel_action = () => window.Close();
|
||||
window.move_to_mouse();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
@@ -22,10 +22,14 @@ namespace mbts.ui {
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
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 +39,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 +123,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 +147,13 @@ 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 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 +170,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 +200,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,21 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<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 +111,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 +297,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
|
||||
|
||||
@@ -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,7 +23,6 @@ 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) { }
|
||||
|
||||
@@ -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);
|
||||
@@ -180,6 +180,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,7 +1,9 @@
|
||||
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;
|
||||
@@ -11,12 +13,13 @@ 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 +28,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 +38,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 +49,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,17 +103,44 @@ 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 = 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(() => {
|
||||
@@ -86,8 +161,27 @@ namespace mbts.spark {
|
||||
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> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
@@ -19,7 +20,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
@@ -48,7 +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,9 +63,9 @@ 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
|
||||
@@ -92,8 +93,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) {
|
||||
|
||||
@@ -420,7 +420,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;
|
||||
|
||||
@@ -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,14 +66,15 @@ 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(() => {
|
||||
), 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 WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
@@ -101,16 +114,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();
|
||||
|
||||
-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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-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,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
@@ -72,7 +73,7 @@ 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;
|
||||
@@ -199,14 +200,16 @@ 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 QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
@@ -318,9 +321,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,76 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
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 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_order_def(OrderDefinitionImmutable order_def) {
|
||||
switch (order_def.CustomDef.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(order_def.CustomDef.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (key.factory.structure.structure.Count > 0)
|
||||
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
|
||||
break;
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
public bool Equals(AlgoID other) =>
|
||||
_factory_key == other._factory_key &&
|
||||
_dynamic_key == other._dynamic_key &&
|
||||
_persisted_key == other._persisted_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AlgoID && Equals(obj);
|
||||
|
||||
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 +150,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,139 @@ 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(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
|
||||
for (int i = pos; i < structure.Length; i++) {
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
int mask = 1 << (31 - di);
|
||||
if ((structure[i] & mask) == mask) {
|
||||
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
|
||||
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
|
||||
pos++;
|
||||
_build_def(structure, ref pos, ref child_algo);
|
||||
cad[di].assign_child(algo, child_algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,8 +18,9 @@ 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;
|
||||
@@ -31,22 +34,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>(
|
||||
@@ -83,45 +83,46 @@ 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),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
);
|
||||
// local methods
|
||||
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
|
||||
@@ -131,14 +132,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 +154,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 IConnectableObservable<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) {
|
||||
@@ -208,8 +212,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 +222,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 +232,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -13,47 +14,46 @@ 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 };
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace mbts.spark {
|
||||
};
|
||||
_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,95 @@
|
||||
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; }
|
||||
}
|
||||
|
||||
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);
|
||||
// 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,109 @@
|
||||
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 fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
|
||||
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
|
||||
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(fill_time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return fill_time;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return fill_kind;
|
||||
yield return fill_key;
|
||||
yield return order_key;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mini {
|
||||
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
|
||||
new Dictionary<Columns, (bool is_visible, double? width_override)> {
|
||||
{ Columns.Security, (true, null) },
|
||||
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
|
||||
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
|
||||
{ Columns.Side, (true, ShortColumnWidths.Side) },
|
||||
{ Columns.Size, (true, ShortColumnWidths.Size) },
|
||||
{ Columns.Price, (true, ShortColumnWidths.Price) },
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
|
||||
|
||||
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
|
||||
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
|
||||
return true;
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static GridColumnSettings _convert(GridColumnSettings default_col) =>
|
||||
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
|
||||
default_col.with(is_visible: d.is_visible, width: d.width_override) :
|
||||
default_col.with(is_visible: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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) {
|
||||
+28
-6
@@ -38,6 +38,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 +119,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 +135,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 +144,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 +155,8 @@ namespace mbts.spark {
|
||||
buy.GetHashCode(),
|
||||
sell.GetHashCode(),
|
||||
passive.GetHashCode(),
|
||||
total.GetHashCode());
|
||||
total.GetHashCode(),
|
||||
show_ltq.GetHashCode());
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -219,6 +232,7 @@ namespace mbts.spark {
|
||||
// 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 +244,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;
|
||||
@@ -250,7 +265,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 +274,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,
|
||||
@@ -293,6 +308,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,
|
||||
@@ -304,6 +320,8 @@ namespace mbts.spark {
|
||||
zoom_factor = _default_zoom_factor,
|
||||
net_change = NetChangeTerms.None,
|
||||
};
|
||||
rv.row_height = LadderRender.measure_row_height(rv);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +405,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)]
|
||||
@@ -463,6 +483,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 &&
|
||||
@@ -478,6 +499,7 @@ namespace mbts.spark {
|
||||
|
||||
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(),
|
||||
+17
-7
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
@@ -21,19 +22,26 @@ namespace bts.shell {
|
||||
readonly LadderConfig _ladder_conf;
|
||||
readonly IBus _bus;
|
||||
readonly IWindowManager _wm;
|
||||
readonly MBOService _mbo;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly MarketDataAuxiliaryModel _md_auxiliary_model;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model, MarketDataAuxiliaryModel md_auxiliary_model)
|
||||
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;
|
||||
_md_auxiliary_model = md_auxiliary_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
@@ -56,15 +64,16 @@ namespace bts.shell {
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
|
||||
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
var model = new LadderModel(md, _orders_model, _md_auxiliary_model);
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
var settings_manager = new WindowSettingsManager<LadderSettings>(
|
||||
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
|
||||
us => sanitize_ladder_settings(us.ladder),
|
||||
(us, v) => us.with(sanitize_ladder_settings(v)));
|
||||
var init = state_settings ?? settings_manager.default_settings;
|
||||
init.use_defaults |= state_settings == null;
|
||||
var vm = new LadderVM(_bus, model, settings_manager, init);
|
||||
var vm = LadderVM.create(_bus, model, settings_manager, init);
|
||||
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;
|
||||
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;
|
||||
@@ -139,12 +148,13 @@ namespace bts.shell {
|
||||
|
||||
readonly struct LadderConfig {
|
||||
public readonly uint max_open_ladders;
|
||||
public readonly bool ladders_on_threads, open_algo_popup_on_select;
|
||||
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;
|
||||
}
|
||||
+130
-69
@@ -29,45 +29,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 +96,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 +169,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 +204,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 +227,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 +248,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 +270,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,14 +292,14 @@ 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 obvm = new OrderBookVM(null, _owner.bus, null, _owner.model.order_gateway, null, true);
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_owner.security),
|
||||
price = price_filter,
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
},
|
||||
bts.shell.OrderBookWindowFactory.OrderBookStartupData.default_mini_grid,
|
||||
bts.shell.orderbook.OrderBookStartupData.default_mini_grid,
|
||||
use_defaults: false
|
||||
);
|
||||
// NOTE:
|
||||
@@ -249,30 +307,33 @@ namespace mbts.spark {
|
||||
// if these EditXXX ever support settings.
|
||||
|
||||
// The end result after this group of lines will be
|
||||
// ------------------------
|
||||
// | Cancel | Qty | Price | ...
|
||||
// ------------------------
|
||||
// ----------------------------------------------
|
||||
// | Cancel | Update | Qty | Price | Commit | Attach | Detach | ...
|
||||
// ----------------------------------------------
|
||||
//
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.Price.ToString());
|
||||
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Price.ToString());
|
||||
var price_index = obvm.columns.indexOf(price_col);
|
||||
if (price_index > -1) {
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.EditablePrice.ToString());
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditablePrice.ToString());
|
||||
var existing_editprice_index = obvm.columns.indexOf(existing_editprice_col);
|
||||
if (existing_editprice_index == -1) {
|
||||
// insert the edit price column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
|
||||
}
|
||||
}
|
||||
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.Size.ToString());
|
||||
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Size.ToString());
|
||||
var qty_index = obvm.columns.indexOf(qty_col);
|
||||
if (qty_index > -1) {
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.EditableQty.ToString());
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditableQty.ToString());
|
||||
var existing_editqty_index = obvm.columns.indexOf(existing_editqty_col);
|
||||
if (existing_editqty_index == -1) {
|
||||
// insert the edit quantity column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditableQtyColumn());
|
||||
}
|
||||
}
|
||||
obvm.columns.Insert(0, new OrderBookVM.UpdateColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CancelColumn(nameof(OrderBookRowVM.cancel_command)));
|
||||
|
||||
obvm.load_from(state);
|
||||
@@ -280,7 +341,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
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() : "")}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
@@ -51,6 +52,7 @@ 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"),
|
||||
@@ -163,6 +165,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;
|
||||
@@ -198,7 +201,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
|
||||
public string key => GridOrLadderSettingsSelectorKeys.Ladder;
|
||||
public string key => GridSettingsTemplateKeys.Ladder;
|
||||
|
||||
public void load(LadderSettings settings) {
|
||||
_current = settings;
|
||||
@@ -231,6 +234,7 @@ namespace mbts.spark {
|
||||
_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;
|
||||
@@ -277,6 +281,7 @@ 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;
|
||||
@@ -448,6 +453,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;
|
||||
@@ -462,6 +468,9 @@ namespace mbts.spark {
|
||||
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 +489,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 +499,7 @@ namespace mbts.spark {
|
||||
sell = sell.get_settings(),
|
||||
passive = passive.get_settings(),
|
||||
total = total.get_settings(),
|
||||
show_ltq = show_last_trade
|
||||
};
|
||||
}
|
||||
|
||||
+191
-128
@@ -2,6 +2,7 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
@@ -24,28 +25,44 @@ namespace mbts.spark {
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly LadderModel _model;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _position, _left_panel_width, _zoom_factor;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _left_panel_width, _zoom_factor;
|
||||
//readonly Property<double> _window_width, _window_height;
|
||||
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
|
||||
readonly Property<ulong> _volume;
|
||||
readonly Property<bool> _auto_clear, _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_account_position, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
readonly Property<bool> _auto_clear;
|
||||
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom;
|
||||
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
double _position, _day_position;
|
||||
bool _show_account_position, _show_day_position;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
|
||||
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
|
||||
public static LadderVM create(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) {
|
||||
var non_shared_wsm = settings_manager.as_non_shared();
|
||||
return new LadderVM(bus, model, settings_manager, non_shared_wsm, init, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
}
|
||||
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model) {
|
||||
var default_settings = LadderSettings.Default;
|
||||
return create_readonly(bus, model, new WindowSettingsManager<LadderSettings>.DisconnectedImpl(default_settings), default_settings);
|
||||
}
|
||||
|
||||
public static LadderVM create_readonly(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, LadderSettings init) =>
|
||||
new LadderVM(bus, model, settings_manager, settings_manager.as_non_shared(), init);
|
||||
|
||||
LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager, IWindowSettingsManagerNonShared<LadderSettings> non_shared_wsm, LadderSettings init, OrderPropertiesEditorVM order_properties_vm = null) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
var non_shared_wsm = settings_manager.as_non_shared();
|
||||
_settings_manager = non_shared_wsm;
|
||||
order_properties_vm = new OrderPropertiesEditorVM(_model.order_gateway, Observable.Return(_model.security_md), _settings_manager.subscribe_current().Select(s => new InlineOrderProperties(_model.order_gateway, s)));
|
||||
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);
|
||||
@@ -63,46 +80,51 @@ namespace mbts.spark {
|
||||
_left_panel_width = backing(nameof(left_panel_width), ws.order_entry_panel_width);
|
||||
_zoom_factor = backing(nameof(zoom_factor), ws.zoom_factor);
|
||||
// remember: account_positions needs to subscribe to selected_account; both the sub and account_positions need to be disposed
|
||||
var account_positions = new KeyDrivenObservableSeries<string, double>(a => _model.order_gateway.security_total_position(security.Id, a));
|
||||
_position = backing(nameof(position), account_positions.OnDispatcher());
|
||||
_volume = backing(nameof(volume), 0ul);
|
||||
_auto_clear = backing(nameof(auto_clear), false);
|
||||
_status = backing(nameof(status_message), "");
|
||||
_status_brush = backing(nameof(status_background), _transparent_status_brush);
|
||||
// brush props
|
||||
_bid_background = brush_prop(nameof(bid_background), s => s.bid_column.background.rgb_brush());
|
||||
_bid_foreground = brush_prop(nameof(bid_foreground), s => s.bid_column.foreground.rgb_brush());
|
||||
_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background.rgb_brush());
|
||||
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
|
||||
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
|
||||
// bool visibility props
|
||||
_show_day = bool_prop(nameof(show_day), s => s.day_visible);
|
||||
_show_high = bool_prop(nameof(show_high), s => s.high_visible);
|
||||
_show_low = bool_prop(nameof(show_low), s => s.low_visible);
|
||||
_show_open = bool_prop(nameof(show_open), s => s.open_visible);
|
||||
_show_settle = bool_prop(nameof(show_settle), s => s.settle_visible);
|
||||
_show_volume = bool_prop(nameof(show_volume), s => s.volume_visible);
|
||||
_show_account = bool_prop(nameof(show_account), s => s.account_visible);
|
||||
_show_tif = bool_prop(nameof(show_tif), s => s.tif_visible);
|
||||
_show_order_type = bool_prop(nameof(show_order_type), s => s.order_type_visible);
|
||||
_show_account_position = bool_prop(nameof(show_account_position), s => s.account_position_visible);
|
||||
_show_size_preset = bool_prop(nameof(show_size_preset), s => s.size_presets_visible);
|
||||
_show_auto_clear = bool_prop(nameof(show_auto_clear), s => s.auto_clear_visible);
|
||||
_show_clear = bool_prop(nameof(show_clear), s => s.clear_visible);
|
||||
_show_cancel_buys = bool_prop(nameof(show_cancel_buys), s => s.cancel_buys_visible);
|
||||
_show_cancel_sells = bool_prop(nameof(show_cancel_sells), s => s.cancel_sells_visible);
|
||||
_show_cancel_all = bool_prop(nameof(show_cancel_all), s => s.cancel_all_visible);
|
||||
_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible);
|
||||
|
||||
Property<bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_bool);
|
||||
// All settings observers should be aggregated together, so they can all share the same subscription to the settings
|
||||
// (these subscriptions are not horribly expensive, but they're not free either - even more so with the .OnDispatcher() extension).
|
||||
var settings_observer = new CompositeObserver<LadderSettings> {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
};
|
||||
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
backing(name, init, get_bool, EqualityComparer.Create(get_bool));
|
||||
|
||||
PropertyAutoWrapper<LadderSettings, Brush> brush_prop(string name, Func<LadderSettings, uint> get_color_uint) =>
|
||||
backing(name, init, s => get_color_uint(s).rgb_brush(), EqualityComparer.Create(get_color_uint));
|
||||
//
|
||||
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
|
||||
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(position)));
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
|
||||
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);
|
||||
@@ -125,26 +147,20 @@ namespace mbts.spark {
|
||||
orders = new ObservableCollection<LadderOrderVM>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(settings_observer),
|
||||
settings_command,
|
||||
non_shared_wsm,
|
||||
settings.OnDispatcher().Subscribe(s => order_properties_vm.on_favorite_algos(s.favorite_algos)),
|
||||
order_properties_vm,
|
||||
order_properties_vm.observe_account(true).Select(a => a?.name ?? string.Empty).Subscribe(account_positions),
|
||||
account_positions,
|
||||
get_order_properties_editor_hooks(),
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
model.order_gateway.working_orders_with_piq.OnDispatcher().Subscribe(working => {
|
||||
// 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);
|
||||
var ladders_orders =
|
||||
working.Values
|
||||
.Where(o =>
|
||||
o.order.Def.SecurityKey == security.Id &&
|
||||
(o.order.HasState && o.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible));
|
||||
_cache = working.state;
|
||||
|
||||
foreach (var osec in ladders_orders) {
|
||||
var wo = osec.order;
|
||||
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();
|
||||
@@ -158,9 +174,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
||||
slot_vm.add_order((wo, osec.piq));
|
||||
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
||||
} else {
|
||||
slot_vm = new LadderOrderVM((wo, osec.piq), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
|
||||
_slot_price_vs_orders[slot_price_target] = slot_vm;
|
||||
}
|
||||
|
||||
@@ -177,7 +193,7 @@ namespace mbts.spark {
|
||||
// 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.orders.Select(o => o.Key).ToList()) {
|
||||
foreach(var existing_order_key in slot_vm.order_keys) {
|
||||
if (!kvp.Value.Contains(existing_order_key)) {
|
||||
slot_vm.remove_order(existing_order_key);
|
||||
}
|
||||
@@ -187,25 +203,6 @@ namespace mbts.spark {
|
||||
|
||||
// update dirty brushes and text
|
||||
foreach (var lovm in _slot_price_vs_orders.Values) {
|
||||
// the parent algo is only valid when there is only a single order at a level
|
||||
if (lovm.orders.Count() == 1) {
|
||||
var only_order = lovm.orders.First();
|
||||
|
||||
if (only_order.has_parent()) {
|
||||
var traversal_candidate = only_order;
|
||||
bool exit_early = false;
|
||||
while (!exit_early && traversal_candidate.ParentKey != 0) {
|
||||
if (working.TryGetFirst(o => o.Key == traversal_candidate.ParentKey, out var first))
|
||||
traversal_candidate = first.Value.order;
|
||||
else
|
||||
exit_early = true;
|
||||
}
|
||||
if (!exit_early)
|
||||
// we have successfully reached the root
|
||||
lovm.parent_algo_type = traversal_candidate.Def.CustomDef.CustomCase;
|
||||
}
|
||||
// else this a single order, so do nothing
|
||||
}
|
||||
lovm.update();
|
||||
}
|
||||
|
||||
@@ -226,6 +223,29 @@ namespace mbts.spark {
|
||||
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));
|
||||
}),
|
||||
position_src.Connect());
|
||||
}
|
||||
}
|
||||
|
||||
public IBus bus => _bus;
|
||||
@@ -236,7 +256,7 @@ namespace mbts.spark {
|
||||
// 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 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;
|
||||
@@ -270,40 +290,53 @@ namespace mbts.spark {
|
||||
public double change => _change.Value;
|
||||
public double open => _open.Value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double position => _position.Value;
|
||||
public double position => _position;
|
||||
public double day_position => _day_position;
|
||||
public ulong volume => _volume.Value;
|
||||
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
|
||||
public CalculatorLogic size_calculator { get; }
|
||||
/// 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 Brush bid_background => _bid_background.Value;
|
||||
public Brush bid_foreground => _bid_foreground.Value;
|
||||
public Brush ask_background => _ask_background.Value;
|
||||
public Brush ask_foreground => _ask_foreground.Value;
|
||||
public Brush bid_background => _bid_background.view;
|
||||
public Brush bid_foreground => _bid_foreground.view;
|
||||
public Brush ask_background => _ask_background.view;
|
||||
public Brush ask_foreground => _ask_foreground.view;
|
||||
public DialogCommand settings_command { get; }
|
||||
public bool show_day { get => _show_day.Value; set => _show_day.set(value); }
|
||||
public bool show_high { get => _show_high.Value; set => _show_high.set(value); }
|
||||
public bool show_low { get => _show_low.Value; set => _show_low.set(value); }
|
||||
public bool show_open { get => _show_open.Value; set => _show_open.set(value); }
|
||||
public bool show_settle { get => _show_settle.Value; set => _show_settle.set(value); }
|
||||
public bool show_volume { get => _show_volume.Value; set => _show_volume.set(value); }
|
||||
public bool show_account { get => _show_account.Value; set => _show_account.set(value); }
|
||||
public bool show_tif { get => _show_tif.Value; set => _show_tif.set(value); }
|
||||
public bool show_order_type { get => _show_order_type.Value; set => _show_order_type.set(value); }
|
||||
public bool show_account_position { get => _show_account_position.Value; set => _show_account_position.set(value); }
|
||||
public bool show_size_preset { get => _show_size_preset.Value; set => _show_size_preset.set(value); }
|
||||
public bool show_auto_clear { get => _show_auto_clear.Value; set => _show_auto_clear.set(value); }
|
||||
public bool show_clear { get => _show_clear.Value; set => _show_clear.set(value); }
|
||||
public bool show_cancel_buys { get => _show_cancel_buys.Value; set => _show_cancel_buys.set(value); }
|
||||
public bool show_cancel_sells { get => _show_cancel_sells.Value; set => _show_cancel_sells.set(value); }
|
||||
public bool show_cancel_all { get => _show_cancel_all.Value; set => _show_cancel_all.set(value); }
|
||||
public bool show_zoom { get => _show_zoom.Value; set => _show_zoom.set(value); }
|
||||
public 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); }
|
||||
@@ -315,6 +348,22 @@ namespace mbts.spark {
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
|
||||
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 state = _settings_manager.current_settings();
|
||||
@@ -324,6 +373,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
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));
|
||||
@@ -333,32 +384,41 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public async void send_order((double price, Side? side, bool size_from_alternate) args) {
|
||||
public void send_order((double price, Side? side, MouseButton buton) args) {
|
||||
if (args.buton == MouseButton.Middle) return;
|
||||
var size_from_alternate = args.buton == MouseButton.Right;
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
var s = _settings_manager.current_settings();
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (args.size_from_alternate && s.alternate_order_size == 0))
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
|
||||
return;
|
||||
else if (!args.size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
var order = order_properties_vm.create(args.side.Value);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
order.CustomDef.Stop.StopPrice = args.price;
|
||||
} else if (order.CustomDef.StopLimit != null) {
|
||||
// swap the prices here so that the level clicked
|
||||
// in the ladder is the StopPrice (trigger) and the Price
|
||||
// is what was specificed in the extra parameter box
|
||||
double temp_price = order.CustomDef.StopLimit.StopPrice;
|
||||
order.CustomDef.StopLimit.StopPrice = args.price;
|
||||
args.price = temp_price;
|
||||
}
|
||||
}
|
||||
|
||||
uint size = args.size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
|
||||
await _model.order_gateway.send_order(order, args.price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
}
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
}
|
||||
else {
|
||||
uint size = size_from_alternate ? s.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() {
|
||||
@@ -382,15 +442,18 @@ namespace mbts.spark {
|
||||
|
||||
}
|
||||
|
||||
void _show_book() =>
|
||||
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.OrderBookWindowFactory.OrderBookStartupData {
|
||||
filters = new bts.shell.OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
}
|
||||
});
|
||||
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() =>
|
||||
_bus.post(SparkTags.ShowOrderTicketTag, (new OrderDefinitionImmutable(order_properties_vm.create(Side.Buy)), 0d, 0u));
|
||||
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();
|
||||
+325
-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,19 @@ 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;
|
||||
double _old_last_trade_price = 0.0;
|
||||
bool _tooltips_are_hooked_up = false;
|
||||
ToolTip tooltip_view = new ToolTip();
|
||||
IDisposable _parameterized_order_popup;
|
||||
|
||||
public LadderGridHost() {
|
||||
AllowDrop = true;
|
||||
@@ -82,6 +91,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 +107,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 +215,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 +265,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 +284,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 +324,141 @@ 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);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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 +497,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 +617,87 @@ 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");
|
||||
_hide_tool_tip();
|
||||
//_old_tooltip_row = Int32.MaxValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - not over Volume column");
|
||||
_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 +726,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 +786,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 +830,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() {
|
||||
@@ -585,7 +849,18 @@ namespace mbts.spark {
|
||||
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 ltp = _last_trade.price;
|
||||
var last_index = LadderRender.index_of(_price_list, ltp, _price_comparer);
|
||||
string direction_arrow = string.Empty;
|
||||
if (MathUtils.epsilon_equals(ltp, _old_last_trade_price)) {
|
||||
// do nothing if in here b/c direction_arrow already string.empty
|
||||
} else if (ltp > _old_last_trade_price) {
|
||||
direction_arrow = "▲";
|
||||
} else {
|
||||
direction_arrow = "▼";
|
||||
}
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _last_trade, direction_arrow);
|
||||
_old_last_trade_price = ltp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +935,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 +1043,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 +1209,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -72,8 +72,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() {
|
||||
@@ -157,6 +156,11 @@ namespace mbts.spark {
|
||||
nameof(OrderBookVM.is_row_collection_populated)
|
||||
);
|
||||
popup.Closed += (sender, event_args) => (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 +191,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 +215,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);
|
||||
}
|
||||
}
|
||||
+50
-10
@@ -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,10 @@ namespace mbts.spark {
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
NativePriceSize last_trade,
|
||||
string direction_arrow
|
||||
) {
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -207,28 +220,47 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
double max = data.Max(v => v.Volume);
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
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($"{direction_arrow} {last_trade.size} ");
|
||||
}
|
||||
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.
+25
-6
@@ -13,11 +13,12 @@
|
||||
TextElement.FontSize="{x:Static u:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalSplit">8</sys:Double>
|
||||
<sys:Double x:Key="IndentChild">24</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetLeft">8</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetRight">24</sys:Double>
|
||||
<sys:Double x:Key="GroupBoxOffsetRight">2</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalSectionBreak">6</sys:Double>
|
||||
<sys:Double x:Key="VerticalSplit">8</sys:Double>
|
||||
@@ -132,7 +133,7 @@
|
||||
<ComboBox Grid.Column="3" ItemsSource="{u:EnumBindingSource {u:Nested sp:LadderSettings.BadgeStyle}}" SelectedItem="{Binding badge_style}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">Alternate Order Qty</TextBlock>
|
||||
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding alternate_order_size}" u:TextBoxBehavior.InputFilter="{u:RegexInputFilter Regex={x:Static Member=Integer, MemberType={u:Nested txt:TextUtils.RegexFilters}}}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="3" Text="{Binding alternate_order_size}" u:TextBoxBehavior.InputFilter="{u:RegexInputFilter Regex={x:Static Member=UnsignedInteger, MemberType={u:Nested txt:TextUtils.RegexFilters}}}"/>
|
||||
|
||||
<TabControl Grid.Row="4" Grid.ColumnSpan="5" Width="543" Height="275">
|
||||
<TabControl.ItemContainerStyle>
|
||||
@@ -141,8 +142,8 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Top"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<!-- Fonts and Colors Tab -->
|
||||
<TabItem Header="Fonts and Colors" VerticalAlignment="Stretch">
|
||||
<!-- Column Settings Tab -->
|
||||
<TabItem Header="Column Settings" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
@@ -172,7 +173,7 @@
|
||||
<ColumnDefinition SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition Width="160" SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition Width="140" SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition Width="153" SharedSizeGroup="c4"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ColumnNameTemplateSelector}"/>
|
||||
@@ -382,7 +383,8 @@
|
||||
FontStyle="Italic"
|
||||
Foreground="{Binding can_choose_alternate_implied_format, Converter={StaticResource IsEnabledForegroundConverter}}"
|
||||
/>
|
||||
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3">
|
||||
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3"
|
||||
Visibility="{Binding DataContext.is_synthetic, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}">
|
||||
<GroupBox.Header>
|
||||
<CheckBox
|
||||
x:Name="show_name_change_chk"
|
||||
@@ -478,6 +480,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="volume_headers" TextElement.Foreground="{u:BTSColor header_text}" Background="{u:BTSColor header}" Margin="8,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -586,6 +589,22 @@
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</WrapPanel>
|
||||
<StackPanel Grid.Row="4" Margin="8,0" VerticalAlignment="Top">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="Show Last on Volume Column" FlowDirection="RightToLeft" DataContext="{Binding volume}" IsChecked="{Binding show_last_trade}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<!-- Size Presets Tab-->
|
||||
File renamed without changes.
+35
-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,33 @@ 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
|
||||
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();
|
||||
+36
-16
@@ -19,6 +19,7 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/OrderDetailsPopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoParamsView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateFavoriteButtonsView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToInVisibility" VisibilityForTrue="Collapsed" VisibilityForFalse="Visible" />
|
||||
@@ -31,7 +32,6 @@
|
||||
</converters:BooleanToObjectConverter.ValueForFalse>
|
||||
</converters:BooleanToObjectConverter>
|
||||
<converters:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<converters:DoubleTextConverter x:Key="PositionTextConverter" Format="0" NaNText="-"/>
|
||||
<converters:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<converters:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
@@ -455,18 +455,38 @@
|
||||
<Button
|
||||
Margin="2"
|
||||
Command="{Binding position_click_command}"
|
||||
Content="{Binding position, Converter={StaticResource PositionTextConverter}}"
|
||||
Content="{Binding position_display_text}"
|
||||
FontWeight="Bold"
|
||||
Foreground="{Binding position, Converter={StaticResource NumericSignBrushConverter}}"
|
||||
Visibility="{Binding show_account_position, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<Button.Visibility>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<converters:MultiValueConverterPipe>
|
||||
<converters:BooleanOrConverter/>
|
||||
<converters:BooleanToVisibilityConverter/>
|
||||
</converters:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</Button.Visibility>
|
||||
<Button.ToolTip>
|
||||
<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>
|
||||
<!--<TextBlock Text="{Binding order_properties_vm.selected_account, StringFormat='{}{0} total position (click to copy to size)'}"/>-->
|
||||
<TextBlock TextAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static spark:LadderView.PositionToolTipConverter}">
|
||||
<Binding Path="order_properties_vm.selected_account"/>
|
||||
<Binding Path="show_day_position"/>
|
||||
<Binding Path="show_account_position"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button.ToolTip>
|
||||
</Button>
|
||||
<!-- Qty textbox -->
|
||||
<TextBox
|
||||
mbts:TextBoxBehavior.InputFilter="{mbts:RegexInputFilter Regex={x:Static Member=Integer, MemberType={mbts:Nested txt:TextUtils.RegexFilters}}}"
|
||||
mbts:TextBoxBehavior.InputFilter="{mbts:RegexInputFilter Regex={x:Static Member=UnsignedInteger, MemberType={mbts:Nested txt:TextUtils.RegexFilters}}}"
|
||||
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
|
||||
HorizontalContentAlignment="Right"
|
||||
Margin="2"
|
||||
@@ -489,6 +509,15 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<!-- Clear button -->
|
||||
<Button Margin="2" HorizontalAlignment="Stretch" Command="{Binding size_calculator.clear}"
|
||||
Visibility="{Binding show_clear, Converter={StaticResource BooleanToVisibilityConverter}}">Clear</Button>
|
||||
<!-- Auto Clear button - reset to defaults after order -->
|
||||
<CheckBox Margin="2" IsChecked="{Binding auto_clear}"
|
||||
Visibility="{Binding show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Auto Clear
|
||||
</CheckBox>
|
||||
<!--Algo Favorite Buttons-->
|
||||
<uag:AlgoTemplateFavoriteButtonsView
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
@@ -501,14 +530,6 @@
|
||||
</ItemsPanelTemplate>
|
||||
</uag:AlgoTemplateFavoriteButtonsView.ItemsPanel>
|
||||
</uag:AlgoTemplateFavoriteButtonsView>
|
||||
<!-- Clear button -->
|
||||
<Button Margin="2" HorizontalAlignment="Stretch" Command="{Binding size_calculator.clear}"
|
||||
Visibility="{Binding show_clear, Converter={StaticResource BooleanToVisibilityConverter}}">Clear</Button>
|
||||
<!-- Auto Clear button - reset to defaults after order -->
|
||||
<CheckBox Margin="2" IsChecked="{Binding auto_clear}"
|
||||
Visibility="{Binding show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Auto Clear
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0,17,0,0" VerticalAlignment="Top">
|
||||
<!-- Cancel Buttons -->
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource CancelBuys}"
|
||||
@@ -553,11 +574,10 @@
|
||||
<Setter Property="Foreground" Value="{Binding foreground}"/>
|
||||
<Setter Property="TextLines" Value="{Binding text_lines}" />
|
||||
<Setter Property="Width" Value="{x:Static controls:Badge.DefaultWidth}"/>
|
||||
<Setter Property="IsExchangeOrder" Value="{Binding is_exchange_order}" />
|
||||
<Setter Property="HasMultipleOrdersAtLevel" Value="{Binding has_multiple_orders_at_same_level}" />
|
||||
<Setter Property="IsHedgeOrder" Value="{Binding is_hedge_order}" />
|
||||
<Setter Property="ParentAlgoType" Value="{Binding parent_algo_type}" />
|
||||
<Setter Property="ParentAlgoTypeBrush" Value="{Binding parent_algo_type_brush}" />
|
||||
<Setter Property="AlgoType" Value="{Binding algo_type}" />
|
||||
<Setter Property="Abbreviation" Value="{Binding algo_studio_abbreviation}" />
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
+35
-4
@@ -1,21 +1,52 @@
|
||||
using mbts.utils;
|
||||
using System.Collections.Generic;
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.converters;
|
||||
using mbts.utils;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderView : UserControl {
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, string>((account, show_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", total_pos_text = "total";
|
||||
if (!account.HasValue)
|
||||
return string.Empty;
|
||||
var sb = new StringBuilderEx(account.Value.name);
|
||||
string primary_pos;
|
||||
if (show_day_pos) {
|
||||
primary_pos = day_pos_text;
|
||||
if (show_total_pos) {
|
||||
sb.Add(" '", primary_pos, " ");
|
||||
sb.Add("(", total_pos_text, ")' ");
|
||||
} else {
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
}
|
||||
} else if (show_total_pos) {
|
||||
primary_pos = total_pos_text;
|
||||
sb.Add(" ", primary_pos, " ");
|
||||
} else {
|
||||
// unexpected code path (the button for which this tooltip is destined should be invisible in this case), but no need to throw an exception.
|
||||
return string.Empty;
|
||||
}
|
||||
sb.Add("position\n(click to copy ");
|
||||
if (show_day_pos && show_total_pos)
|
||||
sb.Add(primary_pos, " position ");
|
||||
sb.Add("to size)");
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
readonly double _max_zoom, _min_zoom;
|
||||
|
||||
internal LadderView(LadderVM vm) {
|
||||
InitializeComponent();
|
||||
_column_headers_presenter.Columns = new GridViewColumnCollection();
|
||||
_ladder.visible_columns_changed += () => _column_headers_presenter.Columns.SetItems(
|
||||
_ladder.visible_columns.Select(t => ColumnFactory.create(_ladder, t)));
|
||||
_ladder.visible_column_types.Select(t => ColumnFactory.create(_ladder, t)));
|
||||
this.vm = vm;
|
||||
DataContext = vm;
|
||||
DataContextChanged += datacontext_changed;
|
||||
@@ -0,0 +1,14 @@
|
||||
using mbts.ui;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
static class LadderViewHelper {
|
||||
public static readonly DragDeltaEventHandler ColumnHeaderThumb_DragDeltaHandler =
|
||||
(s, e) =>
|
||||
e.Handled =
|
||||
s is Thumb thumb &&
|
||||
thumb.try_find_ancestor_of_type(out Canvas canvas) &&
|
||||
canvas.ActualWidth + e.HorizontalChange < canvas.MinWidth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.MiniLadderView"
|
||||
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:usp="clr-namespace:mbts.ui.spark"
|
||||
Background="{x:Static u:BTSColors.common_background_brush}"
|
||||
TextElement.FontSize="{x:Static u:FontSizes.Small}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style TargetType="GridViewColumnHeader">
|
||||
<Setter Property="Background" Value="{u:BTSColor header}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor header_text}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor header_border}"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,1,0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="Height" Value="23" />
|
||||
<Setter Property="MinWidth" Value="30" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
|
||||
<Grid SnapsToDevicePixels="True">
|
||||
<Border x:Name="HeaderBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MaxHeight="7"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Padding="{TemplateBinding Padding}" Grid.RowSpan="2">
|
||||
<ContentPresenter x:Name="HeaderContent"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentStringFormat="{TemplateBinding ContentStringFormat}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
Margin="0,0,0,1"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Canvas MinWidth="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MinWidth, Mode=OneTime}">
|
||||
<Thumb x:Name="PART_HeaderGripper" DragDelta="{x:Static usp:LadderViewHelper.ColumnHeaderThumb_DragDeltaHandler}">
|
||||
<Thumb.Style>
|
||||
<Style TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Canvas.Right" Value="-9"/>
|
||||
<Setter Property="Width" Value="18"/>
|
||||
<Setter Property="Height" Value="{Binding ActualHeight, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border Background="Transparent" Padding="{TemplateBinding Padding}">
|
||||
<Rectangle Fill="{TemplateBinding Background}" HorizontalAlignment="Center" Width="2"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{u:BTSColor header_text}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Thumb.Style>
|
||||
</Thumb>
|
||||
</Canvas>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<sp:LadderGridHost
|
||||
x:Name="ladder_grid_host"
|
||||
Command="{Binding send_order_command}"
|
||||
Orders="{Binding orders}"
|
||||
orders_column_width="{Binding Settings.orders_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
bid_column_width="{Binding Settings.orders_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
price_column_width="{Binding Settings.price_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
ask_column_width="{Binding Settings.ask_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
LastColumnWidth="{Binding Settings.last_column.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
VolumeColumnWidth="{Binding Settings.volume_column.general.width, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
CanUserSendOrders="{Binding CanUserSendOrders, RelativeSource={RelativeSource AncestorType=usp:MiniLadderView}}"
|
||||
IsTabStop="False"
|
||||
>
|
||||
<sp:LadderGridHost.Resources>
|
||||
<Style TargetType="c:Badge">
|
||||
<Setter Property="Background" Value="{Binding background}"/>
|
||||
<Setter Property="BorderBrush" Value="{Binding border}"/>
|
||||
<Setter Property="FontFamily" Value="{Binding font_family}"/>
|
||||
<Setter Property="FontSize" Value="{Binding font_size}"/>
|
||||
<Setter Property="Foreground" Value="{Binding foreground}"/>
|
||||
<Setter Property="TextLines" Value="{Binding text_lines}"/>
|
||||
<Setter Property="Width" Value="{x:Static c:Badge.DefaultWidth}"/>
|
||||
<Setter Property="HasMultipleOrdersAtLevel" Value="{Binding has_multiple_orders_at_same_level}"/>
|
||||
<Setter Property="IsHedgeOrder" Value="{Binding is_hedge_order}"/>
|
||||
<Setter Property="AlgoType" Value="{Binding algo_type}"/>
|
||||
<Setter Property="Abbreviation" Value="{Binding algo_studio_abbreviation}" />
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{Binding hoverbackground}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</sp:LadderGridHost.Resources>
|
||||
</sp:LadderGridHost>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class MiniLadderView : UserControl {
|
||||
public MiniLadderView() =>
|
||||
InitializeComponent();
|
||||
|
||||
#region Settings
|
||||
public static readonly DependencyProperty SettingsProperty =
|
||||
WPFHelper.create_dp<mbts.spark.LadderSettings, MiniLadderView>(nameof(Settings));
|
||||
|
||||
public mbts.spark.LadderSettings Settings {
|
||||
get => GetValue(SettingsProperty) as mbts.spark.LadderSettings;
|
||||
set => SetValue(SettingsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CanUserSendOrders
|
||||
public static readonly DependencyProperty CanUserSendOrdersProperty =
|
||||
WPFHelper.create_dp<bool, MiniLadderView>(nameof(CanUserSendOrders));
|
||||
|
||||
public bool CanUserSendOrders {
|
||||
get => (bool)GetValue(CanUserSendOrdersProperty);
|
||||
set => SetValue(CanUserSendOrdersProperty, value);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,149 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ManualFillRow : DisposableViewModelBase, IEquatable<ManualFillRow> {
|
||||
public const int _uncommitted_record_id = -1;
|
||||
static readonly PropertyInfo[] _snapshot_props =
|
||||
Array.ConvertAll(new[] {
|
||||
nameof(security),
|
||||
nameof(account),
|
||||
nameof(user),
|
||||
nameof(side),
|
||||
nameof(size),
|
||||
nameof(price)
|
||||
}, typeof(ManualFillRow).GetProperty);
|
||||
readonly Property<bool> _is_dirty;
|
||||
readonly Property<EditableItemState> _editable_item_state;
|
||||
readonly DelegateProperty<bool> _is_valid;
|
||||
SecurityImmutable? _security;
|
||||
AccountTuple? _account;
|
||||
UserTuple? _user;
|
||||
Side _side;
|
||||
uint _size;
|
||||
double _price;
|
||||
bool _is_selected;
|
||||
ManualFillData? _data;
|
||||
IDisposable _committed_snapshot;
|
||||
|
||||
public ManualFillRow(ManualFillData data) {
|
||||
_data = data;
|
||||
key = data.key;
|
||||
_security = data.value.security;
|
||||
_account = data.value.account;
|
||||
_user = data.value.user;
|
||||
_side = data.value.manual_fill.Side;
|
||||
_size = data.value.manual_fill.Quantity;
|
||||
_price = data.value.manual_fill.Price;
|
||||
if (placeholder_key.HasValue) {
|
||||
_is_dirty = backing(nameof(is_dirty), false);
|
||||
_editable_item_state = backing(nameof(editable_item_state), EditableItemState.Uncommitted);
|
||||
_committed_snapshot = ObjectSnapshot.create(this, _snapshot_props);
|
||||
} else {
|
||||
_is_dirty = backing(nameof(is_dirty), ObjectSnapshot.dirty_monitor(this, out _committed_snapshot, _snapshot_props));
|
||||
_editable_item_state = backing(nameof(editable_item_state), _is_dirty, ch => ch.newval ? EditableItemState.Modified : EditableItemState.None);
|
||||
}
|
||||
_is_valid = backing(nameof(is_valid), () => _validate(this));
|
||||
}
|
||||
|
||||
public event Action<bool> is_selected_changed;
|
||||
// key/id
|
||||
public ManualFillKey key { get; }
|
||||
public long? placeholder_key => key.unpersisted;
|
||||
public int record_id => key.persisted ?? _uncommitted_record_id;
|
||||
public string record_id_display_text => key.persisted?.ToString();
|
||||
// security/spec
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (_set(ref _security, value))
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}
|
||||
}
|
||||
public ContractSpecificationImmutable? spec => security.spec_or_default();
|
||||
// account/user
|
||||
public AccountTuple? account { get => _account; set => _set(ref _account, value); }
|
||||
public UserTuple? user { get => _user; set => _set(ref _user, value); }
|
||||
// side/size/price
|
||||
public Side side { get => _side; set => _set(ref _side, value); }
|
||||
public uint size { get => _size; set => _set(ref _size, value); }
|
||||
public double price { get => _price; set => _set(ref _price, value); }
|
||||
// is selected
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (_set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
// dirty/valid/editable-state
|
||||
public bool is_dirty => _is_dirty.Value;
|
||||
public bool is_valid => _is_valid.Value;
|
||||
public EditableItemState editable_item_state => _editable_item_state.Value;
|
||||
|
||||
public ManualFillData data => _data ??
|
||||
(_data = new ManualFillData(key, new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user))).Value;
|
||||
|
||||
public void revert() =>
|
||||
_committed_snapshot.Dispose();
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() =>
|
||||
_is_dirty.observe_current(true);
|
||||
|
||||
public IObservable<bool> subscribe_is_selected() =>
|
||||
Observable.Create<bool>(o => {
|
||||
o.OnNext(is_selected);
|
||||
is_selected_changed += o.OnNext;
|
||||
return Disposable.Create(() => is_selected_changed -= o.OnNext);
|
||||
});
|
||||
|
||||
public ManualFill to_man_fill() =>
|
||||
new ManualFill {
|
||||
RecordId = record_id,
|
||||
AccountId = account?.id ?? 0,
|
||||
AccountName = account?.name ?? string.Empty,
|
||||
UserId = user?.id ?? 0,
|
||||
Price = price,
|
||||
Side = side,
|
||||
Quantity = size,
|
||||
SecurityKey = security?.Id ?? 0
|
||||
};
|
||||
|
||||
public ManualFillTuple to_tuple() =>
|
||||
new ManualFillTuple(new ManualFillImmutable(to_man_fill()), security, account, user);
|
||||
|
||||
public bool Equals(ManualFillRow other) =>
|
||||
data == other?.data;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as ManualFillRow);
|
||||
public override int GetHashCode() => record_id;
|
||||
|
||||
static bool _validate(ManualFillRow row) =>
|
||||
row.security.HasValue &&
|
||||
row.account.HasValue &&
|
||||
row.user.HasValue &&
|
||||
row.side != Side.Undefined &&
|
||||
!double.IsNaN(row._price);
|
||||
|
||||
bool _set<T>(ref T backing, T value, [CallerMemberName] string prop_name = "") =>
|
||||
_set(ref backing, value, EqualityComparer<T>.Default, prop_name);
|
||||
|
||||
bool _set<T>(ref T backing, T value, IEqualityComparer<T> comparer, [CallerMemberName] string prop_name = "") {
|
||||
if (Set(ref backing, value, comparer, prop_name)) {
|
||||
_is_valid.refresh();
|
||||
_data = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillRowEditor"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">8</sys:Double>
|
||||
<Style x:Key="HeaderText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{x:Static u:BTSColors.header_text_brush}"/>
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="11" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.header_brush}"/>
|
||||
<TextBlock Grid.Column="0" Text="Security" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="2" Text="Account" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="4" Text="User" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="6" Text="Side" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="8" Text="Qty" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="10" Text="Price" Style="{StaticResource HeaderText}"/>
|
||||
|
||||
<c:ContractPickerPopup
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SecuritiesKey}}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.AccountsKey}}" SelectedItem="{Binding account}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="4" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.UsersKey}}" SelectedItem="{Binding user}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="6" ItemsSource="{DynamicResource ResourceKey={x:Static usp:ManualFillRowEditor.SidesKey}}" SelectedItem="{Binding side}" Width="48"/>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="8" MinWidth="40" Value="{Binding size}"/>
|
||||
<usp:PriceEntryControl Grid.Row="2" Grid.Column="10" MinWidth="55" Value="{Binding price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Content="OK" Command="{x:Static usp:ManualFillRowEditor.CommitCommand}" CommandParameter="{Binding}" IsDefault="True"/>
|
||||
<Button Content="Cancel" Command="{x:Static usp:ManualFillRowEditor.CancelCommand}" IsCancel="True"/>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillRowEditor.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillRowEditor : UserControl {
|
||||
public const string
|
||||
SecuritiesKey = nameof(SecuritiesKey),
|
||||
AccountsKey = nameof(AccountsKey),
|
||||
UsersKey = nameof(UsersKey),
|
||||
SidesKey = nameof(SidesKey);
|
||||
|
||||
public static readonly RoutedCommand
|
||||
CommitCommand = new RoutedCommand(),
|
||||
CancelCommand = new RoutedCommand();
|
||||
|
||||
public ManualFillRowEditor() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.ManualFillsImportConflictsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Fills Conflicts"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:ManualFillsImportConflictsView Margin="12,10,12,8"/>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Command="{Binding ok_cmd}">Continue</Button>
|
||||
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsImportConflictsDialog.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsImportConflictsDialog : Window {
|
||||
public ManualFillsImportConflictsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Component = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ManualFillsImportVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly RefCountMap<ParsedManualFill> _conflicts = new RefCountMap<ParsedManualFill>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public ManualFillsImportVM(int num_fills) {
|
||||
rows_view = CollectionViewSource.GetDefaultView(rows);
|
||||
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
|
||||
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
|
||||
this.num_fills = num_fills;
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(rows, r =>
|
||||
r.subscribe_has_error().Subscribe(has_error => {
|
||||
if (has_error) {
|
||||
if (r.affected_fills.Count(_conflicts.increment_adds_key) == 0)
|
||||
return;
|
||||
} else if (r.affected_fills.Count(_conflicts.decrement_removes_key) == 0)
|
||||
return;
|
||||
RaisePropertyChanged(nameof(num_successfully_parsed_fills));
|
||||
}));
|
||||
}
|
||||
|
||||
public event Action import_request;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public ICollectionView rows_view { get; }
|
||||
public int num_fills { get; }
|
||||
public int num_successfully_parsed_fills => num_fills - _conflicts.Count;
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
if (!ReferenceEquals(_security_map, value)) {
|
||||
_security_map = value;
|
||||
_resolve_securities();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand ok_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
|
||||
void _resolve_securities() {
|
||||
if (_security_map == null)
|
||||
return;
|
||||
var reverse_lookup = ManualFillsParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
|
||||
foreach (var r in rows) {
|
||||
if (r.component == Component.Security && r.has_error) {
|
||||
var id = reverse_lookup(r.text);
|
||||
if (id != 0 && _security_map.TryGetValue(id, out var smd))
|
||||
r.value = smd.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Row : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly DelegateProperty<bool> _has_error;
|
||||
object _value;
|
||||
string _text;
|
||||
|
||||
public Row(Component component, string text, IReadOnlyCollection<ParsedManualFill> affected_fills, object error_value, object value) {
|
||||
this.component = component;
|
||||
this.text = text;
|
||||
this.affected_fills = affected_fills;
|
||||
_value = value;
|
||||
_has_error = backing(nameof(has_error), () => Equals(this.value, error_value));
|
||||
}
|
||||
|
||||
public Component component { get; }
|
||||
public string text { get => _text; set => Set(ref _text, value); }
|
||||
public IReadOnlyCollection<ParsedManualFill> affected_fills { get; }
|
||||
public object value {
|
||||
get => _value;
|
||||
set {
|
||||
if (Set(ref _value, value))
|
||||
_has_error.refresh();
|
||||
}
|
||||
}
|
||||
public bool has_error => _has_error.Value;
|
||||
string ITemplateSelectorKey.key => component.ToString();
|
||||
|
||||
public IObservable<bool> subscribe_has_error() => _has_error.observe_current(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillsImportConflictsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
MinWidth="400"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<!--Value Cell Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<!--Value Cell Editing Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Security, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding value}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Account, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static User, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Side, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Size, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Price, MemberType={u:Nested usp:ManualFillsImportConflictsView.ManualFillComponentNames}}">
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<!--Content-->
|
||||
<Grid x:Name="content_root">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image VerticalAlignment="Center" Margin="0,0,8,0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
|
||||
<Run Text="{Binding num_successfully_parsed_fills, Mode=OneWay}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding num_fills, Mode=OneWay}"/>
|
||||
<Run Text=" fills have been successfully converted."/>
|
||||
<Run Text="Resolve conflicts below by selecting a value to assign each affected fill,"/>
|
||||
<Run Text="or modify individual fills in the main window."/>
|
||||
<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Run Text="Click 'Continue' to proceed to the main window."/>
|
||||
</TextBlock>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
Margin="0,8,0,0"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Component" Binding="{Binding component}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Affected Fills" Binding="{Binding affected_fills.Count}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsImportConflictsView.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsImportConflictsView : UserControl {
|
||||
public ManualFillsImportConflictsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public enum ManualFillComponent {
|
||||
Security,
|
||||
Account,
|
||||
User,
|
||||
Side,
|
||||
Size,
|
||||
Price
|
||||
}
|
||||
|
||||
public static class ManualFillComponentNames {
|
||||
public static readonly string
|
||||
Security = ManualFillComponent.Security.ToString(),
|
||||
Account = ManualFillComponent.Account.ToString(),
|
||||
User = ManualFillComponent.User.ToString(),
|
||||
Side = ManualFillComponent.Side.ToString(),
|
||||
Size = ManualFillComponent.Size.ToString(),
|
||||
Price = ManualFillComponent.Price.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
struct ManualFillKey : IEquatable<ManualFillKey>, IComparable<ManualFillKey> {
|
||||
public readonly int? persisted;
|
||||
public readonly long? unpersisted;
|
||||
|
||||
public ManualFillKey(int persisted) {
|
||||
this.persisted = persisted;
|
||||
unpersisted = null;
|
||||
}
|
||||
|
||||
public ManualFillKey(long unpersisted) {
|
||||
this.unpersisted = unpersisted;
|
||||
persisted = null;
|
||||
}
|
||||
|
||||
public int CompareTo(ManualFillKey other) {
|
||||
if (persisted.HasValue) {
|
||||
if (other.persisted.HasValue) {
|
||||
return persisted.Value.CompareTo(other.persisted.Value);
|
||||
} else if (other.unpersisted.HasValue) {
|
||||
return -1;
|
||||
} else return 1;
|
||||
} else if (unpersisted.HasValue) {
|
||||
return other.unpersisted.HasValue ? unpersisted.Value.CompareTo(other.unpersisted.Value) : 1;
|
||||
} else return !other.persisted.HasValue && !other.unpersisted.HasValue ? 0 : -1;
|
||||
}
|
||||
|
||||
public override string ToString() => persisted.HasValue ? persisted.Value.ToString() : unpersisted.HasValue ? unpersisted.Value.ToString() : "(N/A)";
|
||||
|
||||
public bool Equals(ManualFillKey other) =>
|
||||
persisted == other.persisted &&
|
||||
unpersisted == other.unpersisted;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => persisted ?? unpersisted?.GetHashCode() ?? 0;
|
||||
public static bool operator ==(ManualFillKey a, ManualFillKey b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillKey a, ManualFillKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
struct ManualFillData : IEquatable<ManualFillData> {
|
||||
public static readonly Func<ManualFillData, ManualFillKey> key_fn = mfd => mfd.key;
|
||||
|
||||
public readonly ManualFillKey key;
|
||||
public readonly ManualFillTuple value;
|
||||
|
||||
public ManualFillData(ManualFillKey key, ManualFillTuple value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool Equals(ManualFillData other) =>
|
||||
key == other.key &&
|
||||
value.Equals(other.value);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ManualFillData other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public static bool operator ==(ManualFillData a, ManualFillData b) => a.Equals(b);
|
||||
public static bool operator !=(ManualFillData a, ManualFillData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
class ManualFillsModel {
|
||||
readonly OrdersModel _model;
|
||||
|
||||
public ManualFillsModel(OrdersModel model)
|
||||
: this(model, model.on_manual_fill_tuples,
|
||||
model.factory.subscribe_accounts().Select(d => MappedDictionary.create(d, kv => new AccountTuple(kv.Key, kv.Value))).Table(u => u.id),
|
||||
model.factory.subscribe_users().Select(d => MappedDictionary.create(d, kv => new UserTuple(kv.Key, kv.Value))).Table(a => a.id)) { }
|
||||
|
||||
public ManualFillsModel(OrdersModel model, IObservable<TableUpdate<ManualFillTuple, int>> manual_fills, IObservable<TableUpdate<AccountTuple, uint>> accounts, IObservable<TableUpdate<UserTuple, uint>> users) {
|
||||
_model = model;
|
||||
this.manual_fills = manual_fills.SelectKeyValue(
|
||||
mft => new ManualFillData(new ManualFillKey(mft.manual_fill.RecordId), mft),
|
||||
ManualFillData.key_fn);
|
||||
this.accounts = accounts;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<ManualFillData, ManualFillKey>> manual_fills { get; }
|
||||
public IObservable<TableUpdate<AccountTuple, uint>> accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> users { get; }
|
||||
|
||||
public async Task delete_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.delete_manual_fills(fills);
|
||||
|
||||
public async Task<bool> create_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.create_manual_fills(fills);
|
||||
|
||||
public async Task<bool> update_fills(IEnumerable<ManualFill> fills) =>
|
||||
await _model.update_manual_fills(fills);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
class ParsedManualFill {
|
||||
public readonly Guid unpersisted_id = Guid.NewGuid();
|
||||
public readonly ManualFill result = new ManualFill();
|
||||
|
||||
public (string text, bool success) security { get; set; }
|
||||
public (string text, bool success) account { get; set; }
|
||||
public (string text, bool success) user { get; set; }
|
||||
public (string text, bool success) side { get; set; }
|
||||
public (string text, bool success) quantity { get; set; }
|
||||
public (string text, bool success) price { get; set; }
|
||||
|
||||
public bool Equals(ParsedManualFill other) => other != null && unpersisted_id == other.unpersisted_id;
|
||||
public override bool Equals(object obj) => Equals(obj as ParsedManualFill);
|
||||
public override int GetHashCode() => unpersisted_id.GetHashCode();
|
||||
}
|
||||
|
||||
static class ManualFillsParser {
|
||||
public const string file_extension = "csv";
|
||||
|
||||
public static Action<ManualFill, object> assign_component(ManualFillComponent component) {
|
||||
switch (component) {
|
||||
case ManualFillComponent.Security: return (mf, val) => mf.SecurityKey = ConvertUtils.try_convert<SecurityImmutable?>(val)?.Id ?? 0;
|
||||
case ManualFillComponent.Account: return (mf, val) => mf.AccountId = ConvertUtils.try_convert<AccountTuple?>(val)?.id ?? 0;
|
||||
case ManualFillComponent.User: return (mf, val) => mf.UserId = ConvertUtils.try_convert<UserTuple?>(val)?.id ?? 0;
|
||||
case ManualFillComponent.Side: return (mf, val) => mf.Side = ConvertUtils.try_convert<Side>(val, Side.Undefined);
|
||||
case ManualFillComponent.Size: return (mf, val) => mf.Quantity = ConvertUtils.try_convert<uint>(val);
|
||||
case ManualFillComponent.Price: return (mf, val) => mf.Price = ConvertUtils.try_convert(val, double.NaN);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: the returned dictionary will have entries for each <see cref="ManualFillComponent"/>, regardless of whether or not that component has any conflicts.
|
||||
public static Dictionary<ManualFillComponent, Dictionary<string, List<ParsedManualFill>>> get_conflicts(IEnumerable<ParsedManualFill> parsed_fills) {
|
||||
var missing_components = EnumHelper.get_values<ManualFillComponent>().ToDictionary(c => c, c => new Dictionary<string, List<ParsedManualFill>>());
|
||||
var mk_list = new Func<string, List<ParsedManualFill>>(_ => new List<ParsedManualFill>());
|
||||
foreach (var pmf in parsed_fills) {
|
||||
if (!pmf.security.success) {
|
||||
add_missing(ManualFillComponent.Security, pmf.security.text, pmf);
|
||||
}
|
||||
if (!pmf.account.success) {
|
||||
add_missing(ManualFillComponent.Account, pmf.account.text, pmf);
|
||||
}
|
||||
if (!pmf.user.success) {
|
||||
add_missing(ManualFillComponent.User, pmf.user.text, pmf);
|
||||
}
|
||||
if (!pmf.quantity.success) {
|
||||
add_missing(ManualFillComponent.Size, pmf.quantity.text, pmf);
|
||||
}
|
||||
if (!pmf.side.success) {
|
||||
add_missing(ManualFillComponent.Side, pmf.side.text, pmf);
|
||||
}
|
||||
if (!pmf.price.success) {
|
||||
add_missing(ManualFillComponent.Price, pmf.price.text, pmf);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(ManualFillComponent component, string text, ParsedManualFill result) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add(result);
|
||||
}
|
||||
|
||||
// This implementation prioritizes simplicity over speed.
|
||||
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, IReadOnlyDictionary<uint, string> account_map, IReadOnlyDictionary<uint, string> user_map) =>
|
||||
parse_rows(rows,
|
||||
reverse_lookup(MappedDictionary.create(security_map, kv => kv.Value.ToString())),
|
||||
reverse_lookup(account_map),
|
||||
reverse_lookup(user_map));
|
||||
|
||||
public static IEnumerable<ParsedManualFill> parse_rows(IEnumerable<string> rows, Func<string, uint> security_id_lookup, Func<string, uint> account_id_lookup, Func<string, uint> user_id_lookup) {
|
||||
var columns = new[] {
|
||||
(Headers.Security, Cells.security(security_id_lookup)),
|
||||
(Headers.Account, Cells.account(account_id_lookup)),
|
||||
(Headers.User, Cells.user(user_id_lookup)),
|
||||
(Headers.Side, Cells.side),
|
||||
(Headers.Quantity, Cells.quantity),
|
||||
(Headers.Price, Cells.price)
|
||||
}.ToDictionary(col => col.Item1, col => col.Item2.with_null_check());
|
||||
return RowParser.parse_rows(rows, columns, Cells.delimiter);
|
||||
}
|
||||
|
||||
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
|
||||
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
|
||||
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
|
||||
}
|
||||
|
||||
static class Headers {
|
||||
public const string
|
||||
Security = "Security",
|
||||
Account = "Account",
|
||||
User = "User",
|
||||
Side = "Side",
|
||||
Quantity = "Quantity",
|
||||
Price = "Price";
|
||||
}
|
||||
|
||||
static class Cells {
|
||||
public const char delimiter = ',';
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> security(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.SecurityKey = reverse_id_lookup(text);
|
||||
pmf.security = (text, pmf.result.SecurityKey != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void side(ref ParsedManualFill pmf, string cell_text) {
|
||||
switch (cell_text[0]) {
|
||||
case 'B': pmf.result.Side = Side.Buy; break;
|
||||
case 'S': pmf.result.Side = Side.Sell; break;
|
||||
default: pmf.side = (cell_text, false); break;
|
||||
}
|
||||
pmf.side = (cell_text, true);
|
||||
}
|
||||
|
||||
public static void quantity(ref ParsedManualFill pmf, string cell_text) {
|
||||
if (uint.TryParse(cell_text, out uint v)) {
|
||||
pmf.result.Quantity = v;
|
||||
pmf.quantity = (cell_text, true);
|
||||
} else {
|
||||
pmf.quantity = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void price(ref ParsedManualFill pmf, string cell_text) {
|
||||
if (double.TryParse(cell_text, out var v)) {
|
||||
pmf.result.Price = v;
|
||||
pmf.price = (cell_text, true);
|
||||
} else {
|
||||
pmf.price = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> account(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.AccountId = reverse_id_lookup(text);
|
||||
pmf.account = (text, pmf.result.AccountId != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedManualFill> user(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedManualFill pmf, string text) {
|
||||
pmf.result.UserId = reverse_id_lookup(text);
|
||||
pmf.user = (text, pmf.result.UserId != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
[ProtoContract]
|
||||
class ManualFillsSettings {
|
||||
[ProtoMember(1)]
|
||||
readonly SortingCriteria[] _sorting_criterion;
|
||||
|
||||
public static ManualFillsSettings Default =>
|
||||
new ManualFillsSettings(new[] { new SortingCriteria(nameof(mbts.ui.spark.ManualFillsView.Columns.Security), true) });
|
||||
|
||||
public ManualFillsSettings() { }
|
||||
|
||||
public ManualFillsSettings(IEnumerable<SortingCriteria> sorting_criterion) =>
|
||||
_sorting_criterion = sorting_criterion.ToArray();
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criterion ?? Array.Empty<SortingCriteria>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.position;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Disposable = mbts.Disposable;
|
||||
using ManualFillComponent = mbts.ui.spark.ManualFillsImportConflictsView.ManualFillComponent;
|
||||
|
||||
namespace bts.shell.manual_fills {
|
||||
enum EditableItemState {
|
||||
None, // Committed and not modified
|
||||
Modified, // Committed and modified
|
||||
Uncommitted // Uncommitted (modified n/a)
|
||||
}
|
||||
|
||||
class ManualFillsVM : DisposableViewModelBase {
|
||||
const int _max_unconfirmed_removes = 16;
|
||||
static readonly IComparer<ManualFillRow> _row_key_comparer = Comparer<ManualFillRow>.Create((a, b) => a.key.CompareTo(b.key));
|
||||
readonly IBus _bus;
|
||||
readonly ManualFillsModel _model;
|
||||
readonly ObservableCollection<ManualFillRow>
|
||||
_persisted_rows = new ObservableCollection<ManualFillRow>(),
|
||||
_placeholder_rows = new ObservableCollection<ManualFillRow>();
|
||||
long _placeholder_id_counter = long.MinValue;
|
||||
IDisposable _import_fills_hooks;
|
||||
|
||||
public static ManualFillsVM create(IBus bus, OrdersModel model) =>
|
||||
new ManualFillsVM(bus, new ManualFillsModel(model));
|
||||
|
||||
public ManualFillsVM(IBus bus, ManualFillsModel model) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
var merged_rows = new ObservableAggregateCollection<ManualFillRow>(_persisted_rows, _placeholder_rows);
|
||||
rows_view = CollectionViewSource.GetDefaultView(merged_rows);
|
||||
// mons
|
||||
var dirty_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_dirty());
|
||||
var selection_mon = new ReactiveThresholdMonitor<ManualFillRow>(r => r.subscribe_is_selected());
|
||||
var placeholder_mon = new ReactiveThresholdMonitor<ManualFillRow>();
|
||||
// commands
|
||||
commit_all_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(merged_rows), out var commit_all_fils_observer);
|
||||
commit_selected_fills_cmd = ObservantAsyncCommand.create(() => _commit_async(selection_mon.filtered_items), out var commit_selected_fills_observer);
|
||||
revert_all_cmd = ObservantCommand.create(() => revert(dirty_mon.filtered_items.ToList()), out var revert_all_observer);
|
||||
revert_selected_fills_cmd = ObservantCommand.create(() => revert(selection_mon.filtered_items.ToList()), out var revert_selected_fills_observer);
|
||||
remove_selected_fills_cmd = ObservantAsyncCommand.create(remove_selected_fills_async, out var remove_selected_fills_observer);
|
||||
add_fill_cmd = new DelegateCommand<ManualFillRow>(_add_fill);
|
||||
import_fills_cmd = FileDialogCommand.open_file(_import_fills, extensions: ManualFillsParser.file_extension);
|
||||
//
|
||||
var dirty_or_new_src = dirty_mon.CombineLatest(placeholder_mon, LogicalDelegates.Or);
|
||||
_dispose = Disposable.Create(
|
||||
RXCollection.connect(_persisted_rows,
|
||||
_model.manual_fills.OnDispatcher(),
|
||||
Observable.Return(Comparer<ManualFillData>.Create((a, b) => a.key.CompareTo(b.key))),
|
||||
mfd => new ManualFillRow(mfd),
|
||||
mfr => mfr.data),
|
||||
_bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => {
|
||||
security_map = m;
|
||||
RaisePropertyChanged(nameof(security_map));
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_persisted_rows, r => Disposable.Create(dirty_mon.join(r), selection_mon.join(r))),
|
||||
ObservableAutoWrapper.connect_item_hooks(_placeholder_rows, r => Disposable.Create(selection_mon.join(r), placeholder_mon.join(r, Observable.Return(true)))),
|
||||
// command param subs
|
||||
dirty_mon.Subscribe(revert_all_observer),
|
||||
dirty_or_new_src.OnDispatcher().Subscribe(commit_all_fils_observer),
|
||||
selection_mon.Subscribe(remove_selected_fills_observer),
|
||||
selection_mon.CombineLatest(dirty_mon, LogicalDelegates.And).OnDispatcher().Subscribe(revert_selected_fills_observer),
|
||||
selection_mon.CombineLatest(dirty_or_new_src, LogicalDelegates.And).OnDispatcher().Subscribe(commit_selected_fills_observer),
|
||||
// accounts/users subs
|
||||
RXCollection.connect(accounts, model.accounts.OnDispatcher(), a => new AccountTuple?(a), a => a.Value, Comparer<AccountTuple>.Default),
|
||||
RXCollection.connect(users, model.users.OnDispatcher(), u => new UserTuple?(u), u => u.Value, Comparer<UserTuple>.Default),
|
||||
Disposable.Create(() => Disposable.dispose(ref _import_fills_hooks))
|
||||
);
|
||||
// local methods
|
||||
void revert(IReadOnlyList<ManualFillRow> rows) {
|
||||
foreach (var r in rows)
|
||||
r.revert();
|
||||
}
|
||||
async Task remove_selected_fills_async() {
|
||||
if (selection_mon.filtered_items.Count < _max_unconfirmed_removes || BTSMessageBox.Confirm($"Are you sure you want to delete {selection_mon.filtered_items.Count} fills?", "Delete Fills")) {
|
||||
var persisted_fills = new List<ManualFill>();
|
||||
foreach (var r in selection_mon.filtered_items.ToList()) {
|
||||
if (r.placeholder_key.HasValue)
|
||||
_placeholder_rows.RemoveSorted(r, _row_key_comparer);
|
||||
else persisted_fills.Add(r.to_man_fill());
|
||||
}
|
||||
await _model.delete_fills(persisted_fills);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICollectionView rows_view { get; }
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public ObservableCollection<UserTuple?> users { get; } = new ObservableCollection<UserTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map { get; private set; } = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
// commands
|
||||
public ICommand add_fill_cmd { get; }
|
||||
public ICommand commit_all_fills_cmd { get; }
|
||||
public ICommand commit_selected_fills_cmd { get; }
|
||||
public ICommand import_fills_cmd { get; }
|
||||
public ICommand remove_selected_fills_cmd { get; }
|
||||
public ICommand revert_all_cmd { get; }
|
||||
public ICommand revert_selected_fills_cmd { get; }
|
||||
|
||||
public ManualFillRow row_factory() =>
|
||||
new ManualFillRow(
|
||||
new ManualFillData(
|
||||
new ManualFillKey(_placeholder_id_counter++),
|
||||
new ManualFillTuple(new ManualFillImmutable(new ManualFill {
|
||||
Side = Side.Buy
|
||||
}), null, accounts.FirstOrDefault(), users.FirstOrDefault())));
|
||||
|
||||
void _add_fill(ManualFillRow new_row) =>
|
||||
_placeholder_rows.InsertSorted((new_row ?? (new_row = row_factory())).placeholder_key.HasValue ?
|
||||
new_row : throw new InvalidOperationException("New row is already persisted."),
|
||||
_row_key_comparer);
|
||||
|
||||
async Task _commit_async(IEnumerable<ManualFillRow> rows) {
|
||||
var updates = new List<ManualFill>();
|
||||
var adds = new List<(ManualFill man_fill, long pl_key)>();
|
||||
foreach (var r in rows) {
|
||||
if (r.is_dirty) {
|
||||
updates.Add(r.to_man_fill());
|
||||
} else if (r.placeholder_key.HasValue) {
|
||||
adds.Add((r.to_man_fill(), r.placeholder_key.Value));
|
||||
}
|
||||
}
|
||||
if (updates.Count > 0)
|
||||
await _model.update_fills(updates);
|
||||
if (adds.Count > 0) {
|
||||
await _model.create_fills(adds.WrapSelector(r => r.man_fill, null));
|
||||
_placeholder_rows
|
||||
.WrapSelector(r => r.placeholder_key.Value, null)
|
||||
.RemoveSorted(adds.Select(r => r.pl_key));
|
||||
}
|
||||
}
|
||||
|
||||
void _import_fills(string file_name) {
|
||||
try {
|
||||
var lines = File.ReadAllLines(file_name);
|
||||
var parsed_fills = ManualFillsParser.parse_rows(
|
||||
lines,
|
||||
MappedDictionary.create(security_map, kv => kv.Value.security),
|
||||
accounts.ToDictionary(a => a.Value.id, a => a.Value.name),
|
||||
users.ToDictionary(u => u.Value.id, u => u.Value.name)
|
||||
).ToList();
|
||||
var conflicts = ManualFillsParser.get_conflicts(parsed_fills);
|
||||
if (conflicts.Values.Any(c => c.Count > 0)) {
|
||||
// init vm
|
||||
var vm = new ManualFillsImportVM(parsed_fills.Count) {
|
||||
security_map = security_map
|
||||
};
|
||||
// add rows
|
||||
foreach (var kv in conflicts) {
|
||||
var assign_del = ManualFillsParser.assign_component(kv.Key);
|
||||
foreach (var text_groups in kv.Value) {
|
||||
var eval = get_error_value(kv.Key);
|
||||
var row = new ManualFillsImportVM.Row(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
|
||||
vm.rows.Add(row);
|
||||
vm.import_request += () => {
|
||||
foreach (var pmf in text_groups.Value)
|
||||
assign_del(pmf.result, row.value);
|
||||
};
|
||||
}
|
||||
}
|
||||
var win = new mbts.ui.spark.ManualFillsImportConflictsDialog {
|
||||
DataContext = vm,
|
||||
Title = ManualFillsWindowFactory.window_name,
|
||||
Topmost = true
|
||||
};
|
||||
vm.RequestClose += (s, e) => Disposable.dispose(ref _import_fills_hooks);
|
||||
vm.import_request += () => {
|
||||
add_imported_fills();
|
||||
Disposable.dispose(ref _import_fills_hooks);
|
||||
};
|
||||
Disposable.replace(ref _import_fills_hooks, Disposable.Create(
|
||||
ObservableAutoWrapper.connect(accounts, vm.accounts),
|
||||
ObservableAutoWrapper.connect(users, vm.users),
|
||||
vm,
|
||||
Disposable.Create(close)
|
||||
));
|
||||
win.move_to_mouse(-200, -75);
|
||||
win.Show();
|
||||
void close() => WindowUtilities.force_close(win);
|
||||
object get_error_value(ManualFillComponent component) {
|
||||
switch (component) {
|
||||
case ManualFillComponent.Price: return double.NaN;
|
||||
case ManualFillComponent.Size: return 0;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
add_imported_fills();
|
||||
}
|
||||
void add_imported_fills() =>
|
||||
_placeholder_rows.InsertSorted(parsed_fills.Select(pmf => new ManualFillRow(new ManualFillData(new ManualFillKey(_placeholder_id_counter++), new ManualFillTuple(new ManualFillImmutable(pmf.result),
|
||||
pmf.result.SecurityKey != 0 && security_map.TryGetValue(pmf.result.SecurityKey, out var smd) ? smd.security : (SecurityImmutable?)null,
|
||||
accounts.FirstOrDefault(a => a?.id == pmf.result.AccountId),
|
||||
users.FirstOrDefault(a => a?.id == pmf.result.UserId))))), _row_key_comparer);
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Error($"Error importing fills: {ex}.", ManualFillsWindowFactory.window_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.ManualFillsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<!--Row Header Style-->
|
||||
<DataGrid.RowHeaderStyle>
|
||||
<Style TargetType="DataGridRowHeader">
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Border x:Name="Root">
|
||||
<u:StateManager.VisualState>
|
||||
<MultiBinding Converter="{x:Static usp:ManualFillsView.RowHeaderVisualStateConverter}">
|
||||
<Binding Path="editable_item_state"/>
|
||||
<Binding Path="is_valid"/>
|
||||
</MultiBinding>
|
||||
</u:StateManager.VisualState>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState Name="None"/>
|
||||
<VisualState Name="Modified">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="pencil" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="0.7"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has modified properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Uncommitted">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#B3006000"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Error">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="exclamation" Storyboard.TargetProperty="(UIElement.Opacity)" BeginTime="0" Duration="0" To="1"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Fill has invalid properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="ErrorUncommitted">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="new_item" Storyboard.TargetProperty="(usp:ManualFillsView.ImageBrush).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="#CCCD4848"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Root" Storyboard.TargetProperty="(FrameworkElement.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="New Fill has invalid properties."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Image x:Name="new_item" usp:ManualFillsView.ImageBrush="Transparent" Height="14">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Brush="{Binding Path='(usp:ManualFillsView.ImageBrush)', ElementName=new_item}" Geometry="{Binding Source={StaticResource {x:Static u:Icons.DocumentAddGeometryKey}}}"/>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Image x:Name="exclamation" Height="14" Opacity="0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Image x:Name="pencil" Height="14" Source="{x:Static u:Icons.Pencil}" Opacity="0">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform Angle="45" CenterX="0" CenterY="7"/>
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.RowHeaderStyle>
|
||||
<!--Cell Style-->
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<!--Row Style-->
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<!--Record ID-->
|
||||
<DataGridTextColumn c:DataGridEx.CellKey="{x:Static RecordID, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="ID" Binding="{Binding record_id_display_text}" SortMemberPath="record_id" IsReadOnly="True"/>
|
||||
<!---Security-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Security, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Security" SortMemberPath="security">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding security}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Account-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Account, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Account" SortMemberPath="account">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding account}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<ComboBox
|
||||
x:Name="mutable_view"
|
||||
ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
SelectedItem="{Binding account, UpdateSourceTrigger=PropertyChanged}"
|
||||
Visibility="Visible"
|
||||
/>
|
||||
<TextBlock
|
||||
x:Name="readonly_view"
|
||||
Text="{Binding account}"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding placeholder_key}" Value="{x:Null}">
|
||||
<Setter TargetName="mutable_view" Property="Visibility" Value="Collapsed"/>
|
||||
<Setter TargetName="readonly_view" Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--User-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static User, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="User" SortMemberPath="user">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding user}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.users, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding user, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Side-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Side, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Side" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding side}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.sides, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding side, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Size-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Size, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Qty" SortMemberPath="side">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding size}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownUInt MinWidth="40" Value="{Binding size, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<!--Price-->
|
||||
<DataGridTemplateColumn c:DataGridEx.CellKey="{x:Static Price, MemberType={u:Nested usp:ManualFillsView.Columns}}" Header="Price" SortMemberPath="price">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="price"/>
|
||||
<Binding Path="spec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate>
|
||||
<DataTemplate>
|
||||
<usp:PriceEntryControl ContractSpec="{Binding spec}" Value="{Binding price, UpdateSourceTrigger=PropertyChanged}" AlwaysReformatOnSpecChange="True"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellEditingTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,97 @@
|
||||
using bts.shell.manual_fills;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.converters;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ManualFillsView.xaml
|
||||
/// </summary>
|
||||
partial class ManualFillsView : UserControl {
|
||||
public static readonly IMultiValueConverter
|
||||
RowHeaderVisualStateConverter = ValueConverter.create<EditableItemState, bool, string>(
|
||||
(edit_state, is_valid) => is_valid ? edit_state.ToString() : edit_state == EditableItemState.Uncommitted ? "ErrorUncommitted" : "Error");
|
||||
|
||||
public enum Columns {
|
||||
RecordID,
|
||||
Security,
|
||||
Account,
|
||||
User,
|
||||
Side,
|
||||
Size,
|
||||
Price
|
||||
}
|
||||
|
||||
IDisposable _transaction;
|
||||
|
||||
public ManualFillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public static readonly DependencyProperty ImageBrushProperty =
|
||||
DependencyProperty.RegisterAttached("ImageBrush", typeof(Brush), typeof(ManualFillsView));
|
||||
|
||||
public static Brush GetImageBrush(DependencyObject d) => d.GetValue(ImageBrushProperty) as Brush;
|
||||
public static void SetImageBrush(DependencyObject d, Brush value) => d.SetValue(ImageBrushProperty, value);
|
||||
|
||||
public void begin_add_fill() {
|
||||
if (!(DataContext is ManualFillsVM vm))
|
||||
return;
|
||||
Disposable.dispose(ref _transaction);
|
||||
var row = vm.row_factory();
|
||||
var row_editor = new ManualFillRowEditor { DataContext = row };
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CommitCommand, commit_executed));
|
||||
row_editor.CommandBindings.Add(new CommandBinding(ManualFillRowEditor.CancelCommand, cancel_executed));
|
||||
// dynamic resources
|
||||
row_editor.Resources[ManualFillRowEditor.AccountsKey] = vm.accounts;
|
||||
row_editor.Resources[ManualFillRowEditor.UsersKey] = vm.users;
|
||||
row_editor.Resources[ManualFillRowEditor.SidesKey] = vm.sides;
|
||||
FocusManager.SetIsFocusScope(row_editor, true);
|
||||
var sec_hook = vm.as_demuxer().subscribe(nameof(ManualFillsVM.security_map)).OnDispatcher().Subscribe(v => row_editor.Resources[ManualFillRowEditor.SecuritiesKey] = v);
|
||||
// popup adorner
|
||||
var popup_adorner = new PopupAdorner(
|
||||
data_grid, data_grid,
|
||||
new Border {
|
||||
Child = row_editor,
|
||||
Background = BTSColors.common_background_brush,
|
||||
BorderBrush = Brushes.Black,
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(0, 6, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
},
|
||||
HorizontalPosition.Middle,
|
||||
VerticalPosition.Middle,
|
||||
0,
|
||||
22) {
|
||||
OpenRelativeToMouse = true,
|
||||
EnableDragMove = true,
|
||||
ClipToBounds = true
|
||||
};
|
||||
popup_adorner.attach();
|
||||
popup_adorner.IsOpen = true;
|
||||
row_editor.Focus();
|
||||
_transaction = new CompositeDisposable(sec_hook) { destroy_popup };
|
||||
// local methods
|
||||
void commit_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
vm.add_fill_cmd?.TryExecuteIfCan(row);
|
||||
Disposable.dispose(ref _transaction);
|
||||
}
|
||||
void cancel_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
row.revert();
|
||||
Disposable.dispose(ref _transaction);
|
||||
}
|
||||
void destroy_popup() {
|
||||
popup_adorner.IsOpen = false;
|
||||
popup_adorner.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using bts.api;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell {
|
||||
class ManualFillsWindowFactory : ShellWindowFactory<ManualFillsSettings> {
|
||||
public const string
|
||||
window_type = "ManualFills",
|
||||
window_name = "Manual Fills";
|
||||
const double _window_min_width = 466;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ManualFillsConfig _manual_fills_conf = ManualFillsConfig.read();
|
||||
|
||||
public ManualFillsWindowFactory(IShellModule owner, IBus bus, OrdersModel orders_model) : base(owner, window_name) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowManualFillsTag).Subscribe(open_window);
|
||||
//
|
||||
async void open_window(ManualFillsSettings state) =>
|
||||
_bus.post(SparkTags.ShowWindow, await load(state));
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(ManualFillsSettings settings) {
|
||||
return Task.FromResult<IShellWindow>(SparkWindow<ManualFillsSettings>.create(
|
||||
window_type, window_name, _bus, this, Icons.DataEdit, _manual_fills_conf.thread_id, thread_init, _window_min_width));
|
||||
//
|
||||
(UIElement, IEnumerable<SparkCommand>, Func<ManualFillsSettings>, IDisposable) thread_init() {
|
||||
var vm = ManualFillsVM.create(_bus, _orders_model);
|
||||
var view = new ManualFillsView { DataContext = vm };
|
||||
view.data_grid.Sorting += on_sorting;
|
||||
IReadOnlyList<SortingCriteria> last_sorting_criterion = Array.Empty<SortingCriteria>();
|
||||
using (apply_sort()) {
|
||||
var col_sort_tuples =
|
||||
from sc in settings.sorting_criterion
|
||||
join col in view.data_grid.Columns on sc.col_name equals _col_id(col)
|
||||
select (col, sc);
|
||||
foreach (var (col, sc) in col_sort_tuples) {
|
||||
col.SortDirection = sc.direction();
|
||||
vm.rows_view.SortDescriptions.Add(new SortDescription(col.SortMemberPath, sc.direction()));
|
||||
}
|
||||
}
|
||||
var cmds = new[] {
|
||||
new SparkCommandCommand("Add New", Icons.AddBoxes, _manual_fills_conf.modal_transactions ? new DelegateCommand(() => view.begin_add_fill()) : vm.add_fill_cmd) { group = CommandGroups.Add },
|
||||
new SparkCommandCommand("Import Fills", Icons.Import, vm.import_fills_cmd) { group = CommandGroups.Add },
|
||||
new SparkCommandCommand("Revert Selected", Icons.UndoCircular,vm.revert_selected_fills_cmd) { group = CommandGroups.Revert, show_in_toolbar = false },
|
||||
new SparkCommandCommand("Revert All", Icons.UndoCircular, vm.revert_all_cmd) { group = CommandGroups.Revert },
|
||||
new SparkCommandCommand("Save Selected", Icons.Disk, vm.commit_selected_fills_cmd) { group = CommandGroups.Save, show_in_toolbar = false },
|
||||
new SparkCommandCommand("Save All", Icons.Disk, vm.commit_all_fills_cmd) { group = CommandGroups.Save },
|
||||
new SparkCommandCommand("Delete Selected", Icons.Trash, vm.remove_selected_fills_cmd)
|
||||
};
|
||||
return (view, cmds, get_settings, Disposable.Create(
|
||||
vm.rows_view.subscribe_sorting_criterion(sm => view.data_grid.Columns.TryGetFirst(dc => dc.SortMemberPath == sm, out DataGridColumn match) ? _col_id(match) : null)
|
||||
.Subscribe(sc => last_sorting_criterion = sc),
|
||||
vm));
|
||||
//
|
||||
void on_sorting(object sender, DataGridSortingEventArgs e) {
|
||||
e.Handled = true;
|
||||
using (apply_sort()) {
|
||||
var new_sort_desc = new SortDescription(e.Column.SortMemberPath, e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending);
|
||||
e.Column.SortDirection = new_sort_desc.Direction;
|
||||
if (KeyboardExtensions.IsShiftDown()) {
|
||||
if (vm.rows_view.SortDescriptions.TryGetFirst(sd => sd.PropertyName == e.Column.SortMemberPath, out var match, out int index)) {
|
||||
vm.rows_view.SortDescriptions[index] = new_sort_desc;
|
||||
} else {
|
||||
vm.rows_view.SortDescriptions.Add(new_sort_desc);
|
||||
}
|
||||
} else {
|
||||
vm.rows_view.SortDescriptions.Clear();
|
||||
foreach (var col in view.data_grid.Columns.Except(new[] { e.Column }))
|
||||
col.SortDirection = null;
|
||||
vm.rows_view.SortDescriptions.Add(new_sort_desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Ensures that the primary sort description property (i.e., at index 0) is the placeholder_key.
|
||||
/// This behavior has the effect of grouping placeholder rows at the bottom, without using <see cref="ICollectionView.GroupDescriptions"/> (which is incompatible with row virtualization).
|
||||
IDisposable apply_sort() =>
|
||||
Disposable.Create(() => {
|
||||
var pk_sort_desc = new SortDescription(nameof(ManualFillRow.placeholder_key), ListSortDirection.Ascending);
|
||||
if (vm.rows_view.SortDescriptions.Count > 0) {
|
||||
if (vm.rows_view.SortDescriptions[0].Equals(pk_sort_desc))
|
||||
return;
|
||||
vm.rows_view.SortDescriptions.removeFirst(sd => sd.PropertyName == pk_sort_desc.PropertyName);
|
||||
}
|
||||
vm.rows_view.SortDescriptions.Insert(0, pk_sort_desc);
|
||||
});
|
||||
|
||||
ManualFillsSettings get_settings() =>
|
||||
new ManualFillsSettings(last_sorting_criterion);
|
||||
}
|
||||
}
|
||||
|
||||
static string _col_id(DataGridColumn col) =>
|
||||
DataGridEx.GetCellKey(col)?.ToString();
|
||||
|
||||
readonly struct ManualFillsConfig {
|
||||
public readonly bool modal_transactions;
|
||||
public readonly string thread_id;
|
||||
|
||||
ManualFillsConfig(string thread_id, bool modal_transactions) {
|
||||
this.thread_id = thread_id;
|
||||
this.modal_transactions = modal_transactions;
|
||||
}
|
||||
|
||||
public static ManualFillsConfig read() => new ManualFillsConfig(
|
||||
mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().manual_fills ? window_type : null,
|
||||
mbts.config.Configuration.get<bts.config.ManualFillsOptions>().modal_transactions);
|
||||
}
|
||||
|
||||
static class CommandGroups {
|
||||
public const string
|
||||
Add = "Add",
|
||||
Save = "Save",
|
||||
Revert = "Revert";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:uc="clr-namespace:mbts.ui"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
@@ -22,6 +22,7 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
@@ -49,16 +50,56 @@
|
||||
|
||||
<Style x:Key="{x:Static sp:MiniOrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<!-- Completed order background -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Completed, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
|
||||
<Setter Property="Background" Value="{x:Static Member=completed_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<!-- Error, RejectedBySpark, Disconnected order background -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Error, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum RejectedBySpark, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Disconnected, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<!-- Cancel order background -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Canceled, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=canceled_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
|
||||
</Style>
|
||||
<DataTemplate x:Key="{x:Static sp:MiniOrderBookView.RowTemplateKey}">
|
||||
<sp:MiniOrderBookRowView/>
|
||||
@@ -82,7 +123,8 @@
|
||||
RowTemplate="{StaticResource {x:Static sp:MiniOrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
RowHeight="20"
|
||||
RowHeight="20"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -95,7 +137,7 @@
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
@@ -149,9 +191,8 @@
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<!--<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/> -->
|
||||
</c:DIYGrid.InputBindings>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class MiniOrderBookView : UserControl {
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
|
||||
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice, Attach }
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
@@ -20,7 +21,10 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
public MiniOrderBookRowView() : base() { }
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public MiniOrderBookRowView() : base() {
|
||||
FocusManager.SetIsFocusScope(this, true);
|
||||
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 738 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user