Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a85a475db | ||
|
|
8a1ab0ba9e | ||
|
|
57ac54b6c5 | ||
|
|
4a8433246c | ||
|
|
d6351cd187 | ||
|
|
eef263f2e7 | ||
|
|
2009027586 | ||
|
|
feaad6335d | ||
|
|
b96612d37b | ||
|
|
84f9338005 | ||
|
|
2837f3edfb | ||
|
|
a82932652b | ||
|
|
c9b5ff844f | ||
|
|
b0cbc2cb71 | ||
|
|
b2a2cae9c1 | ||
|
|
93d9a924e9 | ||
|
|
9d699711c1 | ||
|
|
5951691cbd | ||
|
|
cbff70d5a1 | ||
|
|
b59d45ae28 | ||
|
|
ea6e158e63 | ||
|
|
55a32935a9 | ||
|
|
9f895df70c | ||
|
|
8551936383 | ||
|
|
cb0378d785 | ||
|
|
489ac45722 | ||
|
|
d910c65e20 | ||
|
|
cb0e4de6c5 | ||
|
|
71539424cc | ||
|
|
602929b945 | ||
|
|
07591f8e28 | ||
|
|
0cb11b7f8d | ||
|
|
1921f02d30 | ||
|
|
2199efc95d | ||
|
|
54ff55f22f | ||
|
|
ca00f256e1 | ||
|
|
733ce7dfc6 | ||
|
|
fda812d792 | ||
|
|
1bf9d0aa4a | ||
|
|
54eec61f46 | ||
|
|
b84ba1d3f5 | ||
|
|
70bf5e0c95 | ||
|
|
2bae938549 | ||
|
|
a67b32fd33 | ||
|
|
3da8fbd25d | ||
|
|
a8cd07cbd2 | ||
|
|
badb463e03 | ||
|
|
b6a942d6b2 | ||
|
|
7d8bc13282 | ||
|
|
cbad79b909 | ||
|
|
58768567d1 | ||
|
|
2475ca3662 | ||
|
|
f12b8fedc0 | ||
|
|
ed920c7c5a | ||
|
|
db20473949 | ||
|
|
5bf8162fcf | ||
|
|
447d3303d1 | ||
|
|
5693fc4444 | ||
|
|
8896066e2c | ||
|
|
4992f4e0cf | ||
|
|
3503946011 | ||
|
|
e7806d2fec | ||
|
|
090a9dfb17 | ||
|
|
d31d8648f1 | ||
|
|
27aa43f44d | ||
|
|
19e087aa69 | ||
|
|
167c8c23a0 | ||
|
|
2c15f83c85 | ||
|
|
f9dfb04fca | ||
|
|
680b19b6fb | ||
|
|
d235f4be41 | ||
|
|
ec43dbd82d | ||
|
|
f07998d6a6 | ||
|
|
b3dc2cdcd4 | ||
|
|
f790b8f59b | ||
|
|
0963aee137 | ||
|
|
fbed8e5428 | ||
|
|
524f3be25a | ||
|
|
9823b44083 | ||
|
|
30feda72bb | ||
|
|
2d0e63546d | ||
|
|
cc02fa1c84 | ||
|
|
c2f8e92b10 | ||
|
|
6139d53e49 | ||
|
|
7f3c2e75a3 | ||
|
|
0f61e24ae5 | ||
|
|
8da2ebee5c | ||
|
|
7ae0142dff | ||
|
|
a1faf685eb | ||
|
|
9e2b01a723 | ||
|
|
d03c74f270 | ||
|
|
834a28916b | ||
|
|
43ccb4e3e7 | ||
|
|
f199cb4157 | ||
|
|
5a3bfd7f81 | ||
|
|
bf6461c126 | ||
|
|
8892505d11 | ||
|
|
5d0a29432a | ||
|
|
cd01c7ae5a | ||
|
|
bf19ec0fda | ||
|
|
1a0158817e | ||
|
|
53853591fe | ||
|
|
80d4899f2c | ||
|
|
75f15c50d8 | ||
|
|
f197f213f3 | ||
|
|
a7e1b71883 | ||
|
|
f9f22e1e36 | ||
|
|
cda50f562d | ||
|
|
4010298a46 | ||
|
|
48e701ba00 | ||
|
|
cb07abbf42 | ||
|
|
3c8e2c636f | ||
|
|
6a3b4ebe75 | ||
|
|
80256fbe12 | ||
|
|
a0a5d82139 | ||
|
|
ee6b00b9f2 | ||
|
|
9a494ce24f | ||
|
|
ae78ebac9a | ||
|
|
5c11eecbd1 | ||
|
|
78a47c33c7 | ||
|
|
9dcd80e3db | ||
|
|
f7563413bd | ||
|
|
29c418db42 | ||
|
|
f177efde70 | ||
|
|
3b83d105a0 | ||
|
|
34aafd79cd | ||
|
|
c30bab899f | ||
|
|
d3de164c53 | ||
|
|
46544f36f1 | ||
|
|
c7df3ac43c | ||
|
|
1d1c0d6b85 | ||
|
|
2f127ecc18 | ||
|
|
3fc080d02c | ||
|
|
d86543261c | ||
|
|
8761f313f5 | ||
|
|
3906ef6507 | ||
|
|
78055d79c6 | ||
|
|
885a9dd939 | ||
|
|
20c449de60 | ||
|
|
52b487946a | ||
|
|
98144ea939 | ||
|
|
5e27a5fa7c | ||
|
|
6cfc15ccd8 | ||
|
|
2de08c2c48 | ||
|
|
fb6e05560b | ||
|
|
1abf84337f | ||
|
|
d691e0aeee | ||
|
|
5e0f1b59fb | ||
|
|
a234ecbf26 | ||
|
|
062cabf075 | ||
|
|
1a7fcb2027 | ||
|
|
db0ffba8ed | ||
|
|
a274a214b4 | ||
|
|
bbcfa3c75a | ||
|
|
89229d65f1 | ||
|
|
0e75cde409 | ||
|
|
42fa6dfaff | ||
|
|
400143c84d | ||
|
|
e915945880 | ||
|
|
ca6673e98f | ||
|
|
479f3dccff | ||
|
|
39e1fd4a1a | ||
|
|
4d902747df | ||
|
|
50d0078556 | ||
|
|
9601ebab20 | ||
|
|
d86b7c6c50 | ||
|
|
c272d8a8c2 | ||
|
|
b344ab3377 | ||
|
|
ae3080b031 | ||
|
|
f0464331cb | ||
|
|
0648b618bf | ||
|
|
59282b8c70 | ||
|
|
34a86009ce | ||
|
|
0ad9ae9001 | ||
|
|
a296e1e772 | ||
|
|
7f53d8b161 | ||
|
|
fbd8bbd4a8 | ||
|
|
3da52b350f | ||
|
|
243d6827d4 | ||
|
|
00221f9bd9 | ||
|
|
a6171ae405 | ||
|
|
be5d01a797 | ||
|
|
e5d9d324bf | ||
|
|
c34dee9d96 | ||
|
|
e5a98aab41 | ||
|
|
10cd35372b | ||
|
|
ad4515be39 | ||
|
|
ff4d87dd6b | ||
|
|
8ddde3f9da | ||
|
|
51093dc3a5 | ||
|
|
5d5db9ad9f | ||
|
|
fca808fe0d | ||
|
|
07d14f877b | ||
|
|
5ea6dccdd4 | ||
|
|
ac956a5c02 | ||
|
|
1fba09cf94 | ||
|
|
0baa6b875e | ||
|
|
cdacc2d4d4 | ||
|
|
1433541947 | ||
|
|
1070ca7ace | ||
|
|
56fa27dd7e | ||
|
|
d87b522bc6 | ||
|
|
166cb03782 | ||
|
|
40bad84c48 | ||
|
|
c3786d9d64 | ||
|
|
9080e3124b | ||
|
|
d9c536124a | ||
|
|
7de6b2907e | ||
|
|
75f017e9f8 | ||
|
|
7757ab3ea0 | ||
|
|
3186dd383c | ||
|
|
45f751c9c6 | ||
|
|
e4d3774980 | ||
|
|
eedb3ce05d | ||
|
|
6a7d38c0fd | ||
|
|
7f9967834d | ||
|
|
31ccbfcd19 | ||
|
|
c2abf90cc4 | ||
|
|
8dc4735248 | ||
|
|
d940d9f11d | ||
|
|
1e0c75c6c7 | ||
|
|
fbaa17bc40 | ||
|
|
38e3cd6197 | ||
|
|
3bb9961af4 | ||
|
|
da8088fd8d | ||
|
|
748a65f671 | ||
|
|
66d80136bd | ||
|
|
10386311d4 | ||
|
|
629a7f8130 | ||
|
|
81189bb2cc | ||
|
|
e5681a50f2 | ||
|
|
69fe4569e3 | ||
|
|
c7caf66f9c | ||
|
|
14504f4a94 | ||
|
|
5b601a4016 | ||
|
|
e3207b921a | ||
|
|
98db8e61a5 | ||
|
|
45aa75e8bb | ||
|
|
b2471d45b2 | ||
|
|
11e2df8c76 | ||
|
|
cba6852655 | ||
|
|
5eeaedde26 | ||
|
|
b63052dbaa | ||
|
|
29f5bc798e | ||
|
|
04603acbb9 | ||
|
|
bc6890055e | ||
|
|
ea94c09c78 | ||
|
|
e3c3bc78fc | ||
|
|
a11d390dcd | ||
|
|
7c74a961d4 | ||
|
|
5db21ad2ca | ||
|
|
f703dabdfe | ||
|
|
237d3eea4b | ||
|
|
e009316bfb | ||
|
|
5528efd2d2 | ||
|
|
818bdec130 | ||
|
|
09f91735fe | ||
|
|
d9fc18547b | ||
|
|
d257cf2c77 | ||
|
|
079f46404f | ||
|
|
edcc73e934 | ||
|
|
5905ca3e92 | ||
|
|
bc7ac8523b | ||
|
|
0c4c2b7231 | ||
|
|
420351c093 | ||
|
|
f69e6b89c5 | ||
|
|
c3e22e4b23 | ||
|
|
32b7b7f40d | ||
|
|
54e5a68730 | ||
|
|
880c95d92b | ||
|
|
e7da6dd79e | ||
|
|
8bf78aa880 | ||
|
|
1c44165181 | ||
|
|
8165ec0e3d | ||
|
|
7d1ec5b82a | ||
|
|
02e7fb1ee0 | ||
|
|
c0ea6943e1 | ||
|
|
4c914a2d48 | ||
|
|
0ee86f2dd4 | ||
|
|
00bf3c7f47 | ||
|
|
b61e18d10e | ||
|
|
90b2607f86 | ||
|
|
e452697088 | ||
|
|
1988443497 | ||
|
|
3f33627f00 | ||
|
|
0610612ae6 | ||
|
|
efcf78bb95 | ||
|
|
03678e79a5 | ||
|
|
c15a364496 | ||
|
|
d8ec713dc5 | ||
|
|
daaa7311a0 | ||
|
|
f893af1930 | ||
|
|
6ec413c516 | ||
|
|
a2a0d88a81 | ||
|
|
7a4dca9589 | ||
|
|
b21cd12cea | ||
|
|
ec0b726751 | ||
|
|
a9f2b4ad9a | ||
|
|
9cb3a21e5a | ||
|
|
3231ffb343 | ||
|
|
8d7f1738cf | ||
|
|
60d4dd87fd | ||
|
|
3a7eeda70e | ||
|
|
712146e028 | ||
|
|
9101214e66 | ||
|
|
f381d536d6 | ||
|
|
d8d5f3b93c | ||
|
|
1d4dc47a62 | ||
|
|
482c300b72 | ||
|
|
59774ccbb5 | ||
|
|
fcb6c9b8d9 | ||
|
|
69ad995ba9 | ||
|
|
0b26ec1132 | ||
|
|
3de494c80f | ||
|
|
c6b2e2f21e | ||
|
|
2eac97c1a9 | ||
|
|
b4b0e10f29 | ||
|
|
7deb25dd1c | ||
|
|
7a29ae1a83 | ||
|
|
6da893128c | ||
|
|
2cb458af2d | ||
|
|
04db38d627 | ||
|
|
a144b03361 | ||
|
|
9c89a9a47e | ||
|
|
007af71bb2 | ||
|
|
7cfe8f4214 | ||
|
|
24f8e66953 | ||
|
|
90fc6d901f | ||
|
|
38d9db4a90 | ||
|
|
a216d1cc81 | ||
|
|
f8395c11b5 | ||
|
|
2b0e46ed38 | ||
|
|
5f3b243265 | ||
|
|
c9ce2d18bf | ||
|
|
7b563ba66c | ||
|
|
6a679ff78c | ||
|
|
3e1be70923 | ||
|
|
aadd7df6c6 | ||
|
|
aba3f21020 | ||
|
|
8206132a37 | ||
|
|
f8b3fd2e14 | ||
|
|
fede19fd11 | ||
|
|
8b17ee72ff | ||
|
|
3fa8dbf112 | ||
|
|
77ae4703b3 | ||
|
|
31ed473999 | ||
|
|
673805a7f4 | ||
|
|
91422449e9 | ||
|
|
438089a1cd | ||
|
|
6a186d1d7e | ||
|
|
9d831a2569 | ||
|
|
d47cf1a54e | ||
|
|
8c9f669e72 | ||
|
|
f5bda11797 | ||
|
|
1841a75fec | ||
|
|
34cdaeec93 | ||
|
|
3230fb9e48 | ||
|
|
888b4e22c0 | ||
|
|
534ce5a8ec | ||
|
|
c2b9ac57dc | ||
|
|
2783361325 | ||
|
|
46d35131d7 | ||
|
|
3b95154c04 | ||
|
|
1a6b918853 | ||
|
|
bc22605a25 | ||
|
|
f3d28add1c | ||
|
|
a28bfa1175 | ||
|
|
4e4a48283d | ||
|
|
63d260a6f8 | ||
|
|
7a761d00ce | ||
|
|
968be6a99a | ||
|
|
9b4a63b4a8 | ||
|
|
03404eb3f1 | ||
|
|
ff1a970982 | ||
|
|
ed890b4d84 | ||
|
|
e87003cc41 | ||
|
|
bc06037843 | ||
|
|
a502c365e5 | ||
|
|
c4c7cebfc5 | ||
|
|
f18bcd2995 | ||
|
|
a6a1b6ac53 | ||
|
|
fdacd8b543 | ||
|
|
3d1de9a0c7 | ||
|
|
b2b3fa5949 | ||
|
|
86ecf69820 | ||
|
|
8c3da4e43c | ||
|
|
06b955dceb | ||
|
|
e75d15146c | ||
|
|
cb07384c9f | ||
|
|
fc9e502539 | ||
|
|
fcbf3388c0 | ||
|
|
899bea940f | ||
|
|
14b7509607 | ||
|
|
8f537a8fda | ||
|
|
01b9ff6b47 | ||
|
|
2609663d3e | ||
|
|
25668e8df0 | ||
|
|
fd89241611 | ||
|
|
a69118c5d3 | ||
|
|
5accd0e2b4 | ||
|
|
df5ad61cd2 | ||
|
|
4a7674eb29 | ||
|
|
5a458287f4 | ||
|
|
5921f9f6b5 | ||
|
|
4c73a63ae8 | ||
|
|
42b1c921a8 | ||
|
|
fab3334b1e | ||
|
|
7239dd3ade | ||
|
|
dd4c8eb649 | ||
|
|
76a29bdc8a | ||
|
|
06d708f3ea | ||
|
|
e2f70cbc19 | ||
|
|
9d54e47a03 | ||
|
|
5345ba8ff8 | ||
|
|
faccdf581e | ||
|
|
1a64714a54 | ||
|
|
3ba6792a85 | ||
|
|
c32a7f1d62 | ||
|
|
10e75f30e6 | ||
|
|
20eac19c4f | ||
|
|
8c0765a2c7 | ||
|
|
7a1f88e813 | ||
|
|
3420f14c8a | ||
|
|
9bf05973bd | ||
|
|
86277dfbfe | ||
|
|
4839e464c6 | ||
|
|
dc2ad071a9 | ||
|
|
65e49992f9 | ||
|
|
afbb9ae92d | ||
|
|
ff088142d9 | ||
|
|
93b7cf346b | ||
|
|
f0edeadd50 | ||
|
|
cea4d05bc4 | ||
|
|
8ee9b84488 | ||
|
|
e3648f5373 | ||
|
|
facd3a9d81 | ||
|
|
84969cb481 | ||
|
|
6d521e77db | ||
|
|
82c49f8f49 | ||
|
|
e074309da1 | ||
|
|
2d622a5f39 | ||
|
|
d51d20e84c | ||
|
|
2ccdef9dc5 | ||
|
|
42e62a30bf | ||
|
|
0b7208784b | ||
|
|
c233c607fa | ||
|
|
462cb930f7 | ||
|
|
059ccbdab6 | ||
|
|
2c0f920422 | ||
|
|
5ee26065ca | ||
|
|
550a4e8d50 | ||
|
|
99b8221efc | ||
|
|
19e74c30f8 | ||
|
|
26693a4c98 | ||
|
|
d8bdb69a7c | ||
|
|
6d357a74e1 | ||
|
|
73f4e4814c | ||
|
|
312bc4be15 | ||
|
|
a0c3ba449a | ||
|
|
d5a2a7f3f0 | ||
|
|
eda8c94def | ||
|
|
9a8e80e149 | ||
|
|
6a7e3d739d | ||
|
|
e6b8274813 | ||
|
|
9783520c62 | ||
|
|
7e7f0bc37f | ||
|
|
75eaf8c3d4 | ||
|
|
b9e2ecdffa | ||
|
|
f1c021a474 | ||
|
|
6fda54ffa1 | ||
|
|
bdb0adc16c | ||
|
|
4b19b0ea14 | ||
|
|
fab1b4dd0f | ||
|
|
f3ed3075cf | ||
|
|
742af36b93 | ||
|
|
c573b5fbb2 | ||
|
|
2d6d4af594 | ||
|
|
1e8fcd794f | ||
|
|
2dcfad979c | ||
|
|
726329c4bf | ||
|
|
2d01f3f0f4 | ||
|
|
2a595c0fd7 | ||
|
|
ba7e35649e | ||
|
|
5f9866c9e8 | ||
|
|
cda4a870b7 | ||
|
|
b3a8564365 | ||
|
|
1e0114a4cf | ||
|
|
3a6e95ef05 | ||
|
|
78f1cf88e0 | ||
|
|
4877436877 | ||
|
|
1b43124756 | ||
|
|
6875263743 | ||
|
|
eac828f323 | ||
|
|
1635d6f649 | ||
|
|
a1feb3fddf | ||
|
|
2da535e9ff | ||
|
|
024328bf8b | ||
|
|
a98be44a40 | ||
|
|
6210ac2ee6 | ||
|
|
80d7e34bf1 | ||
|
|
cfebab0488 | ||
|
|
eacd766359 | ||
|
|
8fb8f8ff58 | ||
|
|
f4b22816a2 | ||
|
|
8ab69c919b | ||
|
|
99531bcfa0 | ||
|
|
6bd516d4f2 | ||
|
|
1029a464d7 | ||
|
|
fd8b8e03ea | ||
|
|
1a1d771e1b | ||
|
|
050fcf7c6e | ||
|
|
41cf66a943 | ||
|
|
1c57c9a012 | ||
|
|
8ccab66120 | ||
|
|
e2b2f7ec9c | ||
|
|
b3c897bf0f | ||
|
|
87c9f54c8b | ||
|
|
df56b76ab6 | ||
|
|
28ff13db11 | ||
|
|
2957075769 | ||
|
|
a6477824a8 | ||
|
|
17431f5ac0 | ||
|
|
8708f02fe0 | ||
|
|
752fb2bc4a | ||
|
|
6efb78ecf9 | ||
|
|
75c1970aef | ||
|
|
c2583518d7 | ||
|
|
aadeae3201 | ||
|
|
b72e8aed02 | ||
|
|
8792871387 | ||
|
|
68a593af7b | ||
|
|
0820cc3e9a | ||
|
|
b1ba9a1315 | ||
|
|
17c2321a82 | ||
|
|
0affc8a942 | ||
|
|
ce36ea4109 | ||
|
|
39ce4323a1 | ||
|
|
7c29a3d1d8 | ||
|
|
d9fcd290d9 | ||
|
|
90bc20575a | ||
|
|
cb268f505a | ||
|
|
dfd93b836b | ||
|
|
05939b02c5 | ||
|
|
28e9c66675 | ||
|
|
aa91638fe4 | ||
|
|
c0520d6a31 | ||
|
|
065fc54f73 | ||
|
|
1ba6e3280c | ||
|
|
3a6f50dfea | ||
|
|
d711f99548 | ||
|
|
07ace80871 | ||
|
|
93c6277bfc | ||
|
|
7ac9d05ae2 | ||
|
|
95fb13f58b | ||
|
|
4eb2931cb8 | ||
|
|
5326015a48 | ||
|
|
fcbe319470 | ||
|
|
5ad21c88b2 | ||
|
|
5a4b31ccc0 | ||
|
|
fb568aab39 | ||
|
|
64416de10d | ||
|
|
46953cb639 | ||
|
|
7e68a235ee | ||
|
|
3a9c1d557b | ||
|
|
85f2285ac0 | ||
|
|
70e0979836 | ||
|
|
9d1b03d1e2 | ||
|
|
9a677cf980 | ||
|
|
08bdd9c8c7 | ||
|
|
1f80c3366e | ||
|
|
886b606093 | ||
|
|
b70510b614 | ||
|
|
6db9a227b1 | ||
|
|
a819eb921d | ||
|
|
6fb5b83249 | ||
|
|
95830fafdf | ||
|
|
af4317a7f9 | ||
|
|
5edd7d5467 | ||
|
|
60d2cb5bc1 | ||
|
|
f26e3d27c4 | ||
|
|
d68b03bb91 | ||
|
|
e7062ce01e | ||
|
|
31dab6ca09 | ||
|
|
1498bf17bb | ||
|
|
1cafe23bd0 | ||
|
|
25290df798 | ||
|
|
20ca48e365 | ||
|
|
78dfb03b8e | ||
|
|
19e66a1c32 | ||
|
|
a1f71153c8 | ||
|
|
b1d260d98c | ||
|
|
a81aafefe0 | ||
|
|
454330e360 | ||
|
|
995159b365 | ||
|
|
fd1a33e9d2 | ||
|
|
e9615bc3aa | ||
|
|
b1b1597686 | ||
|
|
bc2eb65c0a | ||
|
|
7baa7ca8a1 | ||
|
|
9bbc73b48c | ||
|
|
b07a379ee1 | ||
|
|
af2c6174e8 | ||
|
|
6e5444709d | ||
|
|
831aa43f0e | ||
|
|
50a8592e5e | ||
|
|
cb7b6ef5cd | ||
|
|
b7c72d1607 | ||
|
|
cb2747528b | ||
|
|
52ad4ad2c9 | ||
|
|
93c81089f6 | ||
|
|
d7d23a7a93 | ||
|
|
5525e35d1a | ||
|
|
cdd7856ba0 | ||
|
|
7a6de46b17 | ||
|
|
1fcccc674a | ||
|
|
c0e6b5556d | ||
|
|
8aa2a7e44e | ||
|
|
0303d999ed | ||
|
|
914a817ddb | ||
|
|
4873e523a9 | ||
|
|
672ef518e2 | ||
|
|
d22cb150f6 | ||
|
|
8605b4f11f | ||
|
|
20eb661fb0 | ||
|
|
fad1ca2b6e | ||
|
|
747a687fe5 | ||
|
|
e34c63382c | ||
|
|
4b5c744ba5 | ||
|
|
932738f98e | ||
|
|
1426b00e83 | ||
|
|
e724409b58 | ||
|
|
9cf828e9a4 | ||
|
|
41408fb609 | ||
|
|
4c21e32838 | ||
|
|
385f3ea402 | ||
|
|
8d6d8cab18 | ||
|
|
01615f824a | ||
|
|
c6779ddc90 | ||
|
|
46eff65435 | ||
|
|
8e6a17cd0c | ||
|
|
17b314ea3a | ||
|
|
cbde36e9e0 | ||
|
|
6fc20924bd | ||
|
|
381b8064fc | ||
|
|
fc1a3ff7dc | ||
|
|
5fd3d13cfe | ||
|
|
1b38822459 | ||
|
|
8b7ecc430b | ||
|
|
d9d7a08fd6 | ||
|
|
2383c00bb9 | ||
|
|
dcce738d53 | ||
|
|
0daa3e0685 | ||
|
|
e06daa349c | ||
|
|
13aa693da1 | ||
|
|
f8afa7fc57 | ||
|
|
2f6a66a2ce | ||
|
|
b4674a9120 | ||
|
|
d76eb1153b | ||
|
|
0934c0014d | ||
|
|
19da3e1390 | ||
|
|
61d0ae5257 | ||
|
|
ebea6a8f50 | ||
|
|
f0b3baaf3f | ||
|
|
956837d3f3 | ||
|
|
25445d58d0 | ||
|
|
0b313a8aa8 | ||
|
|
54f954fd4b | ||
|
|
980f38ba03 | ||
|
|
9eada2724b | ||
|
|
b5f53ee918 | ||
|
|
efc175f654 | ||
|
|
6074afd999 | ||
|
|
219dff4055 | ||
|
|
9a3b07b622 | ||
|
|
a5f27d0d35 | ||
|
|
ce7ff0d14f | ||
|
|
d08571e862 | ||
|
|
e713210030 | ||
|
|
c057bf18f6 | ||
|
|
17a0bef824 | ||
|
|
9180c48b67 | ||
|
|
01acbe08f5 | ||
|
|
8a47cd4821 | ||
|
|
03050d9b07 | ||
|
|
9178878880 | ||
|
|
f38b1c7af9 | ||
|
|
45cd958b1e | ||
|
|
4d49c8f030 | ||
|
|
b2227ce37b | ||
|
|
a4694462ed | ||
|
|
f404b0e936 | ||
|
|
2df1e94fb3 | ||
|
|
acd0d7c343 | ||
|
|
1e8a7f09dd | ||
|
|
b65d50a9c7 | ||
|
|
909b4b6cfe | ||
|
|
2fbc238ecc | ||
|
|
7c73024919 | ||
|
|
0d85aa1644 | ||
|
|
df16acc4af | ||
|
|
7ad0c23704 | ||
|
|
e85e0322e3 | ||
|
|
1a6a31c857 | ||
|
|
65b09c6802 | ||
|
|
148dae32d5 | ||
|
|
9c116fa3d6 | ||
|
|
2eba014203 | ||
|
|
37d022251e | ||
|
|
04cc1da4d9 | ||
|
|
56bba90fdb | ||
|
|
a95841b0e9 | ||
|
|
9c6be9e87c | ||
|
|
7c830786ef | ||
|
|
9eb9047249 | ||
|
|
f7847b7767 | ||
|
|
ae720e4966 | ||
|
|
3b77bc99b6 | ||
|
|
b69a0c245e | ||
|
|
14bcef4c0e | ||
|
|
a23fe74cdc | ||
|
|
7b48f71dec | ||
|
|
c334c1f636 | ||
|
|
8572ff273f | ||
|
|
e6ddc71c75 | ||
|
|
81a0f9193d |
No files matched your search
@@ -34,3 +34,7 @@ trace_data/
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
/.ignore
|
||||
*.rej
|
||||
/build
|
||||
.clangd/
|
||||
.venv/**
|
||||
Vendored
+29
-1
@@ -1,13 +1,41 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Spark Local",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"run",
|
||||
"test",
|
||||
"-a",
|
||||
"OutrightVanillaOrder"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
|
||||
"environment": [
|
||||
{ "name": "TZ", "value": "America/Chicago" }
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SparkVM Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
"test",
|
||||
"-a",
|
||||
"[ilink3]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
|
||||
Vendored
+14
-2
@@ -48,9 +48,9 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "linux build (Debug)",
|
||||
"label": "linux_build",
|
||||
"type": "shell",
|
||||
"command": "cd build/ninja_debug; time ninja",
|
||||
"command": "docker exec -i ${USER}s_docker bash -c \"cd build/ninja_${input:build_configuration} && ninja\"",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
@@ -59,5 +59,17 @@
|
||||
"$gcc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "build_configuration",
|
||||
"type": "pickString",
|
||||
"default": "debug",
|
||||
"description": "Configuration:",
|
||||
"options": [
|
||||
"debug",
|
||||
"release"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+13
-2
@@ -1,6 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(spark)
|
||||
|
||||
set (USE_ONIX "TRUE")
|
||||
|
||||
if (WIN32)
|
||||
set (platform "windows")
|
||||
else ()
|
||||
@@ -40,11 +42,17 @@ else ()
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
|
||||
if (${USE_ONIX})
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_ONIX")
|
||||
endif()
|
||||
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
||||
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/web)
|
||||
# add_subdirectory(src/web)
|
||||
add_subdirectory(doc/user)
|
||||
add_subdirectory(src/sdk)
|
||||
add_subdirectory(src/data_files)
|
||||
@@ -55,11 +63,14 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.13.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0 ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${EXTERNALS_ROOT}/lib/Release/libACE-5.7.5.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libACE-5.7.5.so" DESTINATION bin)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0" DESTINATION bin)
|
||||
endif()
|
||||
@@ -5,7 +5,7 @@ weight = 10
|
||||
|
||||
# C#/.NET
|
||||
|
||||
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://nuget.org) to get started.
|
||||
We target [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) so you can use .NET Framework 4.6.1+ or .NET Core 2.0+. Simply add a reference to the [Spark Nuget package](https://www.nuget.org/packages/spark_sdk/) to get started.
|
||||
|
||||
```
|
||||
dotnet new console -lang c#
|
||||
|
||||
@@ -124,4 +124,17 @@ service OrderService {
|
||||
```
|
||||
### C++ Example
|
||||
```cpp
|
||||
#include "lib/api/public/ServiceContext.h"
|
||||
#include "lib/api/services/OrderService.client.pb.h"
|
||||
using namespace bts::api;
|
||||
ServiceContext ctx;
|
||||
auto orders = ctx.locate<orderman::OrderService>();
|
||||
auto unsub = orders.subscribe_fills().subscribe([](const auto& update) {
|
||||
std::cout
|
||||
<< "Added " << update.added().size() << " items, "
|
||||
<< "Removed " << update.removed().size() << " items, "
|
||||
<< "Updated " << update.updated().size() << " items, "
|
||||
<< "Total items " << update.all().size() << std::endl;
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::minutes(5));
|
||||
```
|
||||
@@ -1,3 +1,51 @@
|
||||
+++
|
||||
title = "Market Data"
|
||||
+++
|
||||
+++
|
||||
|
||||
|
||||
Use the MarketDataService to subscribe enumerate all available securities.
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using System.Threading;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
|
||||
namespace sample {
|
||||
class Program {
|
||||
static void Main(string[] args) {
|
||||
using var context = new ServiceContext();
|
||||
var md = context.locate<MarketDataService>();
|
||||
using var sub = md.subscribe_securities().Subscribe(update => {
|
||||
foreach (var security in update.added) {
|
||||
Console.WriteLine($"Added security {security.Id}: {security.Symbol}");
|
||||
}
|
||||
});
|
||||
|
||||
Thread.Sleep(TimeSpan.FromMinutes(5));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```cpp
|
||||
#include "lib/api/public/ServiceContext.h"
|
||||
#include "spark/lib/domain_model/Security.h"
|
||||
#include "lib/api/services/MarketDataService.client.pb.h"
|
||||
#include <thread>
|
||||
|
||||
using namespace bts::api;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ServiceContext ctx;
|
||||
auto md_service = ctx.locate<bts::api::MarketDataService>();
|
||||
auto sub = md_service.subscribe_securities().subscribe([](const auto& update) {
|
||||
for (const auto& s : update.added()) {
|
||||
std::cout << "Added security: " << s->id() << ": " << s->symbol() << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::minutes(5));
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -68,12 +68,21 @@ To update your Spark SDK simply download an unzip a new [spark_sdk.zip](https://
|
||||
|
||||
To preserve the old image so you can go back to it later run `docker tag spark_sdk spark_sdk:your_custom_tag` before loading the new SDK image.
|
||||
|
||||
<!--
|
||||
Then stop, remove and restart spark_sdk.
|
||||
### Re-build the Dev Container
|
||||
|
||||
If you are using Visual Studio Code you also need to rebuild the dev-container so it picks up the new image.
|
||||
|
||||
#### Inside VSCode
|
||||
To re-build from inside Visual Studio Code. Click the 'Remote Explorer' icon on the left hand side.
|
||||
You should see your spark_sdk project listed under the Containers section. Right click on it and select **rebuild-container**.
|
||||
|
||||
#### Command Line
|
||||
To re-build from the command line run `docker ps -a` and find the row with the spark_sdk image.
|
||||
```
|
||||
c:\bts> docker stop spark_sdk
|
||||
c:\bts> docker rm spark_sdk
|
||||
c:\bts> docker run -d -v %cd%:/sdk --name spark_sdk bluetradingsytems/spark_sdk
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
54d96cfa3888 spark_sdk "/bin/sh -c 'echo Co…" About a minute ago Up About a minute 127.0.0.1:9000-9001->9000-9001/tcp quizzical_lichterman
|
||||
```
|
||||
Then run `docker rm 54d96cfa3888` (use your container id instead of 54d96cfa3888) or `docker rm quizzical_lichterman` (use your container name).
|
||||
|
||||
|
||||
``` -->
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<p>
|
||||
The Spark client SDK allows you to integrate spark with your existing ecosystem. The Spark Client is built entirely on this simple, powerful API.
|
||||
</p>
|
||||
<a href="" class="button">Get started</a>
|
||||
<a href="{{ get_url(path="@/client/_index.md") }}" class="button">Get started</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
set(sources /spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_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/Market/_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/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_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/Trading/OrderTypes/index.md;content/bts_spark/Trading/_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/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
set(sources /home/pfisher/bts/DJ_ICE/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_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/Market/_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/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_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/Trading/OrderTypes/index.md;content/bts_spark/Trading/_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/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
@@ -1,9 +1,29 @@
|
||||
+++
|
||||
+++
|
||||
title = "Release notes"
|
||||
weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.16.2
|
||||
### User Visible Change
|
||||
- Synthetic creation dialog has a "fractional" checkbox next to the tick settings. Users may uncheck this box to enter integer tick sizes, so the synthetic security's prices will be displayed without decimals, or fractional units.
|
||||
## 2.16.1
|
||||
### Bug Fixes
|
||||
- OrderBook's algo state parameters (in the panel on the right side) are sorted alphabetically
|
||||
## 2.16.0
|
||||
### Bug Fixes
|
||||
- Solved an issue that could result in incorrect ladder levels
|
||||
- Solved an issue that could lead to incorrect routing of orders in the event of certain MSGW changes
|
||||
### API Changes
|
||||
- Add C# client side API samples for order entry, position monitoring and market data observation
|
||||
|
||||
## 2.15.10
|
||||
### Bug Fixes
|
||||
- Solved an issue that could cause the user interface to fail when playing some alert sounds
|
||||
### API Changes
|
||||
- Added settlement and position information to the API
|
||||
- Added the ability for user algos to report custom state to the user interface
|
||||
- Added the ability for algos to discover all active securities
|
||||
## 2.15.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug where some CME currency spreads exhibited incorrect tick values
|
||||
@@ -708,3 +728,4 @@ insert_anchor_links = "right"
|
||||
- Start, Day and Total PnL columns are painted with a red or green background color based on if the pnl value is negative or positive respectively
|
||||
- Corrected cash per point values, used in pnl calculations, for a number of products
|
||||
- PnL report will now remember any customizations you make to filtering and/or sorting criteria
|
||||
|
||||
+1
-1
Submodule docker/linux updated: 68f43cef8d...56c61231f3.
+1
-1
Submodule externals updated: 6d8f64ca5c...768cfa0f9b.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "2.1.508",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
astroid==2.7.3
|
||||
autopep8==1.5.7
|
||||
cffi==1.14.6
|
||||
colorama==0.4.4
|
||||
isort==5.9.3
|
||||
lazy-object-proxy==1.6.0
|
||||
mccabe==0.6.1
|
||||
pkg_resources==0.0.0
|
||||
platformdirs==2.3.0
|
||||
protobuf==3.19.1
|
||||
pybars3==0.9.7
|
||||
pycodestyle==2.7.0
|
||||
pycparser==2.20
|
||||
pylint==2.10.2
|
||||
PyMeta3==0.5.1
|
||||
termcolor==1.1.0
|
||||
toml==0.10.2
|
||||
typed-ast==1.4.3
|
||||
typing_extensions==4.1.1
|
||||
wrapt==1.12.1
|
||||
yapf==0.31.0
|
||||
@@ -22,6 +22,10 @@
|
||||
<GenDir>$(BaseOutputPath)\gen\$(MSBuildProjectName)</GenDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(VCTargetsPath)'==''">
|
||||
<VCTargetsPath>C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\VC\VCTargets</VCTargetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(ComputerSpecificConfig)" Condition="Exists($(ComputerSpecificConfig))" />
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace RiskUI {
|
||||
/// <summary>
|
||||
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
|
||||
///
|
||||
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
|
||||
/// Add this XmlNamespace attribute to the root element of the markup file where it is
|
||||
/// to be used:
|
||||
///
|
||||
/// xmlns:MyNamespace="clr-namespace:RiskUI"
|
||||
///
|
||||
///
|
||||
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
|
||||
/// Add this XmlNamespace attribute to the root element of the markup file where it is
|
||||
/// to be used:
|
||||
///
|
||||
/// xmlns:MyNamespace="clr-namespace:RiskUI;assembly=RiskUI"
|
||||
///
|
||||
/// You will also need to add a project reference from the project where the XAML file lives
|
||||
/// to this project and Rebuild to avoid compilation errors:
|
||||
///
|
||||
/// Right click on the target project in the Solution Explorer and
|
||||
/// "Add Reference"->"Projects"->[Select this project]
|
||||
///
|
||||
///
|
||||
/// Step 2)
|
||||
/// Go ahead and use your control in the XAML file.
|
||||
///
|
||||
/// <MyNamespace:CustomControl1/>
|
||||
///
|
||||
/// </summary>
|
||||
public class CustomControl1 : Control {
|
||||
static CustomControl1() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
<RootNamespace>bts</RootNamespace>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<Version>$(major).$(minor).$(patch)</Version>
|
||||
<Version Condition="'$(version)'=='..'">1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../spark_api/spark_api.csproj" />
|
||||
<ProjectReference Include="../Spark/Spark.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:RiskUI">
|
||||
<Style TargetType="{x:Type local:CustomControl1}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,24 +1,33 @@
|
||||
using bts;
|
||||
using bts.admin.risk;
|
||||
using bts.api;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
public readonly UserTuple user;
|
||||
|
||||
readonly AdminService _service;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
IDisposable _dispose;
|
||||
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, UserTuple user) {
|
||||
AdminModel(AdminService service, UserTuple user, SecurityStore security_store, ErrorReporter error_reporter) {
|
||||
this.user = user;
|
||||
_service = service;
|
||||
_error_reporter = error_reporter;
|
||||
// accounts
|
||||
on_accounts = service.subscribei_admin_data()
|
||||
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
|
||||
@@ -31,6 +40,34 @@ namespace mbts.spark {
|
||||
new Dictionary<uint, string>(0))
|
||||
.Replay(1)
|
||||
.RefCount();
|
||||
// account risk limits
|
||||
account_security_risk_limits = service.subscribei_admin_data().Where(ad => ad.all.Any())
|
||||
.Select(upd => {
|
||||
AdminDataImmutable admin_data = upd.all.First();
|
||||
return admin_data.Accounts.SelectMany(acc =>
|
||||
(acc.HasProprietaryRiskLimits ? acc.ProprietaryRiskLimits.SecurityLimits : Enumerable.Empty<SecurityRiskLimitUnitImmutable>())
|
||||
.Select(api_limit => AccountSecurityRiskLimitsDataHelper.to_client_data(acc.Id, api_limit)))
|
||||
.ToDictionary(AccountSecurityRiskLimitsDataHelper.key_fn);
|
||||
})
|
||||
.Table(AccountSecurityRiskLimitsDataHelper.key_fn);
|
||||
// this isn't the most efficient join, but the SecurityStore join is more complex than a simple
|
||||
// left join operation, because missing securities are retrieved asynchronously, then merged back into the output sequence.
|
||||
// Until it is known that this inefficiency is problematic, best to leave the battle-tested SecurityStore code alone.
|
||||
account_security_risk_limits = security_store.join<
|
||||
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>,
|
||||
AccountSecurityRiskLimits,
|
||||
AccountSecurityKey,
|
||||
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>>(account_security_risk_limits,
|
||||
cli_lim => cli_lim.security_key,
|
||||
table => (table.state.Values, table.added, table.changes, table.removed, AccountSecurityRiskLimitsDataHelper.key_fn),
|
||||
(all, added, changes, removed, key_fn) =>
|
||||
new TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>(
|
||||
ImmutableDictionary.CreateRange<AccountSecurityKey, AccountSecurityRiskLimits>(
|
||||
all.Select(tup => new KeyValuePair<AccountSecurityKey, AccountSecurityRiskLimits>(tup.val.key, risk_lim_with_sec(tup)))),
|
||||
added.Select(risk_lim_with_sec),
|
||||
changes.Select(ch => new Change<AccountSecurityRiskLimits>(risk_lim_with_sec(ch.Old), risk_lim_with_sec(ch.New))),
|
||||
removed.Select(risk_lim_with_sec),
|
||||
AccountSecurityRiskLimitsDataHelper.key_fn));
|
||||
// users
|
||||
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
|
||||
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
|
||||
@@ -45,12 +82,16 @@ namespace mbts.spark {
|
||||
.Cache() :
|
||||
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
|
||||
_dispose = on_accounts.Subscribe(map => _account_map = map);
|
||||
// local methods
|
||||
AccountSecurityRiskLimits risk_lim_with_sec((SecurityImmutable? sec, AccountSecurityRiskLimits val) tup) =>
|
||||
tup.val.with(security: tup.sec);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
|
||||
public IObservable<TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>> account_security_risk_limits { get; }
|
||||
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
@@ -68,7 +109,86 @@ namespace mbts.spark {
|
||||
public AccountTuple? get_default_account() =>
|
||||
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
|
||||
|
||||
public async Task<Result<AdminServiceOperationResult>[]> set_risk_limits(IReadOnlyDictionary<uint, IEnumerable<(uint, RiskLimits)>> state) {
|
||||
var results = await Task.WhenAll(state.Select(impl));
|
||||
return results;
|
||||
async Task<Result<AdminServiceOperationResult>> impl(KeyValuePair<uint, IEnumerable<(uint, RiskLimits)>> kv) {
|
||||
var prlimits = new ProprietaryRiskLimits();
|
||||
foreach (var (sec, lim) in kv.Value) {
|
||||
var api_lim = AccountSecurityRiskLimitsDataHelper.from_client_data(sec, lim);
|
||||
prlimits.SecurityLimits.Add(api_lim);
|
||||
}
|
||||
return await _service.update_proprietary_risk_limits_async(new ProprietaryRiskLimitsAPIArgument {
|
||||
AccountId = kv.Key,
|
||||
ProprietaryRiskLimits = prlimits
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Task<(bool success, string error_msg)> add_or_update_risk_limits(uint account_key, IEnumerable<(uint security_key, RiskLimits limits)> new_limits) {
|
||||
return _modify_risk_limits("Updating Risk Limits", account_key, add_limits);
|
||||
//
|
||||
ProprietaryRiskLimits add_limits(ProprietaryRiskLimits prlimits) {
|
||||
foreach (var (security_key, limits) in new_limits) {
|
||||
var api_limits = AccountSecurityRiskLimitsDataHelper.from_client_data(security_key, limits);
|
||||
if (prlimits.SecurityLimits.TryGetFirstIndex(l => l.SecurityKey == security_key, out int index)) {
|
||||
prlimits.SecurityLimits[index] = api_limits;
|
||||
} else {
|
||||
prlimits.SecurityLimits.Add(api_limits);
|
||||
}
|
||||
}
|
||||
return prlimits;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<(bool success, string error_msg)> delete_risk_limits(uint account_key, IEnumerable<uint> security_keys) {
|
||||
return _modify_risk_limits("Deleting Risk Limits", account_key, remove_secs);
|
||||
//
|
||||
ProprietaryRiskLimits remove_secs(ProprietaryRiskLimits prlimits) {
|
||||
var sec_key_set = new HashSet<uint>(security_keys);
|
||||
for (int i = 0; i < prlimits.SecurityLimits.Count;) {
|
||||
if (sec_key_set.Contains(prlimits.SecurityLimits[i].SecurityKey)) {
|
||||
prlimits.SecurityLimits.RemoveAt(i);
|
||||
} else i++;
|
||||
}
|
||||
return prlimits;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<(bool success, string error_msg)> _modify_risk_limits(string op_desc, uint account_key, Func<ProprietaryRiskLimits, ProprietaryRiskLimits> modify) {
|
||||
var api_limits = new ProprietaryRiskLimits();
|
||||
var snapshot = await _get_current_admin_data_async();
|
||||
if (!snapshot.HasValue)
|
||||
return (false, "Timed out waiting for data from server.");
|
||||
var mutable_snap = snapshot.Value.CloneToMutable();
|
||||
if (!snapshot.Value.Accounts.TryGetFirst(acc => acc.Id == account_key, out var target_acci)) {
|
||||
return (false, $"Account id '{account_key}' not found.");
|
||||
}
|
||||
var prlimits = target_acci.HasProprietaryRiskLimits ? target_acci.ProprietaryRiskLimits.CloneToMutable() : new ProprietaryRiskLimits();
|
||||
prlimits = modify(prlimits);
|
||||
if (prlimits is null) {
|
||||
return (false, "Client-side modification function returned null.");
|
||||
}
|
||||
var req = new ProprietaryRiskLimitsAPIArgument {
|
||||
AccountId = account_key,
|
||||
ProprietaryRiskLimits = prlimits
|
||||
};
|
||||
var reply = await _error_reporter.safe_execute<AdminServiceOperationResult>(
|
||||
() => _service.update_proprietary_risk_limits_async(req),
|
||||
r => r.IsSuccess,
|
||||
r => r.ErrorText,
|
||||
op_desc);
|
||||
return (reply.success, reply.value.ErrorText);
|
||||
}
|
||||
|
||||
async Task<AdminDataImmutable?> _get_current_admin_data_async() =>
|
||||
await _service.subscribei_admin_data()
|
||||
.Where(upd => upd.all.Any())
|
||||
.Select(upd => (AdminDataImmutable?)upd.all.First())
|
||||
.FirstOrDefaultAsync(TimeSpan.FromSeconds(5));
|
||||
//async Task<AdminDataImmutable?>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using bts.api;
|
||||
using bts.api.admin;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts.admin.risk {
|
||||
readonly struct AccountSecurityKey : IEquatable<AccountSecurityKey> {
|
||||
public AccountSecurityKey(uint account, uint security) {
|
||||
this.account = account;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public uint account { get; }
|
||||
public uint security { get; }
|
||||
|
||||
public bool Equals(AccountSecurityKey other) =>
|
||||
security == other.security &&
|
||||
account == other.account;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AccountSecurityKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(security.GetHashCode(), account.GetHashCode());
|
||||
|
||||
public static bool operator ==(AccountSecurityKey a, AccountSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AccountSecurityKey a, AccountSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
readonly struct AccountSecurityRiskLimits : IEquatable<AccountSecurityRiskLimits> {
|
||||
readonly AccountTuple? _account;
|
||||
|
||||
public AccountSecurityRiskLimits(AccountSecurityKey key, RiskLimits limits, SecurityImmutable? security = null, AccountTuple? account = null) {
|
||||
this.key = key;
|
||||
this.limits = limits;
|
||||
this.security = security;
|
||||
_account = account;
|
||||
}
|
||||
|
||||
public AccountSecurityKey key { get; }
|
||||
public uint account_key => key.account;
|
||||
public uint security_key => key.security;
|
||||
public string account_name => _account?.name;
|
||||
public SecurityImmutable? security { get; }
|
||||
public RiskLimits limits { get; }
|
||||
|
||||
public bool Equals(AccountSecurityRiskLimits other) =>
|
||||
key.Equals(other.key) &&
|
||||
limits.Equals(other.limits);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AccountSecurityRiskLimits other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(key.GetHashCode(), limits.GetHashCode());
|
||||
}
|
||||
|
||||
readonly struct RiskLimits : IEquatable<RiskLimits> {
|
||||
public RiskLimits(
|
||||
bool is_position_limit_enabled,
|
||||
uint max_long_position,
|
||||
uint max_short_position,
|
||||
bool is_clip_limit_enabled,
|
||||
uint clip_limit,
|
||||
bool is_price_limit_enabled,
|
||||
double min_price,
|
||||
double max_price) {
|
||||
//
|
||||
this.is_position_limit_enabled = is_position_limit_enabled;
|
||||
this.max_long_position = max_long_position;
|
||||
this.max_short_position = max_short_position;
|
||||
this.is_clip_limit_enabled = is_clip_limit_enabled;
|
||||
this.clip_limit = clip_limit;
|
||||
this.is_price_limit_enabled = is_price_limit_enabled;
|
||||
this.min_price = min_price;
|
||||
this.max_price = max_price;
|
||||
}
|
||||
|
||||
// position
|
||||
public bool is_position_limit_enabled { get; }
|
||||
public uint max_long_position { get; }
|
||||
public uint max_short_position { get; }
|
||||
// clip
|
||||
public bool is_clip_limit_enabled { get; }
|
||||
public uint clip_limit { get; }
|
||||
// price
|
||||
public bool is_price_limit_enabled { get; }
|
||||
public double min_price { get; }
|
||||
public double max_price { get; }
|
||||
|
||||
public bool Equals(RiskLimits other) =>
|
||||
is_position_limit_enabled == other.is_position_limit_enabled &&
|
||||
max_long_position == other.max_long_position &&
|
||||
max_short_position == other.max_short_position &&
|
||||
is_clip_limit_enabled == other.is_clip_limit_enabled &&
|
||||
clip_limit == other.clip_limit &&
|
||||
is_price_limit_enabled == other.is_price_limit_enabled &&
|
||||
min_price == other.min_price &&
|
||||
max_price == other.max_price;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is RiskLimits other && Equals(other);
|
||||
|
||||
public override int GetHashCode() {
|
||||
const int non_bin_bits = 28;
|
||||
// Evenly split the non-binary props across the first 28 bits.
|
||||
// The last 3 bits are used to represent each of the 'is_enabled' (binary) props.
|
||||
var hash = EqualityComparer.auto_phase(new[] {
|
||||
max_long_position.GetHashCode(),
|
||||
max_short_position.GetHashCode(),
|
||||
clip_limit.GetHashCode(),
|
||||
min_price.GetHashCode(),
|
||||
max_price.GetHashCode()
|
||||
}, non_bin_bits);
|
||||
if (is_position_limit_enabled)
|
||||
hash |= 1 << (non_bin_bits + 1);
|
||||
if (is_clip_limit_enabled)
|
||||
hash |= 1 << (non_bin_bits + 2);
|
||||
if (is_price_limit_enabled)
|
||||
hash |= 1 << (non_bin_bits + 3);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
static class AccountSecurityRiskLimitsDataHelper {
|
||||
public static readonly Func<AccountSecurityRiskLimits, AccountSecurityKey> key_fn =
|
||||
a => a.key;
|
||||
|
||||
// Don't make this a constructor. Subscribers to this data are discouraged
|
||||
// from using the server side data (client/server coupling should be minimal).
|
||||
public static AccountSecurityRiskLimits to_client_data(uint account_key, SecurityRiskLimitUnitImmutable api_data) =>
|
||||
new AccountSecurityRiskLimits(
|
||||
new AccountSecurityKey(account_key, api_data.SecurityKey),
|
||||
new RiskLimits(
|
||||
api_data.EnforceNetPosLimits,
|
||||
api_data.NetLongPositionLimit,
|
||||
api_data.NetShortPositionLimit,
|
||||
api_data.EnforceClipLimits,
|
||||
api_data.ClipSize,
|
||||
api_data.EnforcePriceBands,
|
||||
api_data.MinSellPrice,
|
||||
api_data.MaxBuyPrice));
|
||||
|
||||
public static AccountSecurityRiskLimits with(this AccountSecurityRiskLimits cli_data, RiskLimits? limits = null, AccountTuple? account = null, SecurityImmutable? security = null, AccountSecurityKey? key = null) =>
|
||||
new AccountSecurityRiskLimits(
|
||||
key ?? cli_data.key,
|
||||
limits ?? cli_data.limits,
|
||||
account: account ?? new AccountTuple(cli_data.account_key, cli_data.account_name),
|
||||
security: security ?? cli_data.security);
|
||||
|
||||
public static RiskLimits with(this RiskLimits limits,
|
||||
bool? is_position_limit_enabled = null,
|
||||
uint? max_long_position = null,
|
||||
uint? max_short_position = null,
|
||||
bool? is_clip_limit_enabled = null,
|
||||
uint? clip_limit = null,
|
||||
bool? is_price_limit_enabled = null,
|
||||
double? min_price = null,
|
||||
double? max_price = null) =>
|
||||
new RiskLimits(
|
||||
is_position_limit_enabled ?? limits.is_position_limit_enabled,
|
||||
max_long_position ?? limits.max_long_position,
|
||||
max_short_position ?? limits.max_short_position,
|
||||
is_clip_limit_enabled ?? limits.is_clip_limit_enabled,
|
||||
clip_limit ?? limits.clip_limit,
|
||||
is_price_limit_enabled ?? limits.is_price_limit_enabled,
|
||||
min_price ?? limits.min_price,
|
||||
max_price ?? limits.max_price);
|
||||
|
||||
public static SecurityRiskLimitUnit from_client_data(uint security_key, RiskLimits limits) =>
|
||||
new SecurityRiskLimitUnit {
|
||||
SecurityKey = security_key,
|
||||
NetLongPositionLimit = limits.max_long_position,
|
||||
NetShortPositionLimit = limits.max_short_position,
|
||||
MaxBuyPrice = limits.max_price,
|
||||
MinSellPrice = limits.min_price,
|
||||
ClipSize = limits.clip_limit,
|
||||
EnforceNetPosLimits = limits.is_position_limit_enabled,
|
||||
EnforceClipLimits = limits.is_clip_limit_enabled,
|
||||
EnforcePriceBands = limits.is_price_limit_enabled
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoProviderFactory {
|
||||
[ImportingConstructor]
|
||||
AlgoProviderFactory(IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
|
||||
var from_conf = config.Configuration.get<bts.config.AlgoTypeRuleSet>();
|
||||
if (from_conf.rules.Count == 0) {
|
||||
// There seems to be a config race bug, where the returned config values are empty, despite having entries in spark.conf.
|
||||
// [John]I've only noticed it happen when attached to the debugger,
|
||||
// but best to have a contingency plan in place anyways, to prevent any chance of regression.
|
||||
// The fallback sets below mimic the old hardcoded algo type arrays.
|
||||
var full_algo_set = new AlgoTypeSetImmutable(
|
||||
ImmutableHashSet.CreateRange(EnumHelper.get_values_except(
|
||||
// non-factory algos
|
||||
AlgoType.None,
|
||||
AlgoType.Dynamic,
|
||||
AlgoType.Composite,
|
||||
// obsolete algos
|
||||
AlgoType.Oco,
|
||||
AlgoType.TimeBox,
|
||||
AlgoType.Pricer,
|
||||
AlgoType.Tick,
|
||||
AlgoType.Trigger,
|
||||
AlgoType.QueueHolder)),
|
||||
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
|
||||
var rules = new[] {
|
||||
// Glbx: Futures/Spreads
|
||||
new SecurityAlgoTypeSetImmutable(
|
||||
new SecurityCriteriaClause(
|
||||
exchanges: new[] { Venue.Glbx },
|
||||
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
|
||||
new AlgoTypeSetImmutable(
|
||||
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader }),
|
||||
full_algo_set.dynamic_types)),
|
||||
// Gen: Futures/Spreads
|
||||
new SecurityAlgoTypeSetImmutable(
|
||||
new SecurityCriteriaClause(
|
||||
exchanges: EnumHelper.get_values_except(Venue.Glbx, Venue.Unset).ToArray(),
|
||||
security_types: new[] { SecurityType.Future, SecurityType.Spread }),
|
||||
new AlgoTypeSetImmutable(
|
||||
full_algo_set.factory_types.Except(new[] { AlgoType.Spreader, AlgoType.GlbxIceberg }),
|
||||
full_algo_set.dynamic_types)),
|
||||
// Gen: Synthetics
|
||||
new SecurityAlgoTypeSetImmutable(
|
||||
new SecurityCriteriaClause(
|
||||
security_types: new[] { SecurityType.Synthetic }),
|
||||
new AlgoTypeSetImmutable(
|
||||
ImmutableHashSet.CreateRange(new[] {
|
||||
AlgoType.Spreader,
|
||||
AlgoType.Iceberg,
|
||||
AlgoType.Lean,
|
||||
AlgoType.Join,
|
||||
AlgoType.Cover,
|
||||
AlgoType.Offset,
|
||||
AlgoType.Top,
|
||||
AlgoType.Splitter,
|
||||
AlgoType.Scratch,
|
||||
AlgoType.Prorata
|
||||
}),
|
||||
full_algo_set.dynamic_types))
|
||||
};
|
||||
// attachable
|
||||
var attachable_algos =
|
||||
ImmutableHashSet.CreateRange(new[] {
|
||||
AlgoType.Iceberg,
|
||||
AlgoType.Lean,
|
||||
AlgoType.Join,
|
||||
AlgoType.Cover,
|
||||
AlgoType.Top,
|
||||
AlgoType.Offset,
|
||||
AlgoType.Scratch,
|
||||
AlgoType.Prorata
|
||||
});
|
||||
var attachable_rules = Array.ConvertAll(rules,
|
||||
satsi => new SecurityAlgoTypeSetImmutable(
|
||||
satsi.security_criteria,
|
||||
new AlgoTypeSetImmutable(
|
||||
satsi.algo_type_set.factory_types.Intersect(attachable_algos),
|
||||
satsi.algo_type_set.dynamic_types)));
|
||||
|
||||
Log.Config.warn($"Failed to load AlgoTypes config, falling back to the following algo set rules:" +
|
||||
"\n\tStandard\n" +
|
||||
rules.fmt_diagnostic("\n\t\t") +
|
||||
"\n\n\tAttachable\n" +
|
||||
attachable_rules.fmt_diagnostic("\n\t\t"));
|
||||
|
||||
security_algo_provider = new AlgoProvider(rules, studio_model, dynamic_helper, security_auxiliary_model);
|
||||
attachable_algo_provider = new AttachableAlgoProvider(attachable_rules, studio_model, dynamic_helper, security_auxiliary_model);
|
||||
} else {
|
||||
var algo_type_data =
|
||||
EnumHelper.get_values_except(AlgoType.Dynamic, AlgoType.Composite, AlgoType.None)
|
||||
.Select(t => _get_type_data(t))
|
||||
.Concat(dynamic_helper.dynamic_types.Select(dt => _get_type_data(AlgoType.Dynamic, dt)))
|
||||
.ToListLazy();
|
||||
var conf_sets = from_conf.rules.Select(r => _to_security_algo_set(r, dynamic_helper, algo_type_data)).ToList();
|
||||
security_algo_provider = new AlgoProvider(conf_sets.Select(tup => tup.standard).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
|
||||
attachable_algo_provider = new AttachableAlgoProvider(conf_sets.Select(tup => tup.attachable).ToList(), studio_model, dynamic_helper, security_auxiliary_model);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public IAlgoProvider security_algo_provider { get; }
|
||||
[Export]
|
||||
public IAttachableAlgoProvider attachable_algo_provider { get; }
|
||||
|
||||
static (string name, AlgoType factory, string dynamic) _get_type_data(AlgoType type, string dynamic_type = null) {
|
||||
string type_name = type.ToString();
|
||||
return (type_name, type, dynamic_type ?? type_name);
|
||||
}
|
||||
|
||||
static SecurityCriteriaClause _to_clause(bts.config.AlgoTypeContext context) =>
|
||||
context == null ?
|
||||
new SecurityCriteriaClause() :
|
||||
new SecurityCriteriaClause(
|
||||
exchanges: context.exchanges?.Select(EnumHelper.parse<Venue>)?.ToArray(),
|
||||
security_types: context.security_types?.Select(EnumHelper.parse<Security.ContractOneofCase>)?.Select(t => t.security_type())?.ToArray(),
|
||||
symbols: context.symbols?.ToArray());
|
||||
|
||||
static (SecurityAlgoTypeSetImmutable standard, SecurityAlgoTypeSetImmutable attachable) _to_security_algo_set(
|
||||
bts.config.AlgoTypeRule rule,
|
||||
DynamicOrderHelper dynamic_helper,
|
||||
IEnumerable<(string name, AlgoType factory, string dynamic)> algo_type_data) {
|
||||
//
|
||||
var factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
|
||||
var dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
|
||||
var attachable_factory_types = ImmutableHashSet<AlgoType>.Empty.ToBuilder();
|
||||
var attachable_dynamic_types = ImmutableHashSet<string>.Empty.ToBuilder();
|
||||
foreach (var a in rule.algos) {
|
||||
if (Enum.TryParse(a.name, out AlgoType sys_algo) && sys_algo != AlgoType.Composite && sys_algo != AlgoType.Dynamic) {
|
||||
on_sys_match(sys_algo, a.is_attachable);
|
||||
}
|
||||
else if (dynamic_helper.try_parse(a.name, out var dyn_type)) {
|
||||
on_dyn_match(dyn_type, a.is_attachable);
|
||||
} else {
|
||||
foreach (var (name, factory, dynamic) in algo_type_data) {
|
||||
if (Regex.Match(name, a.name).Success) {
|
||||
switch (factory) {
|
||||
case AlgoType.None:
|
||||
case AlgoType.Composite:
|
||||
// unexpected, but no need to crash
|
||||
continue;
|
||||
case AlgoType.Dynamic:
|
||||
on_dyn_match(dynamic, a.is_attachable);
|
||||
break;
|
||||
default:
|
||||
on_sys_match(factory, a.is_attachable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
// standard
|
||||
new SecurityAlgoTypeSetImmutable(
|
||||
_to_clause(rule.context),
|
||||
new AlgoTypeSetImmutable(
|
||||
factory_types.ToImmutable(),
|
||||
dynamic_types.ToImmutable())),
|
||||
// attachable
|
||||
new SecurityAlgoTypeSetImmutable(
|
||||
_to_clause(rule.context),
|
||||
new AlgoTypeSetImmutable(
|
||||
attachable_factory_types.ToImmutable(),
|
||||
attachable_dynamic_types.ToImmutable())));
|
||||
|
||||
// local methods
|
||||
void on_sys_match(AlgoType sys_algo, bool is_attachable) {
|
||||
factory_types.Add(sys_algo);
|
||||
if (is_attachable)
|
||||
attachable_factory_types.Add(sys_algo);
|
||||
}
|
||||
void on_dyn_match(string dyn_algo, bool is_attachable) {
|
||||
dynamic_types.Add(dyn_algo);
|
||||
if (is_attachable)
|
||||
attachable_dynamic_types.Add(dyn_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
readonly struct AlgoTypeSetImmutable {
|
||||
public static readonly AlgoTypeSetImmutable empty = new AlgoTypeSetImmutable(ImmutableHashSet<AlgoType>.Empty, ImmutableHashSet<string>.Empty);
|
||||
public readonly ImmutableHashSet<AlgoType> factory_types;
|
||||
public readonly ImmutableHashSet<string> dynamic_types;
|
||||
|
||||
public AlgoTypeSetImmutable(
|
||||
ImmutableHashSet<AlgoType> factory_types,
|
||||
ImmutableHashSet<string> dynamic_types) {
|
||||
this.factory_types = factory_types;
|
||||
this.dynamic_types = dynamic_types;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct SecurityAlgoTypeSetImmutable {
|
||||
public readonly SecurityCriteriaClause security_criteria;
|
||||
public readonly AlgoTypeSetImmutable algo_type_set;
|
||||
|
||||
public SecurityAlgoTypeSetImmutable(SecurityCriteriaClause security_criteria, AlgoTypeSetImmutable algo_type_set, CustomOrderDefinitionImmutable? default_algo = null) {
|
||||
this.security_criteria = security_criteria;
|
||||
this.algo_type_set = algo_type_set;
|
||||
}
|
||||
}
|
||||
|
||||
static class AlgoTypeSetExtensions {
|
||||
public static string fmt_diagnostic(this IEnumerable<SecurityAlgoTypeSetImmutable> rules, string line_delim = "\n") {
|
||||
var sb = new StringBuilder();
|
||||
int rule_count = 1;
|
||||
using (var en = rules.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
string line_delim_tab1 = line_delim + "\t";
|
||||
string line_delim_tab2 = line_delim + "\t\t";
|
||||
append_rule(en.Current);
|
||||
while (en.MoveNext()) {
|
||||
sb.AppendLine();
|
||||
append_rule(en.Current);
|
||||
}
|
||||
//
|
||||
void append_rule(SecurityAlgoTypeSetImmutable r) {
|
||||
sb.Append(line_delim + $"**RULE {rule_count++}**");
|
||||
sb.Append(line_delim_tab1);
|
||||
sb.Append("Security Criteria: ");
|
||||
sb.Append(r.security_criteria.is_defined ? string.Join("; ", r.security_criteria.fmt_diagnostic_msgs()) : "(Undefined)");
|
||||
sb.Append(line_delim_tab1);
|
||||
// Factory
|
||||
sb.Append("Factory Types:");
|
||||
sb.Append(line_delim_tab2);
|
||||
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.factory_types));
|
||||
sb.Append(line_delim_tab1);
|
||||
// Dynamic
|
||||
sb.Append("Dynamic Types:");
|
||||
sb.Append(line_delim_tab2);
|
||||
sb.Append(string.Join(line_delim_tab2, r.algo_type_set.dynamic_types));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static AlgoTypeSetImmutable intersect_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
|
||||
new AlgoTypeSetImmutable(a.factory_types.Intersect(b.factory_types), a.dynamic_types.Intersect(b.dynamic_types));
|
||||
|
||||
public static AlgoTypeSetImmutable union_with(this AlgoTypeSetImmutable a, AlgoTypeSetImmutable b) =>
|
||||
new AlgoTypeSetImmutable(a.factory_types.Union(b.factory_types), a.dynamic_types.Union(b.dynamic_types));
|
||||
|
||||
public static IEnumerable<AlgoData> define_algos(
|
||||
this AlgoTypeSetImmutable algo_set,
|
||||
DynamicOrderHelper dynamic_helper,
|
||||
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
|
||||
uint display_qty_divisor = 1) =>
|
||||
//
|
||||
algo_set.factory_types
|
||||
.Select(algo_type => {
|
||||
var def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(algo_type);
|
||||
if (fit_to_type != null)
|
||||
def = fit_to_type(def);
|
||||
// multiply quantity props by display qty divisor to get appropriate actual value
|
||||
if (display_qty_divisor > 1) {
|
||||
switch (def.CustomCase) {
|
||||
case AlgoType.Iceberg:
|
||||
var mdef = def.CloneToMutable();
|
||||
mdef.Iceberg.ShowQuantity *= display_qty_divisor;
|
||||
def = new CustomOrderDefinitionImmutable(mdef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
})
|
||||
.Concat(algo_set.dynamic_types.Select(dynamic_helper.create_custom_defi))
|
||||
.Select(AlgoData.from_custom_def);
|
||||
|
||||
public static ImmutableDictionary<AlgoKey, AlgoData> match_algos(
|
||||
this IEnumerable<SecurityAlgoTypeSetImmutable> security_algo_sets,
|
||||
SecurityCriteriaClause clause,
|
||||
DynamicOrderHelper dynamic_helper,
|
||||
Func<CustomOrderDefinitionImmutable, CustomOrderDefinitionImmutable> fit_to_type = null,
|
||||
uint display_qty_divisor = 1) {
|
||||
//
|
||||
ImmutableDictionary<AlgoKey, AlgoData> rv = null;
|
||||
foreach (var set in security_algo_sets) {
|
||||
if (!clause.is_subset_of(set.security_criteria)) {
|
||||
continue;
|
||||
} else if (rv == null) {
|
||||
rv = ImmutableDictionary.CreateRange(
|
||||
set.algo_type_set.define_algos(dynamic_helper, fit_to_type, display_qty_divisor)
|
||||
.Select(a => new KeyValuePair<AlgoKey, AlgoData>(a.key, a)));
|
||||
} else {
|
||||
foreach (var kv in rv) {
|
||||
if (kv.Value.value.CustomCase == AlgoType.Dynamic) {
|
||||
if (!set.algo_type_set.dynamic_types.Contains(kv.Value.value.Dynamic.DynamicOrderType))
|
||||
rv = rv.Remove(kv.Key);
|
||||
} else if (!set.algo_type_set.factory_types.Contains(kv.Value.value.CustomCase))
|
||||
rv = rv.Remove(kv.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv ?? ImmutableDictionary<AlgoKey, AlgoData>.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
interface IAlgoProvider {
|
||||
AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security);
|
||||
AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause);
|
||||
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos();
|
||||
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security);
|
||||
IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause clause);
|
||||
}
|
||||
|
||||
// This interface is used to distinguish the attachable algo provider
|
||||
// from the standard one wrt MEF export/import.
|
||||
interface IAttachableAlgoProvider : IAlgoProvider { }
|
||||
|
||||
class AlgoProvider : IAlgoProvider {
|
||||
readonly IEnumerable<SecurityAlgoTypeSetImmutable> _security_algo_types;
|
||||
readonly IAlgoStudioModel _studio_model;
|
||||
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
|
||||
readonly IObservable<TableUpdate<AlgoData, AlgoKey>> _studio_algos_source;
|
||||
readonly DynamicOrderHelper _dynamic_helper;
|
||||
readonly AlgoTypeSetImmutable _full_set;
|
||||
|
||||
public AlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model) {
|
||||
_security_algo_types = algo_sets;
|
||||
_studio_model = studio_model;
|
||||
_dynamic_helper = dynamic_helper;
|
||||
_full_set = new AlgoTypeSetImmutable(
|
||||
ImmutableHashSet.CreateRange(
|
||||
EnumHelper.get_values_except(CustomOrderDefinition.CustomOneofCase.None, CustomOrderDefinition.CustomOneofCase.Dynamic, CustomOrderDefinition.CustomOneofCase.Composite)),
|
||||
ImmutableHashSet.CreateRange(dynamic_helper.dynamic_types));
|
||||
_security_auxiliary_model = security_auxiliary_model;
|
||||
/// initialize <see cref="_studio_algos_source"/> with an empty table, so the
|
||||
/// <see cref="UpdateExtensions.AppendTable{T, TKey}(IObservable{TableUpdate{T, TKey}}, IEnumerable{T})"/>
|
||||
/// calls made by the 'subscribe_algos' methods will fire at least once, even if the underlying
|
||||
/// studio algos stream is empty have been created (this would be the case if the user had not yet created any studio algos).
|
||||
/// <see cref="subscribe_algos"/>,
|
||||
/// <see cref="subscribe_algos(SecurityCriteriaClause)"/>,
|
||||
/// <see cref="subscribe_algos(SecurityImmutable)"/>
|
||||
_studio_algos_source = studio_model.definitions
|
||||
.StartWith(TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn))
|
||||
.Cache();
|
||||
}
|
||||
|
||||
public AlgoTypeSetImmutable get_permitted_algos(SecurityImmutable security) =>
|
||||
_security_algo_types.Where(p => p.security_criteria.includes(security))
|
||||
.Select(sec_algos => sec_algos.algo_type_set)
|
||||
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
|
||||
|
||||
public AlgoTypeSetImmutable get_permitted_algos(SecurityCriteriaClause clause) =>
|
||||
_security_algo_types.Where(p => p.security_criteria.is_subset_of(clause))
|
||||
.Select(sec_algos => sec_algos.algo_type_set)
|
||||
.Aggregate(_full_set, (result, algos) => result.intersect_with(algos));
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos() =>
|
||||
_studio_algos_source
|
||||
.AppendTable(_security_algo_types
|
||||
.Select(s => s.algo_type_set)
|
||||
.Aggregate(AlgoTypeSetImmutable.empty, (a, b) => a.union_with(b))
|
||||
.define_algos(_dynamic_helper));
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityCriteriaClause security_clause) {
|
||||
var system_algos = _security_algo_types.match_algos(
|
||||
security_clause,
|
||||
_dynamic_helper,
|
||||
def => _fit_to_type(def, security_clause, _security_algo_types));
|
||||
|
||||
return _studio_algos_source.Select(
|
||||
a => (algo: a, fitted_def: _fit_to_type(a.value, security_clause, _security_algo_types)),
|
||||
tup => tup.algo.key)
|
||||
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), a => a.key)
|
||||
.AppendTable(system_algos.Values);
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_algos(SecurityImmutable security) {
|
||||
var clause = new SecurityCriteriaClause(
|
||||
exchanges: new[] { security.Exchange },
|
||||
security_types: new[] { security.security_type() });
|
||||
// i.e., non-studio algos
|
||||
var system_algos = _security_algo_types.match_algos(
|
||||
clause,
|
||||
_dynamic_helper,
|
||||
def => _fit_to_type(def, security, _security_algo_types),
|
||||
_security_auxiliary_model.get_display_quantity_divisor(security));
|
||||
|
||||
return _studio_algos_source.Select(
|
||||
a => (algo: a, fitted_def: _fit_to_type(a.value, security, _security_algo_types)),
|
||||
tup => tup.algo.key)
|
||||
// filter out algos that use an unsupported root, because this would have the effect of duplicating the default algo
|
||||
// (e.g., GlbxIceberg orders would be converted to Limit/Spreader orders for non-Glbx securities).
|
||||
.Filter(tup => tup.fitted_def.actual().CustomCase == tup.algo.value.actual().CustomCase, tup => tup.algo.key)
|
||||
.Select(tup => AlgoData.create(tup.fitted_def, tup.algo.name, tup.algo.key), AlgoData.key_fn)
|
||||
.AppendTable(system_algos.Values);
|
||||
}
|
||||
|
||||
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) {
|
||||
if (security_clause.security_types.empty_if_null().Contains(Security.ContractOneofCase.Synthetic.security_type())) {
|
||||
mdef.Spreader = new SpreaderOrderDefinition();
|
||||
} else {
|
||||
mdef.Limit = new LimitOrderDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) {
|
||||
if (security.ContractCase == Security.ContractOneofCase.Synthetic) {
|
||||
mdef.Spreader = new SpreaderOrderDefinition {
|
||||
Security = security.CloneToMutable()
|
||||
};
|
||||
} else {
|
||||
mdef.Limit = new LimitOrderDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
|
||||
var mroot = defi.CloneToMutable();
|
||||
if (_fit_to_type(mroot, security_clause, algo_sets)) {
|
||||
defi = new CustomOrderDefinitionImmutable(mroot);
|
||||
}
|
||||
return defi;
|
||||
}
|
||||
|
||||
CustomOrderDefinitionImmutable _fit_to_type(CustomOrderDefinitionImmutable defi, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
|
||||
var mroot = defi.CloneToMutable();
|
||||
if (_fit_to_type(mroot, security, algo_sets)) {
|
||||
defi = new CustomOrderDefinitionImmutable(mroot);
|
||||
}
|
||||
return defi;
|
||||
}
|
||||
|
||||
bool _fit_to_type(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
|
||||
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
|
||||
mdef = mdef.Composite.Definition;
|
||||
var sec_sets = algo_sets.Where(s => security_clause.is_subset_of(s.security_criteria));
|
||||
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
if (!algo_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
|
||||
assign_fallback_algo(mdef, security_clause);
|
||||
return true;
|
||||
}
|
||||
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
|
||||
assign_fallback_algo(mdef, security_clause);
|
||||
return true;
|
||||
}
|
||||
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security_clause, algo_sets)) > 0;
|
||||
}
|
||||
|
||||
bool _fit_to_type(CustomOrderDefinition mdef, SecurityImmutable security, IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets) {
|
||||
while (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
|
||||
mdef = mdef.Composite.Definition;
|
||||
var sec_sets = algo_sets.Where(s => s.security_criteria.includes(security));
|
||||
if (mdef.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
if (!sec_sets.All(s => s.algo_type_set.dynamic_types.Contains(mdef.Dynamic.DynamicOrderType))) {
|
||||
assign_fallback_algo(mdef, security);
|
||||
return true;
|
||||
}
|
||||
} else if (!sec_sets.All(s => s.algo_type_set.factory_types.Contains(mdef.CustomCase))) {
|
||||
assign_fallback_algo(mdef, security);
|
||||
return true;
|
||||
}
|
||||
return OrderDataHelper.get_child_algo_data(mdef).Count(cad => _fit_to_type(cad.get_child(mdef), security, algo_sets)) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
class AttachableAlgoProvider : AlgoProvider, IAttachableAlgoProvider {
|
||||
public AttachableAlgoProvider(IEnumerable<SecurityAlgoTypeSetImmutable> algo_sets, IAlgoStudioModel studio_model, DynamicOrderHelper dynamic_helper, ISecurityAuxiliaryDataModel security_auxiliary_model)
|
||||
: base(algo_sets, studio_model, dynamic_helper, security_auxiliary_model) { }
|
||||
|
||||
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
|
||||
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
|
||||
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityCriteriaClause security_clause) =>
|
||||
mdef.Limit = new LimitOrderDefinition();
|
||||
|
||||
// arbitrarily assign LimitOrderDefinition (this is just a placeholder; the real child is the attach target algo).
|
||||
// Note: although 'None' might seem approrpriate, the server requires a valid definition (perhaps this will change in the future).
|
||||
protected override void assign_fallback_algo(CustomOrderDefinition mdef, SecurityImmutable security) =>
|
||||
mdef.Limit = new LimitOrderDefinition();
|
||||
}
|
||||
|
||||
static class AlgoProviderExtensions {
|
||||
// This stream filters out composite algos that are not subsets of the non-composite algo types in the table
|
||||
// (normally, such composites are 'auto-fit,' meaning the algo nodes that are not part of the set are replaced with ones that are).
|
||||
// For example, consider a composite algo the following structure: Offset -> GlbxIceberg.
|
||||
// If an update arrives with these noncomposite types: "Iceberg, Limit, Market," that composite algo is not a subset of the non-composite algo types,
|
||||
// because GlbxIceberg is not in the table, so it would only be added to the table if/when GlbxIceberg arrives.
|
||||
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(this IAlgoProvider algo_provider, bool allow_undefined_child = false) =>
|
||||
subscribe_complete_algos(algo_provider.subscribe_algos(), allow_undefined_child);
|
||||
|
||||
public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_complete_algos(IObservable<TableUpdate<AlgoData, AlgoKey>> source, bool allow_undefined_child = false) =>
|
||||
Observable.Create<TableUpdate<AlgoData, AlgoKey>>(o => {
|
||||
var last_out = TableUpdate<AlgoData, AlgoKey>.mk_empty(AlgoData.key_fn);
|
||||
var incomplete_composites = ImmutableHashSet<AlgoData>.Empty;
|
||||
return source.Subscribe(table => {
|
||||
var bd = last_out.to_builder();
|
||||
foreach (var algo in incomplete_composites.Where(a => _validate_algo_structure(a.value, table.state, allow_undefined_child))) {
|
||||
incomplete_composites = incomplete_composites.Remove(algo);
|
||||
bd.add_or_update(algo);
|
||||
}
|
||||
foreach (var a in table.removed.Concat(table.changes.Old()))
|
||||
bd.remove(a.key);
|
||||
foreach (var a in table.added.Concat(table.changed)) {
|
||||
if (_validate_algo_structure(a.value, table.state, allow_undefined_child)) {
|
||||
bd.add_or_update(a);
|
||||
} else {
|
||||
incomplete_composites = incomplete_composites.Add(a);
|
||||
}
|
||||
}
|
||||
o.OnNext(last_out = bd.to_table());
|
||||
});
|
||||
});
|
||||
|
||||
static bool _validate_algo_structure(CustomOrderDefinitionImmutable custom_def, IReadOnlyDictionary<AlgoKey, AlgoData> allowed_algo_parts, bool allow_undefined_child) =>
|
||||
OrderDataHelper.enumerate_algo_structure(custom_def)
|
||||
.Select((algo_node, index) =>
|
||||
// None is an acceptable algo type as long as it's not the root algo (i.e., index > 0)
|
||||
(index > 0 && allow_undefined_child && algo_node.type == CustomOrderDefinition.CustomOneofCase.None) || _is_part_contained(algo_node, allowed_algo_parts))
|
||||
.All();
|
||||
|
||||
static bool _is_part_contained((CustomOrderDefinition.CustomOneofCase type, string dyn, uint studio_id, uint spreader_sec) structure, IReadOnlyDictionary<AlgoKey, AlgoData> state) =>
|
||||
structure.type == CustomOrderDefinition.CustomOneofCase.Composite ||
|
||||
structure.type == CustomOrderDefinition.CustomOneofCase.Dynamic ?
|
||||
state.ContainsKey(AlgoKey.from_dynamic_type(structure.dyn)) :
|
||||
state.ContainsKey(AlgoKey.from_known_type(structure.type));
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ namespace mbts.ui.shell
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
|
||||
string path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\BTS\\stunnel\\";
|
||||
if (File.Exists(path + "config\\stunnel.conf"))
|
||||
{
|
||||
uint job_error = BTSUINativeMethods.setup_process_job_object();
|
||||
@@ -124,6 +124,15 @@ namespace mbts.ui.shell
|
||||
kill_stunnel();
|
||||
start_stunnel(path, job_error);
|
||||
}
|
||||
path = AppDomain.CurrentDomain.BaseDirectory + "\\..\\stunnel\\";
|
||||
if (File.Exists(path + "config\\stunnel.conf"))
|
||||
{
|
||||
uint job_error = BTSUINativeMethods.setup_process_job_object();
|
||||
//if we can't put this process and stunnel into a job, try to stop stunnel then restart
|
||||
if (job_error != 0)
|
||||
kill_stunnel();
|
||||
start_stunnel(path, job_error);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
|
||||
@@ -3,11 +3,13 @@ using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
@@ -19,20 +21,26 @@ namespace bts.shell {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
readonly bool _close_on_send;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _md_map;
|
||||
TableUpdate<(SecurityImmutable? sec, OrderRecordImmutable order), uint> _orders;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter) {
|
||||
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter)
|
||||
: this(bus, model, error_reporter, Configuration.get<bts.config.OrderTicketOptions>().close_on_send) { }
|
||||
|
||||
public OrderTicketDaemon(IBus bus, OrdersModel model, ErrorReporter error_reporter, bool close_on_send) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_error_reporter = error_reporter;
|
||||
_close_on_send = close_on_send;
|
||||
_dispose = Disposable.Create(
|
||||
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketAutoLookupTag).Subscribe(_show_order_tickets),
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
||||
);
|
||||
}
|
||||
@@ -54,17 +62,28 @@ namespace bts.shell {
|
||||
void _show_order_ticket(SecurityMD smd) =>
|
||||
_show_order_window(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd,
|
||||
Observable.Return(LadderSettings.Default))
|
||||
Observable.Return(LadderSettings.Default)) {
|
||||
close_on_send = _close_on_send
|
||||
}
|
||||
});
|
||||
|
||||
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
|
||||
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
|
||||
_show_order_window(new OrderTicket {
|
||||
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons))
|
||||
Observable.Return(LadderSettings.Default.calculator_buttons)) {
|
||||
close_on_send = _close_on_send
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _show_order_tickets(IImmutableSet<uint> securities) {
|
||||
foreach (var id in securities.empty_if_null()) {
|
||||
if (_md_map.TryGetValue(id, out var smd))
|
||||
_show_order_ticket(smd);
|
||||
}
|
||||
}
|
||||
|
||||
void _show_modify_order(uint order_key) {
|
||||
if (_orders.state.TryGetValue(order_key, out var ori) && ori.sec.HasValue
|
||||
&& (ori.order.HasDef && ori.order.Def.HasCustomDef && !(ori.order.Def.CustomDef.HasStop || ori.order.Def.CustomDef.HasStopLimit))) {
|
||||
@@ -78,4 +97,4 @@ namespace bts.shell {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ namespace mbts.ui {
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
|
||||
CopyBoxesGeometryKey = nameof(CopyBoxesGeometryKey),
|
||||
CurrencyExchangeGeometryKey = nameof(CurrencyExchangeGeometryKey),
|
||||
DataEditGeometryKey = nameof(DataEditGeometryKey),
|
||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
@@ -45,6 +47,7 @@ namespace mbts.ui {
|
||||
KeyGeometryKey = nameof(KeyGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
LetterDKey = nameof(LetterDKey),
|
||||
MenuOrientationGeometryKey = nameof(MenuOrientationGeometryKey),
|
||||
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
||||
PauseGeometryKey = nameof(PauseGeometryKey),
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
@@ -62,6 +65,8 @@ namespace mbts.ui {
|
||||
|
||||
public static Geometry PlayGeometry => Geometry.Parse(get_geometric_path(PlayGeometryKey).FirstOrDefault() ?? string.Empty);
|
||||
|
||||
static readonly Pen _copy_boxes_pen = new Pen(Brushes.Black, HighDpiExtensions.RoundLayoutValue(5)).frozen();
|
||||
|
||||
static readonly string base_uri = "pack://application:,,,/Images/Icons/";
|
||||
private static ResourceDictionary _resources = (ResourceDictionary)Application.LoadComponent(new Uri("/Images/Vector/Icons.xaml", UriKind.RelativeOrAbsolute));
|
||||
|
||||
@@ -156,6 +161,8 @@ namespace mbts.ui {
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
|
||||
public static ImageSource CopyBoxes => create_icon(CopyBoxesGeometryKey, null, _copy_boxes_pen);
|
||||
public static ImageSource CurrencyExchange => create_icon(CurrencyExchangeGeometryKey);
|
||||
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
|
||||
@@ -179,6 +186,7 @@ namespace mbts.ui {
|
||||
public static ImageSource Import => load_resource_and_freeze("Import");
|
||||
public static ImageSource Key => create_icon(KeyGeometryKey);
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource MenuOrientation => create_icon(MenuOrientationGeometryKey);
|
||||
public static ImageSource Pause => create_icon(PauseGeometryKey);
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
|
||||
@@ -46,10 +46,26 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<x:Array x:Key="{x:Static mbts:Icons.CopyBoxesGeometryKey}" Type="{x:Type sys:String}">
|
||||
<sys:String>
|
||||
M-55,5 L-55,55 L-5,55
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M-40,-10 L-40,40 L10,40 L10,-10 L-40,-10
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M-10,10 L40,10 L40,-40 L-10,-40 L-10,10
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
|
||||
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.CurrencyExchangeGeometryKey}">
|
||||
M0,24L15.324005,24C16.042007,24.785004,16.875,25.460999,17.800003,26L2,26 2,30 23.087997,30 23.087006,27.424988C23.771011,27.423004,24.439011,27.352997,25.085999,27.225983L25.085999,30C25.085999,31.10199,24.189011,32,23.085999,32L2,32C0.89700317,32,0,31.10199,0,30z M22.484015,13.026998L23.475018,13.026998 23.475018,13.856997C24.151018,13.885996,24.605021,14.016996,24.93902,14.167996L24.646021,15.233995C24.38302,15.129995 23.919018,14.912995 23.191017,14.912995 22.535015,14.912995 22.323015,15.176995 22.323015,15.440995 22.323015,15.751994 22.676016,15.950994 23.535017,16.251994 24.73702,16.647994 25.222021,17.166993 25.222021,18.015992 25.222021,18.854991 24.58502,19.571991 23.424017,19.759991L23.424017,20.72199 22.434015,20.72199 22.434015,19.825991C21.757013,19.797991,21.101011,19.627991,20.71701,19.420991L21.019012,18.317992C21.444014,18.533992 22.040014,18.731992 22.697016,18.731992 23.272017,18.731992 23.666018,18.524992 23.666018,18.147992 23.666018,17.788992 23.343018,17.562993 22.596016,17.326993 21.515013,16.987993 20.777012,16.516994 20.777012,15.601995 20.777012,14.771996 21.404013,14.119996 22.484015,13.922997z M0.065009117,12.384011L13.628021,12.384011C13.320021,13.018011,13.075021,13.687011,12.89902,14.384011L2.065011,14.384011 2.065011,18.384011 12.68002,18.384011C12.77602,19.075011,12.93702,19.743011,13.16102,20.384011L0.065009117,20.384011z M23.060013,10.460008C19.480015,10.460008 16.568016,13.368006 16.568016,16.943003 16.568016,20.519001 19.480015,23.426999 23.060013,23.427 26.640011,23.426999 29.55201,20.519001 29.55201,16.943003 29.55201,13.368006 26.640011,10.460008 23.060013,10.460008z M23.060013,8.4600086C27.74201,8.4600086 31.552008,12.266006 31.552008,16.943003 31.552008,21.621 27.74201,25.426998 23.060013,25.426998 18.378014,25.426998 14.568016,21.621 14.568016,16.943003 14.568016,12.266006 18.378014,8.4600086 23.060013,8.4600086z M2.0000181,2L2.0000181,6 23.08602,6 23.08602,2z M2.0000181,0L23.08602,0C24.18902,0,25.08602,0.89699936,25.08602,2L25.08602,6.6599998C24.430019,6.5310001 23.753019,6.4599991 23.060019,6.4599991 21.06402,6.4599991 19.20402,7.0300007 17.614019,8L1.8119812E-05,8 1.8119812E-05,2C1.8066406E-05,0.89699936,0.89701813,0,2.0000181,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
|
||||
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
|
||||
</sys:String>
|
||||
@@ -264,6 +280,7 @@
|
||||
m464.867188 0c-25.988282 0-47.132813 21.144531-47.132813 47.132812v313.75c0 49.324219 11.660156 98.710938 33.71875 142.824219 2.539063 5.082031 7.734375 8.292969 13.414063 8.292969 5.683593 0 10.875-3.210938 13.417968-8.292969 22.054688-44.113281 33.714844-93.503906 33.714844-142.824219v-313.75c0-25.988281-21.144531-47.132812-47.132812-47.132812zm0 30c9.445312 0 17.132812 7.6875 17.132812 17.132812v33.199219h-34.265625v-33.199219c0-9.445312 7.683594-17.132812 17.132813-17.132812zm17.132812 323.46875h-34.265625v-243.136719h34.265625zm-17.132812 105.4375c-8.71875-24.277344-14.1875-49.734375-16.222657-75.4375h32.445313c-2.035156 25.707031-7.507813 51.160156-16.222656 75.4375zm0 0
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.CheckMarkGeometryKey}">
|
||||
F1M9.97498,1.22334L4.6983,9.09834 4.52164,9.09834 0,5.19331 1.27664,3.52165 4.255,6.08833 8.33331,1.52588E-05 9.97498,1.22334z
|
||||
</sys:String>
|
||||
@@ -280,6 +297,10 @@
|
||||
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.MenuOrientationGeometryKey}">
|
||||
M14.997001,19.999038L15.000001,23.499012C15.000001,24.327005,14.327008,25,13.500016,25L9.9970522,25 9.9970522,23.000015 13.000022,23.000015 12.997022,20.001038z M9.000001,19.000006L9.000001,30.000006 30.000001,30.000006 30.000001,19.000006z M2,2L2,23 7.000001,23 7.000001,18.500006C7.000001,17.673006,7.6730013,17.000006,8.500001,17.000006L13.000001,17.000006 13.000001,2z M1.5,0L13.500001,0C14.327001,0,15.000001,0.67300034,15.000001,1.5L15.000001,17.000006 26.956518,17.000006 23.162404,13.207182C22.772376,12.816143 22.772376,12.184205 23.162404,11.793225 23.357907,11.597736 23.613928,11.499992 23.869827,11.499992 24.125725,11.499992 24.381502,11.597736 24.576517,11.793225L25.889371,13.105454 25.866285,12.914201C25.554945,10.547287 24.699391,8.7684691 23.308905,7.6089654 21.974919,6.4954615 20.356436,6.1229606 19.066825,6.0297103L19.059648,6.0292783 20.545799,7.5160289C20.936786,7.9070184 20.936786,8.5390015 20.545799,8.929991 20.350807,9.1249857 20.094815,9.2229831 19.838825,9.2229831 19.582834,9.2229831 19.326843,9.1249857 19.131849,8.929991L15.185988,4.9830971 18.981854,1.1871986C19.371841,0.79620934 20.004819,0.79620934 20.395805,1.1871986 20.785791,1.5771885 20.785791,2.2101717 20.395805,2.601161L18.976535,4.020443 18.999199,4.0214248C20.641604,4.1119318 22.785724,4.567862 24.590892,6.0739603 26.493747,7.660778 27.606404,10.054969 27.91277,13.209869L27.913735,13.221022 29.341699,11.793225C29.536714,11.597736 29.792459,11.499992 30.048328,11.499992 30.304196,11.499992 30.560187,11.597736 30.75569,11.793225 31.146694,12.184205 31.146694,12.816143 30.75569,13.207182L26.962552,17.000006 30.500001,17.000006C31.327001,17.000006,32.000001,17.673006,32.000001,18.500006L32.000001,30.500006C32.000001,31.327005,31.327001,32.000006,30.500001,32.000006L8.500001,32.000006C7.6730013,32.000006,7.000001,31.327005,7.000001,30.500006L7.000001,25 1.5,25C0.67300034,25,0,24.327001,0,23.5L0,1.5C0,0.67300034,0.67300034,0,1.5,0z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
|
||||
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
|
||||
</sys:String>
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
class MarketShellModule : IShellModule {
|
||||
[ImportingConstructor]
|
||||
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model) {
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
|
||||
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub, orders_model.security_auxiliary_model);
|
||||
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -24,7 +26,6 @@ namespace mbts.spark {
|
||||
readonly AnimationManager _animation = new AnimationManager();
|
||||
readonly ObservableCollection<OrderRecordImmutable> _orders = new ObservableCollection<OrderRecordImmutable>();
|
||||
readonly ObservableFilterCollection<OrderRecordImmutable> _working_orders, _working_bids, _working_asks;
|
||||
readonly string _security_text, _security_key_text;
|
||||
readonly Property<double> _bid_price, _ask_price, _last_price, _day_change, _settlement, _session_high, _session_low;
|
||||
readonly Property<bool> _last_price_from_spread;
|
||||
readonly CellProperty<int> _position;
|
||||
@@ -33,48 +34,51 @@ namespace mbts.spark {
|
||||
readonly CellProperty<bool> _inside_bids, _inside_asks;
|
||||
readonly CellProperty<int> _working_bid_volume, _working_ask_volume;
|
||||
readonly CellProperty<double> _working_best_bid, _working_best_ask;
|
||||
readonly CellProperty<string> _working_bids_text, _working_asks_text;
|
||||
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
|
||||
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
|
||||
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
|
||||
PropertyAutoWrapper<uint, uint>
|
||||
_bid_direct_size,
|
||||
_bid_implied_size,
|
||||
_bid_merged_size,
|
||||
_ask_direct_size,
|
||||
_ask_implied_size,
|
||||
_ask_merged_size;
|
||||
double _raw_day_position;
|
||||
string _bid_merged_size_text, _ask_merged_size_text;
|
||||
ulong _bbo_ref, _last_ref;
|
||||
uint? _display_qty_divisor_backing;
|
||||
|
||||
public Row(IBus bus, SecurityMD smd, OrdersModel orders_model) : base(smd) {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_security_text = smd.security.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
string format_price(double price) => security.ContractSpec.format(price);
|
||||
_position = new CellProperty<int>(this, nameof(position), 0);
|
||||
_position = new CellProperty<int>(this, nameof(position), fmt_display_qty_int);
|
||||
_bid_price = backing(nameof(bid_price), double.NaN);
|
||||
_ask_price = backing(nameof(ask_price), double.NaN);
|
||||
_last_price = backing(nameof(last_price), double.NaN);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), 0);
|
||||
_last_size = new CellProperty<uint>(this, nameof(last_size), fmt_display_qty_uint);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_last_price_from_spread = backing(nameof(last_price_from_spread), false);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), 0);
|
||||
_volume = new CellProperty<ulong>(this, nameof(volume), fmt_display_qty_ulong, 0);
|
||||
_inside_bids = new CellProperty<bool>(this, nameof(inside_bids));
|
||||
_inside_asks = new CellProperty<bool>(this, nameof(inside_asks));
|
||||
_working_bid_volume = new CellProperty<int>(this, nameof(working_bid_volume));
|
||||
_working_ask_volume = new CellProperty<int>(this, nameof(working_ask_volume));
|
||||
//
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), default(uint));
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), default(uint));
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), default(uint));
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), default(uint));
|
||||
_bid_direct_size = backing(nameof(bid_direct_size), 0u, convert_display_qty_uint);
|
||||
_bid_implied_size = backing(nameof(bid_implied_size), 0u, convert_display_qty_uint);
|
||||
_bid_merged_size = backing(nameof(bid_merged_size), 0u, convert_display_qty_uint);
|
||||
_ask_direct_size = backing(nameof(ask_direct_size), 0u, convert_display_qty_uint);
|
||||
_ask_implied_size = backing(nameof(ask_implied_size), 0u, convert_display_qty_uint);
|
||||
_ask_merged_size = backing(nameof(ask_merged_size), 0u, convert_display_qty_uint);
|
||||
//
|
||||
_working_orders = new ObservableFilterCollection<OrderRecordImmutable>(_orders, o => o.is_active());
|
||||
_working_bids = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Buy);
|
||||
_working_asks = new ObservableFilterCollection<OrderRecordImmutable>(_working_orders, o => o.Def.Side == Side.Sell);
|
||||
_working_best_bid = new CellProperty<double>(this, nameof(working_best_bid), format_price, double.NaN);
|
||||
_working_best_ask = new CellProperty<double>(this, nameof(working_best_ask), format_price, double.NaN);
|
||||
_working_bids_text = new CellProperty<string>(this, nameof(working_bids_text), string.Empty);
|
||||
_working_asks_text = new CellProperty<string>(this, nameof(working_asks_text), string.Empty);
|
||||
//
|
||||
default_command = new DelegateCommand(_show_ladder);
|
||||
bid_price_click = new DelegateCommand(() => _show_order_ticket(MarketViewView.Columns.Bid));
|
||||
@@ -90,17 +94,45 @@ namespace mbts.spark {
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
attach_prop_changed_cb(_bid_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_direct_size_text))),
|
||||
attach_prop_changed_cb(_bid_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_implied_size_text))),
|
||||
attach_prop_changed_cb(_bid_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(bid_merged_size_text))),
|
||||
attach_prop_changed_cb(_ask_direct_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_direct_size_text))),
|
||||
attach_prop_changed_cb(_ask_implied_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_implied_size_text))),
|
||||
attach_prop_changed_cb(_ask_merged_size, mk_echo_prop_changed_cb<(uint,uint)>(nameof(ask_merged_size_text))),
|
||||
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
|
||||
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position))) {
|
||||
RaisePropertyChanged(nameof(raw_day_position_text));
|
||||
raise_invalidated();
|
||||
}
|
||||
}),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
|
||||
uint convert_display_qty_uint(uint value) =>
|
||||
value.safe_divide(_display_qty_divisor);
|
||||
|
||||
string fmt_display_qty_int(int value) =>
|
||||
value.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
string fmt_display_qty_uint(uint value) =>
|
||||
value.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
string fmt_display_qty_ulong(ulong value) =>
|
||||
value.safe_divide(_display_qty_divisor).ToString();
|
||||
|
||||
PropertyChange<T> mk_echo_prop_changed_cb<T>(params string[] props) =>
|
||||
(oldval, newval) => RaisePropertyChanged(props);
|
||||
|
||||
IDisposable attach_prop_changed_cb<TView, TModel>(PropertyAutoWrapper<TModel, TView> prop, PropertyChange<(TModel, TView)> callback) {
|
||||
prop.changed += callback;
|
||||
return Disposable.Create(() => prop.changed -= callback);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
@@ -109,32 +141,63 @@ namespace mbts.spark {
|
||||
public bool inside_bids => _inside_bids.value;
|
||||
public bool inside_asks => _inside_asks.value;
|
||||
public int position => _position.value;
|
||||
public string position_text => _position.text;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public string raw_day_position_text => SparkUtils.fmt_position(_raw_day_position.safe_divide(_display_qty_divisor));
|
||||
public double last_price => _last_price.Value;
|
||||
public bool last_price_from_spread => _last_price_from_spread.Value;
|
||||
public uint last_size => _last_size.value;
|
||||
public string last_size_text => _last_size.text;
|
||||
// bid price/size
|
||||
public double bid_price => _bid_price.Value;
|
||||
public uint bid_direct_size => _bid_direct_size.Value;
|
||||
public uint bid_implied_size => _bid_merged_size.Value - _bid_direct_size.Value;
|
||||
public uint bid_merged_size => _bid_merged_size.Value;
|
||||
public string bid_merged_size_text => _bid_merged_size_text ?? (_bid_merged_size_text = _size_formatter(_bid_merged_size.Value, bid_implied_size));
|
||||
public uint bid_direct_size => _bid_direct_size.model;
|
||||
public uint bid_implied_size => _bid_merged_size.model - _bid_direct_size.model;
|
||||
public uint bid_merged_size => _bid_merged_size.model;
|
||||
// bid size text
|
||||
public string bid_direct_size_text => _bid_direct_size.view.ToString();
|
||||
public string bid_implied_size_text => _bid_implied_size.view.ToString();
|
||||
public string bid_merged_size_text => _bid_merged_size.view.ToString();
|
||||
// ask price/size
|
||||
public double ask_price => _ask_price.Value;
|
||||
public uint ask_direct_size => _ask_direct_size.Value;
|
||||
public uint ask_implied_size => _ask_merged_size.Value - _ask_direct_size.Value;
|
||||
public uint ask_merged_size => _ask_merged_size.Value;
|
||||
public string ask_merged_size_text => _ask_merged_size_text ?? (_ask_merged_size_text = _size_formatter(_ask_merged_size.Value, ask_implied_size));
|
||||
public uint ask_direct_size => _ask_direct_size.model;
|
||||
public uint ask_implied_size => _ask_implied_size.model;
|
||||
public uint ask_merged_size => _ask_merged_size.model;
|
||||
// ask size text
|
||||
public string ask_direct_size_text => _ask_direct_size.view.ToString();
|
||||
public string ask_implied_size_text => _ask_implied_size.view.ToString();
|
||||
public string ask_merged_size_text => _ask_merged_size.view.ToString();
|
||||
//
|
||||
public ulong volume => _volume.value;
|
||||
public string volume_text => _volume.text;
|
||||
public double settlement => _settlement.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double session_high => _session_high.Value;
|
||||
public int working_bid_volume => _working_bid_volume.value;
|
||||
public int working_ask_volume => _working_ask_volume.value;
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_bid, _order_summarizer.best_working_bid_qty, _order_summarizer.working_bid_qty, security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(_order_summarizer.best_working_ask, _order_summarizer.best_working_ask_qty, _order_summarizer.working_ask_qty, security.ContractSpec);
|
||||
public string working_bids_text => OrderUtils.order_collection_summary(
|
||||
_order_summarizer.best_working_bid,
|
||||
_order_summarizer.best_working_bid_qty.safe_divide(_display_qty_divisor),
|
||||
_order_summarizer.working_bid_qty.safe_divide(_display_qty_divisor),
|
||||
security.ContractSpec);
|
||||
public string working_asks_text => OrderUtils.order_collection_summary(
|
||||
_order_summarizer.best_working_ask,
|
||||
_order_summarizer.best_working_ask_qty.safe_divide(_display_qty_divisor),
|
||||
_order_summarizer.working_ask_qty.safe_divide(_display_qty_divisor),
|
||||
security.ContractSpec);
|
||||
public double working_best_bid => _working_best_bid.value;
|
||||
public double working_best_ask => _working_best_ask.value;
|
||||
// ice props
|
||||
public string market_description => security.market_description();
|
||||
public string cleared_alias => security.cleared_alias();
|
||||
public string product_description => security.product_name();
|
||||
public string hub_alias => security.hub_alias();
|
||||
public string strip_name => security.strip_name();
|
||||
public string mic_code => security.mic_code();
|
||||
// animations
|
||||
public IList<ui.spark.CellAnimation> animations => _animation.get_animations().ToList();
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
// commands
|
||||
public ICommand bid_price_click { get; }
|
||||
public ICommand bid_merged_size_click { get; }
|
||||
public ICommand bid_direct_size_click { get; }
|
||||
@@ -146,27 +209,30 @@ namespace mbts.spark {
|
||||
public ICommand ask_implied_size_click { get; }
|
||||
public ICommand working_asks_click { get; }
|
||||
public ICommand default_command { get; }
|
||||
public string market_description {get;}
|
||||
public string cleared_alias { get; }
|
||||
public string product_description { get; }
|
||||
public string hub_alias { get; }
|
||||
public string strip_name{ get; }
|
||||
public string mic_code { get; }
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
#endregion
|
||||
|
||||
uint _display_qty_divisor => _display_qty_divisor_backing ??
|
||||
(_display_qty_divisor_backing = _orders_model.security_auxiliary_model.get_display_quantity_divisor(security)).Value;
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public void refresh() {
|
||||
bool dirty = false;
|
||||
if (md.md.get_bbo(ref _bbo_ref, out NativePriceSize b, out NativePriceSize a)) {
|
||||
_bid_price.set(b.price);
|
||||
refresh_size(ref b, _bid_direct_size, _bid_merged_size, ref _bid_merged_size_text, nameof(bid_merged_size_text));
|
||||
// don't use the '||' operator - every 'set_new' call must be made
|
||||
// (the operation mustn't terminate the first time a set_new call returns true).
|
||||
dirty |= (
|
||||
_bid_direct_size.set_new(b.direct_size) |
|
||||
_bid_implied_size.set_new(b.implied_size) |
|
||||
_bid_merged_size.set_new(b.size) |
|
||||
_ask_direct_size.set_new(a.direct_size) |
|
||||
_ask_implied_size.set_new(a.implied_size) |
|
||||
_ask_merged_size.set_new(a.size));
|
||||
|
||||
_ask_price.set(a.price);
|
||||
refresh_size(ref a, _ask_direct_size, _ask_merged_size, ref _ask_merged_size_text, nameof(ask_merged_size_text));
|
||||
_inside_bids.on_next_distinct(_bid_price.Value >= _working_best_bid.value, ref dirty);
|
||||
_inside_asks.on_next_distinct(_ask_price.Value <= _working_best_ask.value, ref dirty);
|
||||
}
|
||||
@@ -183,16 +249,6 @@ namespace mbts.spark {
|
||||
_volume.on_next_distinct(md.md.total_volume, ref dirty);
|
||||
if (dirty)
|
||||
invalidated?.Invoke();
|
||||
|
||||
void refresh_size(ref NativePriceSize nps, Property<uint> direct, Property<uint> merged, ref string text, string text_prop) {
|
||||
if (nps.size != merged.Value || nps.direct_size != direct.Value) {
|
||||
merged.set(nps.size);
|
||||
direct.set(nps.direct_size);
|
||||
text = null;
|
||||
dirty = true;
|
||||
RaisePropertyChanged(text_prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(Row other) =>
|
||||
@@ -220,7 +276,7 @@ namespace mbts.spark {
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
security_criteria = SecurityCriteria.from_securities(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
@@ -18,6 +19,9 @@ using static mbts.ui.spark.MarketViewView;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class MarketViewVM : SecurityMDGrid<MarketViewVM.Row, SecurityMD> {
|
||||
static IReadOnlyList<Func<MarketViewVM, IDataGridColumnFilter<Row>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<MarketViewVM, IDataGridColumnFilter<Row>>();
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -37,21 +41,21 @@ namespace mbts.spark {
|
||||
const double working_orders_col_width = 105;
|
||||
IMultiValueConverter price_converter = new ui.spark.converters.TickDisplayConverter();
|
||||
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.Volume, "Volume", r => r.volume_text, nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
|
||||
add_col(Columns.LastSpd, "Spd", r => r.last_price_from_spread ? "S" : "", nameof(Row.last_price_from_spread), DefaultColumnWidths.Side, last_price_from_spread_filter, DataGridFilterKnownTemplates.Regex);
|
||||
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.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_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size_text, 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_text, nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.RawDayPosition, "Raw Day Pos", r => r.raw_day_position_text, nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, 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);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size_text, nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size_text, nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size_text, nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size_text, nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_hl_working_orders_col(Columns.WorkingAsks, working_col_header, nameof(Row.working_asks_text), nameof(Row.working_best_ask), nameof(Row.working_asks_click), working_ask_volume_filter, BTSColors.MarketData.ask, System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.Settlement, "Settlement", r => r.format_price(r.settlement), nameof(Row.settlement), DefaultColumnWidths.Settlement, settlement_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
add_col(Columns.DayChange, "Change", r => r.format_price(r.day_change), nameof(Row.day_change), DefaultColumnWidths.Price, day_change_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
@@ -151,14 +155,16 @@ namespace mbts.spark {
|
||||
working_ask_volume_filter.load_state(filters.working_ask_volume);
|
||||
volume_filter.load_state(filters.volume);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
market_description_filter.load_state(filters.market_description);
|
||||
cleared_alias_filter.load_state(filters.cleared_alias);
|
||||
product_description_filter.load_state(filters.product_description);
|
||||
hub_alias_filter.load_state(filters.hub_alias);
|
||||
strip_name_filter.load_state(filters.strip_name);
|
||||
mic_code_filter.load_state(filters.mic_code);
|
||||
}
|
||||
rows_view.set_sort_descriptions(s.sorting_criterion, columns);
|
||||
}
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, security_filter,
|
||||
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
|
||||
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
|
||||
last_price_filter, last_price_from_spread_filter, last_size_filter, day_change_filter, settlement_filter,
|
||||
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
|
||||
}), DataGridFilterHelper.hook_filters(rows_view, _filters),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
WorkLoop.Start("MarketView", _refresh)
|
||||
);
|
||||
@@ -233,13 +239,15 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MarketDescription : "", nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ClearedAlias : "", nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.ProductName : "", nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.HubAlias : "", nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.StripName : "", nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.security.HasIce ? r.security.Ice.MicCode : "", nameof(Row.mic_code));
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, string> market_description_filter { get; } = new RegexFilterVM<Row>(r => r.market_description, nameof(Row.market_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> cleared_alias_filter { get; } = new RegexFilterVM<Row>(r => r.cleared_alias, nameof(Row.cleared_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> product_description_filter { get; } = new RegexFilterVM<Row>(r => r.product_description, nameof(Row.product_description));
|
||||
public IDataGridColumnPersistentFilter<Row, string> hub_alias_filter { get; } = new RegexFilterVM<Row>(r => r.hub_alias, nameof(Row.hub_alias));
|
||||
public IDataGridColumnPersistentFilter<Row, string> strip_name_filter { get; } = new RegexFilterVM<Row>(r => r.strip_name, nameof(Row.strip_name));
|
||||
public IDataGridColumnPersistentFilter<Row, string> mic_code_filter { get; } = new RegexFilterVM<Row>(r => r.mic_code, nameof(Row.mic_code));
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<Row>> _filters =>
|
||||
_filter_getters.Select(fn => fn(this));
|
||||
|
||||
public FilterStates get_filter_states() {
|
||||
return new FilterStates {
|
||||
|
||||
@@ -48,5 +48,5 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row, MarketViewView.Columns> { }
|
||||
class MarketViewRowView : DIYGrid.HighPerformanceRow<MarketViewVM.Row> { }
|
||||
}
|
||||
@@ -4,10 +4,10 @@ using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark {
|
||||
SecurityCriteria
|
||||
_selected_security_criteria_backing,
|
||||
_sorted_selected_security_criteria_backing;
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
|
||||
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
@@ -48,7 +49,9 @@ namespace mbts.spark {
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
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));
|
||||
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns)) {
|
||||
menu_display = _menu_display_vm.safe_create()
|
||||
};
|
||||
|
||||
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
@@ -63,7 +66,7 @@ namespace mbts.spark {
|
||||
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));
|
||||
_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Select(r => r.security));
|
||||
}
|
||||
return _selected_security_criteria_backing;
|
||||
}
|
||||
@@ -89,7 +92,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
var comp = comp_builder.to_comparer();
|
||||
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
_sorted_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
}
|
||||
}
|
||||
return _sorted_selected_security_criteria_backing;
|
||||
@@ -101,6 +104,8 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
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 };
|
||||
@@ -160,7 +165,8 @@ namespace mbts.spark {
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
})),
|
||||
_menu_display_vm.spark_cmd
|
||||
};
|
||||
//
|
||||
_vm.selection_changed += on_selection_changed;
|
||||
@@ -190,5 +196,8 @@ namespace mbts.spark {
|
||||
_vm.Dispose();
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.proto;
|
||||
using bts.settings;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
@@ -53,6 +54,8 @@ namespace bts.shell {
|
||||
public readonly FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
[ProtoMember(5)]
|
||||
public SparkMenuDisplaySettings menu_display;
|
||||
|
||||
public StartupData() { }
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace bts.shell.time_and_sales {
|
||||
[ProtoContract]
|
||||
class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
public TimeFilterTuple time;
|
||||
[ProtoMember(2)]
|
||||
public string security;
|
||||
[ProtoMember(3)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(4)]
|
||||
public UIntMinMaxState size;
|
||||
// [ProtoMember(5)]
|
||||
// ProtoMember 5 cannot be used further
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public co2.AggressorSide[] aggressor_sides;
|
||||
}
|
||||
|
||||
static class TimeAndSalesFilterStatesExtensions {
|
||||
public static Func<TimeAndSalesRowVM, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<TimeAndSalesRowVM>();
|
||||
pb.add_regex(fs.security, r => r.security.ToString());
|
||||
pb.add_time_filter(fs.time, r => r.time);
|
||||
pb.add_price(fs.price, r => r.price, r => r.security);
|
||||
pb.add_numeric(fs.size, r => r.size);
|
||||
pb.add_discrete_values(fs.aggressor_sides, r => r.aggressor_side);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
|
||||
|
||||
namespace bts.shell.time_and_sales {
|
||||
[ProtoContract]
|
||||
class TimeAndSalesSettings {
|
||||
[ProtoMember(1)]
|
||||
public readonly SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criterion;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
[ProtoMember(5)]
|
||||
public SparkMenuDisplaySettings menu_display;
|
||||
[ProtoMember(6)]
|
||||
public bool use_defaults;
|
||||
|
||||
public TimeAndSalesSettings() { }
|
||||
|
||||
public TimeAndSalesSettings(SecurityCriteria security_criteria) :
|
||||
this(security_criteria, new FilterStates(), null, null) { }
|
||||
|
||||
public TimeAndSalesSettings(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
|
||||
this.security_criteria = security_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
_sorting_criterion = sorting_criterion;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public static TimeAndSalesSettings Default(SecurityCriteria security_criteria) =>
|
||||
new TimeAndSalesSettings(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public TimeAndSalesSettings with(SecurityCriteria security_criteria) =>
|
||||
new TimeAndSalesSettings(security_criteria, filters, grid);
|
||||
|
||||
public static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.col_id(), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings time => new GridColumnSettings(Columns.Time.col_id(), DefaultColumnHeaders.Time, width: DefaultColumnWidths.TimeOfDay);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.col_id(), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings size => new GridColumnSettings(Columns.Size.col_id(), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings aggressor => new GridColumnSettings(Columns.Aggressor.col_id(), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.col_id(), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> default_sorting_criterion {
|
||||
get {
|
||||
yield return new SortingCriteria(time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return time;
|
||||
yield return price;
|
||||
yield return size;
|
||||
yield return aggressor;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
namespace bts.shell.time_and_sales {
|
||||
class TimeAndSalesModel : IDisposable {
|
||||
internal readonly SecurityCriteria security_criteria;
|
||||
|
||||
static readonly TimeSpan _poll_interval = TimeSpan.FromSeconds(0.4);
|
||||
Lazy<IObservable<IReadOnlyList<TradeInfo>>> _trades;
|
||||
IDisposable _dispose;
|
||||
|
||||
public TimeAndSalesModel(IBus bus, MDConsumer consumer, SecurityCriteria security_criteria) {
|
||||
this.security_criteria = security_criteria;
|
||||
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
|
||||
public TimeAndSalesModel(IBus bus, MDConsumer consumer, IObservable<SecurityCriteria> security_criteria) {
|
||||
var curr_channels = new Dictionary<ChannelKey, ChannelConsumer>();
|
||||
_trades = new Lazy<IObservable<IReadOnlyList<TradeInfo>>>(() => Observable.Interval(_poll_interval).Select(_ => {
|
||||
var copy = curr_channels;
|
||||
return copy.Values.SelectMany(c => c.read_trades()).ToList();
|
||||
}));
|
||||
_dispose = security_criteria.subscribe_md_map(bus).Subscribe(
|
||||
_dispose = security_criteria.SelectManyInSeries(sc =>sc.subscribe_md_map(bus)).Subscribe(
|
||||
md_map => {
|
||||
var copy = new Dictionary<ChannelKey, ChannelConsumer>(curr_channels);
|
||||
copy.sync_with_demux(md_map, get_channel_key, get_channel_consumer, update_channel_consumer);
|
||||
@@ -95,4 +94,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.shell.time_and_sales {
|
||||
class TimeAndSalesRowVM : ViewModelBase, DIYGrid.IRowVM, ISecurityRow, IEquatable<TimeAndSalesRowVM> {
|
||||
// key comparer guarentees deterministic sorting
|
||||
// if the rows in the collection have unique row_keys.
|
||||
public static readonly IComparer<TimeAndSalesRowVM> key_comparer =
|
||||
new ComparerBuilder<TimeAndSalesRowVM>(new[] {
|
||||
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
|
||||
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
|
||||
})
|
||||
.to_comparer()
|
||||
.to_safe();
|
||||
|
||||
readonly TradeMessageImmutable _trade;
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
uint? _display_size;
|
||||
|
||||
public TimeAndSalesRowVM(int row_key, SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
|
||||
this.row_key = row_key;
|
||||
this.security = security;
|
||||
_trade = trade;
|
||||
trade_key = trade.Key;
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public int row_key { get; }
|
||||
public uint trade_key { get; }
|
||||
public uint security_key => security.Id;
|
||||
public SecurityImmutable security { get; }
|
||||
public DateTime time => ((ulong)_trade.Time).lldatetime().ToLocalTime();
|
||||
public double price => _trade.price();
|
||||
public uint size => _trade.Size;
|
||||
public uint display_size => _display_size ??
|
||||
(_display_size = size.safe_divide(_get_display_qty_divisor(security))).Value;
|
||||
public AggressorSide aggressor_side => _trade.AggressorSide;
|
||||
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(TimeAndSalesRowVM),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(time),
|
||||
nameof(price),
|
||||
nameof(size),
|
||||
nameof(aggressor_side),
|
||||
nameof(security_key),
|
||||
});
|
||||
|
||||
public bool Equals(TimeAndSalesRowVM other) =>
|
||||
other != null &&
|
||||
row_key == other.row_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as TimeAndSalesRowVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
row_key;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,365 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
|
||||
readonly IBus _bus;
|
||||
readonly TimeAndSalesModel _model;
|
||||
IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
namespace bts.shell.time_and_sales {
|
||||
class TimeAndSalesVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection {
|
||||
const int _max_row_count = 1000;
|
||||
static int _id_counter;
|
||||
static IReadOnlyList<Func<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>> _filter_getters =
|
||||
ObjectSnapshot.property_delegates_of_type<TimeAndSalesVM, IDataGridColumnFilter<TimeAndSalesRowVM>>();
|
||||
|
||||
readonly ColumnSortManager _sort_manager;
|
||||
readonly RowFactory _row_factory = new RowFactory();
|
||||
readonly LoggerContext _log;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
readonly DeferrableActionWrapper
|
||||
_refresh_filter,
|
||||
_refresh_sort,
|
||||
_refresh_selected_rows;
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
IComparer<TimeAndSalesRowVM> _comparer_backing;
|
||||
Func<TimeAndSalesRowVM, bool> _filter_backing;
|
||||
ImmutableHashSet<TimeAndSalesRowVM> _selected_rows = ImmutableHashSet<TimeAndSalesRowVM>.Empty;
|
||||
ImmutableList<TimeAndSalesRowVM> _all_rows = ImmutableList<TimeAndSalesRowVM>.Empty;
|
||||
ImmutableList<TradeInfo> _buffered_trades;
|
||||
/// Although the sorting criteria can be obtained from the <see cref="_sort_manager"/> (<see cref="ColumnSortManager.SortingCriteria"/>),
|
||||
/// a local copy is kept because the settings collector may call <see cref="create"/> from a thread
|
||||
/// other than the dispatcher thread associated with this instance as a viewmodel,
|
||||
/// and the <see cref="ColumnSortManager.SortingCriteria"/> property is not thread-safe.
|
||||
/// Therefore, <see cref="create"/> returns this local copy, and thus avoids accessing the non-thread-safe property.
|
||||
ImmutableList<SortingCriteria> _sorting_criteria = ImmutableList<SortingCriteria>.Empty;
|
||||
TimeAndSalesRowVM _current_row;
|
||||
bool _is_paused;
|
||||
int
|
||||
_load_settings_count,
|
||||
_filter_change_count,
|
||||
_sort_change_count;
|
||||
|
||||
public TimeAndSalesVM(IBus bus, IWindowSettingsManager<GridSettings> settings_manager, TimeAndSalesModel model, StartupData startup_data)
|
||||
: base(RXCollection.CreateBuffer(model.trades, 1000), Enumerable.Empty<SortingCriteria>()) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
public TimeAndSalesVM(IWindowSettingsManager<GridSettings> settings_manager, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
|
||||
_sort_manager = new ColumnSortManager(columns.WrapSelector(c => c as ISortableDIYGridColumn, null), TimeAndSalesView.Columns.RowKey.ToString());
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
_comparer_backing = _sort_manager.BuildComparer(_get_column_comparer);
|
||||
_log = new LoggerContext(Log.Ui, $"Time And Sales VM ({Interlocked.Increment(ref _id_counter)})", null);
|
||||
_settings_manager = settings_manager;
|
||||
show_ladder = DelegateCommandParamObserver.Create(current_row_observable, r => _show_ladder_async(r).ignore(), r => r != null);
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
if (startup_data.filters is FilterStates filters) {
|
||||
// deferrable filter/sort refresh
|
||||
_refresh_filter = new DeferrableActionWrapper(_refresh_filter_impl);
|
||||
_refresh_sort = new DeferrableActionWrapper(_refresh_sort_impl);
|
||||
_refresh_selected_rows = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke(_selected_rows));
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<TimeAndSalesRowVM>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
// Columns
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.Security,
|
||||
r => r.security.ToString(),
|
||||
DefaultColumnHeaders.Security,
|
||||
DefaultColumnWidths.Security,
|
||||
security_filter,
|
||||
DataGridFilterKnownTemplates.Regex);
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.Time,
|
||||
r => r.time.ToString("h:mm:ss tt"),
|
||||
DefaultColumnHeaders.Time,
|
||||
DefaultColumnWidths.Time,
|
||||
time_filter,
|
||||
DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.Price,
|
||||
r => r.security.spec_or_default().format(r.price),
|
||||
DefaultColumnHeaders.Price,
|
||||
DefaultColumnWidths.Price,
|
||||
price_filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
System.Windows.HorizontalAlignment.Right);
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.Size,
|
||||
r => r.display_size.ToString(),
|
||||
DefaultColumnHeaders.Size,
|
||||
DefaultColumnWidths.Size,
|
||||
size_filter,
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
System.Windows.HorizontalAlignment.Right);
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.Aggressor,
|
||||
r => r.aggressor_side.ToString(),
|
||||
DefaultColumnHeaders.Aggressor,
|
||||
DefaultColumnWidths.Aggressor,
|
||||
aggressor_filter,
|
||||
DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(
|
||||
TimeAndSalesView.Columns.SecurityKey,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
security_key_filter,
|
||||
DataGridFilterKnownTemplates.Regex);
|
||||
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager);
|
||||
// commands
|
||||
show_ladder_cmd = new DelegateCommand(() => {
|
||||
if (current_row != null)
|
||||
request_show_ladder?.Invoke(current_row);
|
||||
});
|
||||
// init IDisposable resources
|
||||
var resources = new List<IDisposable>();
|
||||
resources.AddRange(_filters.Select(f => {
|
||||
f.filter_changed += _refresh_filter.execute;
|
||||
return Disposable.Create(() => f.filter_changed -= _refresh_filter.execute);
|
||||
}));
|
||||
_sort_manager.active_sorting_changed += _refresh_sort.execute;
|
||||
resources.Add(Disposable.Create(() => _sort_manager.active_sorting_changed -= _refresh_sort.execute));
|
||||
_dispose = Disposable.Create(resources.ToArray());
|
||||
// local methods
|
||||
void add_column(
|
||||
TimeAndSalesView.Columns col_key,
|
||||
Func<TimeAndSalesRowVM, string> get_cell_text,
|
||||
string header,
|
||||
double width,
|
||||
IDataGridColumnFilter filter,
|
||||
string filter_template,
|
||||
System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<TimeAndSalesRowVM, TimeAndSalesView.Columns>(
|
||||
col_key, get_cell_text, filter, filter_template, _get_aggressor_brush) {
|
||||
id = col_key.col_id(), Header = header, Width = width, horizontal_alignment = h_alignment
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public event Action<ImmutableHashSet<TimeAndSalesRowVM>> selected_rows_changed;
|
||||
public event Action<TimeAndSalesRowVM> current_row_changed, request_show_ladder;
|
||||
|
||||
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
|
||||
public TimeAndSalesRowVM current_row {
|
||||
get => _current_row;
|
||||
set {
|
||||
if (Set(ref _current_row, value))
|
||||
current_row_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
public ObservableCollection<TimeAndSalesRowVM> rows { get; } = new ObservableCollection<TimeAndSalesRowVM>();
|
||||
public ImmutableHashSet<TimeAndSalesRowVM> selected_rows => _selected_rows;
|
||||
public int total_row_count => _all_rows.Count;
|
||||
public SimpleGridSettingsManager<TimeAndSalesRowVM> grid_settings_manager { get; }
|
||||
public bool is_paused {
|
||||
get => _is_paused;
|
||||
set {
|
||||
if (_is_paused != value) {
|
||||
_is_paused = value;
|
||||
if (value) {
|
||||
_buffered_trades = ImmutableList<TradeInfo>.Empty;
|
||||
} else {
|
||||
var resume = _buffered_trades;
|
||||
_buffered_trades = null;
|
||||
on_new_trades(resume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// filters
|
||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<TimeAndSalesRowVM>(r => r.time);
|
||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security.ToString(), nameof(TimeAndSalesRowVM.security));
|
||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<TimeAndSalesRowVM>(r => r.price);
|
||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, UIntMinMaxState> size_filter { get; } = new UIntMinMax<TimeAndSalesRowVM>(r => r.size, nameof(TimeAndSalesRowVM.size));
|
||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<TimeAndSalesRowVM, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side);
|
||||
public IDataGridColumnPersistentFilter<TimeAndSalesRowVM, string> security_key_filter { get; } = new RegexFilterVM<TimeAndSalesRowVM>(r => r.security_key.ToString());
|
||||
// commands
|
||||
public ICommand show_ladder_cmd { get; }
|
||||
|
||||
IComparer<TimeAndSalesRowVM> _comparer {
|
||||
get => _comparer_backing;
|
||||
set {
|
||||
if (value == null) {
|
||||
_log.warn("Comparer should not be set to null, will use the default comparer instead (based on trade time, then row key).");
|
||||
_comparer_backing = new ComparerBuilder<TimeAndSalesRowVM>(new[] {
|
||||
Comparer<DateTime>.Default.wrap<DateTime, TimeAndSalesRowVM>(r => r.time),
|
||||
Comparer<int>.Default.wrap<int, TimeAndSalesRowVM>(r => r.row_key)
|
||||
})
|
||||
.to_comparer()
|
||||
.to_safe();
|
||||
} else {
|
||||
_comparer_backing = value;
|
||||
}
|
||||
var sorted_rows = rows.ToArray();
|
||||
var snapshot = _selected_rows;
|
||||
Array.Sort(sorted_rows, _comparer_backing);
|
||||
using (_refresh_selected_rows.defer_execution()) {
|
||||
for (int i = 0; i < sorted_rows.Length; i++) {
|
||||
if (rows[i].row_key != sorted_rows[i].row_key)
|
||||
rows[i] = sorted_rows[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Func<TimeAndSalesRowVM, bool> _filter {
|
||||
get => _filter_backing;
|
||||
set {
|
||||
var old_filter = _filter_backing;
|
||||
var new_filter = value;
|
||||
_filter_backing = new_filter;
|
||||
if (new_filter != null) {
|
||||
for (int i = 0; i < rows.Count; i++) {
|
||||
if (!new_filter(rows[i]))
|
||||
rows.RemoveAt(i);
|
||||
}
|
||||
rows.RemoveWhere(r => !new_filter(r));
|
||||
if (old_filter != null) {
|
||||
foreach (var r in _all_rows) {
|
||||
if (!old_filter(r) && new_filter(r))
|
||||
rows.InsertSorted(r, _comparer);
|
||||
}
|
||||
}
|
||||
} else if (old_filter != null) {
|
||||
foreach (var r in _all_rows) {
|
||||
if (!old_filter(r))
|
||||
rows.InsertSorted(r, _comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IDataGridColumnFilter<TimeAndSalesRowVM>> _filters =>
|
||||
_filter_getters.Select(fn => fn(this));
|
||||
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting_criteria, GridSettings grid_settings) create() =>
|
||||
(_get_filter_states(), _sorting_criteria, grid_settings_manager.create());
|
||||
|
||||
public void load(FilterStates filters, IEnumerable<SortingCriteria> sorting_criteria, GridSettings grid_settings) {
|
||||
using (_refresh_filter.defer_execution())
|
||||
using (_refresh_sort.defer_execution()) {
|
||||
_log.info($"begin load settings ({++_load_settings_count})...");
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
grid_settings_manager.load_from(grid_settings);
|
||||
_sort_manager.SortingCriteria = sorting_criteria;
|
||||
// load filters
|
||||
time_filter.load_state(filters.time);
|
||||
security_filter.load_state(filters.security);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
aggressor_filter.load_state(filters.aggressor_sides);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
//
|
||||
timer.Stop();
|
||||
_log.info($"load settings ({_load_settings_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
|
||||
}
|
||||
// Columns
|
||||
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, nameof(Row.security), security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, nameof(Row.time), time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
|
||||
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
|
||||
// if the collection is not populated, the column settings will not be applied
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, row_vms);
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
_resources.AddRange(
|
||||
show_ladder,
|
||||
DataGridFilterHelper.hook_filters(
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, security_key_filter),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
|
||||
);
|
||||
rows_view.set_sort_descriptions(startup_data.sorting_criterion, columns);
|
||||
}
|
||||
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
|
||||
|
||||
public GridSettings get_grid_save_state() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return null;
|
||||
else
|
||||
return grid_settings_manager.create();
|
||||
public void on_new_trades(IReadOnlyList<TradeInfo> trades) {
|
||||
/// The total row count is limited by <see cref="_max_row_count"/>;
|
||||
/// trades are kept in a circular buffer (FIFO).
|
||||
/// If paused, just need to add trades to <see cref="_buffered_trades"/>.
|
||||
if (is_paused) {
|
||||
if (_buffered_trades == null)
|
||||
_buffered_trades = ImmutableList<TradeInfo>.Empty;
|
||||
if (trades.Count >= _max_row_count) {
|
||||
_buffered_trades = _buffered_trades
|
||||
.Clear()
|
||||
.AddRange(ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count));
|
||||
} else {
|
||||
int overflow_count = _buffered_trades.Count + trades.Count - _max_row_count;
|
||||
if (overflow_count > 0) {
|
||||
var rows_to_remove = _buffered_trades.WrapSubRange(0, overflow_count);
|
||||
_buffered_trades = _buffered_trades.RemoveRange(0, overflow_count);
|
||||
}
|
||||
_buffered_trades = _buffered_trades.AddRange(trades);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/// If not paused, update in the normal fasion:
|
||||
/// add to <see cref="_all_rows"/> and <see cref="rows"/>
|
||||
if (trades.Count >= _max_row_count) {
|
||||
trades = ReadOnlyListSubRangeWrapper.Create(trades, trades.Count - _max_row_count, _max_row_count);
|
||||
_all_rows = _all_rows.Clear();
|
||||
rows.Clear();
|
||||
} else {
|
||||
int overflow_count = _all_rows.Count + trades.Count - _max_row_count;
|
||||
if (overflow_count > 0) {
|
||||
var rows_to_remove = _all_rows.WrapSubRange(0, overflow_count);
|
||||
_all_rows = _all_rows.RemoveRange(0, overflow_count);
|
||||
rows.RemoveSorted(rows_to_remove, _comparer);
|
||||
}
|
||||
}
|
||||
var all_rows_bd = _all_rows.ToBuilder();
|
||||
foreach (var t in trades) {
|
||||
var r = _row_factory.create(t.security, t.trade, _get_display_qty_divisor);
|
||||
all_rows_bd.Add(r);
|
||||
if (_filter_backing == null || _filter_backing(r))
|
||||
rows.InsertSorted(r, _comparer);
|
||||
}
|
||||
_all_rows = all_rows_bd.ToImmutable();
|
||||
RaisePropertyChanged(nameof(total_row_count));
|
||||
}
|
||||
|
||||
public StartupData create() =>
|
||||
new StartupData(_model.security_criteria, _get_filter_states(), get_grid_save_state(), rows_view.get_sorting_criteria(columns));
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
try {
|
||||
if (e.Column is ISortableDIYGridColumn sc) {
|
||||
_log.info($"handling sort event for column {sc.id}.");
|
||||
_sort_manager.OnColumnClick(sc, KeyboardExtensions.IsShiftDown());
|
||||
var sorted_rows = rows.ToArray();
|
||||
var snapshot = _selected_rows;
|
||||
_refresh_sort_impl();
|
||||
} else {
|
||||
_log.warn($"ignoring sort event because column '{e.Column?.id}' does not implement {nameof(ISortableDIYGridColumn)}.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
_log.error($"error handling sort event: {ex}.");
|
||||
}
|
||||
}
|
||||
|
||||
private Brush _get_aggressor_brush(Row r) {
|
||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
||||
var bd = _selected_rows.ToBuilder();
|
||||
if (e.AddedItems != null)
|
||||
bd.AddRange(e.AddedItems.OfType<TimeAndSalesRowVM>());
|
||||
if (e.RemovedItems != null)
|
||||
bd.removeRange(e.RemovedItems.OfType<TimeAndSalesRowVM>());
|
||||
_selected_rows = bd.ToImmutable();
|
||||
_refresh_selected_rows.execute();
|
||||
}
|
||||
|
||||
static IComparer<TimeAndSalesRowVM> _get_column_comparer(string column_name) =>
|
||||
_get_column_comparer(EnumHelper.parse<TimeAndSalesView.Columns>(column_name));
|
||||
|
||||
static IComparer<TimeAndSalesRowVM> _get_column_comparer(TimeAndSalesView.Columns column) {
|
||||
switch (column) {
|
||||
case TimeAndSalesView.Columns.Aggressor: return wrap(r => r.aggressor_side);
|
||||
case TimeAndSalesView.Columns.Time: return wrap(r => r.time);
|
||||
case TimeAndSalesView.Columns.Security: return wrap(r => r.security);
|
||||
case TimeAndSalesView.Columns.Price: return wrap(r => r.price);
|
||||
case TimeAndSalesView.Columns.Size: return wrap(r => r.size);
|
||||
case TimeAndSalesView.Columns.SecurityKey: return wrap(r => r.security_key);
|
||||
case TimeAndSalesView.Columns.RowKey: return wrap(r => r.row_key);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(column, nameof(column));
|
||||
}
|
||||
IComparer<TimeAndSalesRowVM> wrap<T>(Func<TimeAndSalesRowVM, T> selector) =>
|
||||
Comparer<T>.Default.wrap(selector);
|
||||
}
|
||||
|
||||
Brush _get_aggressor_brush(TimeAndSalesRowVM r) {
|
||||
BTSColor c;
|
||||
Brush b;
|
||||
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
|
||||
@@ -90,14 +369,6 @@ namespace mbts.spark {
|
||||
return b;
|
||||
}
|
||||
|
||||
#region Filters
|
||||
public IDataGridColumnPersistentFilter<Row, TimeFilterTuple> time_filter { get; } = new TimeFilterVM<Row>(r => r.time, nameof(Row.time));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> size_filter { get; } = new UIntMinMax<Row>(r => r.size, nameof(Row.size));
|
||||
public IDataGridColumnPersistentFilter<Row, AggressorSide[]> aggressor_filter { get; } = DiscreteValuesFilterVM.Create<Row, AggressorSide>(EnumHelper.get_values<AggressorSide>().ToArray(), r => r.aggressor_side, nameof(Row.aggressor_side));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
FilterStates _get_filter_states() =>
|
||||
new FilterStates {
|
||||
time = time_filter.get_state(),
|
||||
@@ -107,73 +378,35 @@ namespace mbts.spark {
|
||||
security_key = security_key_filter.get_state(),
|
||||
aggressor_sides = aggressor_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() {
|
||||
yield return new SortingCriteria(nameof(Row.time), false);
|
||||
void _refresh_sort_impl() {
|
||||
_log.info($"begin change sort ({++_sort_change_count})...");
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
_comparer = _sort_manager.BuildComparer(_get_column_comparer, TimeAndSalesRowVM.key_comparer);
|
||||
_sorting_criteria = ImmutableList.CreateRange(_sort_manager.SortingCriteria);
|
||||
timer.Stop();
|
||||
_log.info($"sort change ({_sort_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
|
||||
}
|
||||
|
||||
protected override Row row_factory(TradeInfo row_model) =>
|
||||
new Row(this, row_model.security, row_model.trade);
|
||||
|
||||
|
||||
async Task _show_ladder_async(Row row) {
|
||||
var smd_lookup = await SparkUtils.get_security_md_async(_bus, row.security.Id);
|
||||
if (smd_lookup.success) _bus.post(SparkTags.ShowLadderTag, smd_lookup.result);
|
||||
void _refresh_filter_impl() {
|
||||
_log.info($"begin change filter ({++_filter_change_count})...");
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
_filter = _get_filter_states().build_predicate();
|
||||
timer.Stop();
|
||||
_log.info($"filter change ({_filter_change_count}) took {timer.ElapsedMilliseconds:0.00}ms.");
|
||||
}
|
||||
|
||||
public class Row : SecurityRow, DIYGrid.IRowVM {
|
||||
readonly uint _trade_key;
|
||||
class RowFactory {
|
||||
// the row key must be unique to each row in the collection
|
||||
int _row_id_counter;
|
||||
|
||||
readonly string
|
||||
_security_text,
|
||||
_time_text,
|
||||
_price_text,
|
||||
_size_text,
|
||||
_aggressor_text,
|
||||
_trade_key_text,
|
||||
_security_key_text;
|
||||
public TimeAndSalesRowVM create(TradeInfo trade_info, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
|
||||
new TimeAndSalesRowVM(_row_id_counter++, trade_info.security, trade_info.trade, get_display_qty_divisor);
|
||||
|
||||
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
|
||||
_trade_key = trade.Key;
|
||||
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
|
||||
price = trade.price();
|
||||
size = trade.Size;
|
||||
aggressor_side = trade.AggressorSide;
|
||||
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
|
||||
_security_text = security.ToString();
|
||||
_time_text = time.ToString("M/d h:mm:ss tt");
|
||||
_price_text = security.ContractSpec.format(price);
|
||||
_size_text = size.ToString();
|
||||
_aggressor_text = aggressor_side.ToString();
|
||||
_trade_key_text = _trade_key.ToString();
|
||||
_security_key_text = security_key.ToString();
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public uint trade_key => _trade_key;
|
||||
public DateTime time { get; }
|
||||
public double price { get; }
|
||||
public uint size { get; }
|
||||
public AggressorSide aggressor_side { get; }
|
||||
public ICommand show_ladder { get; }
|
||||
|
||||
public string this[TimeAndSalesView.Columns col] {
|
||||
get {
|
||||
switch (col) {
|
||||
case TimeAndSalesView.Columns.Security: return _security_text;
|
||||
case TimeAndSalesView.Columns.Time: return _time_text;
|
||||
case TimeAndSalesView.Columns.Price: return _price_text;
|
||||
case TimeAndSalesView.Columns.Size: return _size_text;
|
||||
case TimeAndSalesView.Columns.Aggressor: return _aggressor_text;
|
||||
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
public TimeAndSalesRowVM create(SecurityImmutable security, TradeMessageImmutable trade, Func<SecurityImmutable?, uint> get_display_qty_divisor) =>
|
||||
new TimeAndSalesRowVM(_row_id_counter++, security, trade, get_display_qty_divisor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.TimeAndSalesView"
|
||||
x:Class="bts.shell.time_and_sales.TimeAndSalesView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:tas="clr-namespace:bts.shell.time_and_sales"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
@@ -26,13 +26,14 @@
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
Rows="{Binding rows_view}"
|
||||
Rows="{Binding rows}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding show_ladder_cmd}"/>
|
||||
<KeyBinding Key="Return" Command="{Binding show_ladder_cmd}"/>
|
||||
<KeyBinding Key="A" Modifiers="Ctrl" Command="{Binding select_all_cmd}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
@@ -43,13 +44,18 @@
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:TimeAndSalesRowView/>
|
||||
<tas:TimeAndSalesRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock Text=" "/>
|
||||
<TextBlock>
|
||||
<Run Text="Trades:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
namespace bts.shell.time_and_sales {
|
||||
/// <summary>
|
||||
/// Interaction logic for TimeAndSalesView.xaml
|
||||
/// </summary>
|
||||
partial class TimeAndSalesView : UserControl {
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, SecurityKey, Aggressor }
|
||||
public enum Columns { Time, Security, Price, Size, SecurityKey, Aggressor, RowKey }
|
||||
|
||||
public TimeAndSalesView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesVM.Row, TimeAndSalesView.Columns> { }
|
||||
}
|
||||
class TimeAndSalesRowView : DIYGrid.HighPerformanceRow<TimeAndSalesRowVM> { }
|
||||
|
||||
static class TimeAndSalesViewColumnsExtensions {
|
||||
public static string col_id(this TimeAndSalesView.Columns column) =>
|
||||
column.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,125 +1,228 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.api;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
namespace bts.shell.time_and_sales {
|
||||
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
SingleValuePump<TimeAndSalesVM> _vm;
|
||||
readonly bool _can_pause_updates;
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
TimeAndSalesVM _vm;
|
||||
TimeAndSalesModel _model;
|
||||
StartupData _state;
|
||||
SecurityCriteria _security_criteria_backing;
|
||||
TimeAndSalesSettings _state;
|
||||
TimeAndSalesView _view;
|
||||
SparkCommand[] _commands;
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
IDisposable _subscriptions;
|
||||
|
||||
public TimeAndSalesWindow(TimeAndSalesWindowFactory factory, IBus bus, MDConsumer consumer, StartupData state, ImageSource icon, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
public TimeAndSalesWindow(
|
||||
TimeAndSalesWindowFactory factory,
|
||||
IBus bus,
|
||||
MDConsumer consumer,
|
||||
TimeAndSalesSettings state,
|
||||
ImageSource icon,
|
||||
IWindowSettingsManager<GridSettings> settings_manager,
|
||||
bool can_pause_updates,
|
||||
Func<SecurityImmutable?, uint> get_display_qty_divisor)
|
||||
: base("Time And Sales", bus, factory, SparkCommands.ShowTimeAndSales.icon) {
|
||||
Log.assert(state != null);
|
||||
_consumer = consumer;
|
||||
_factory = factory;
|
||||
_state = state;
|
||||
_state = state ?? TimeAndSalesSettings.Default(new SecurityCriteria());
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
if (_state.security_criteria == null)
|
||||
_state = _state.with(new SecurityCriteria());
|
||||
_security_criteria_backing = _state.security_criteria;
|
||||
_settings_manager = settings_manager;
|
||||
_can_pause_updates = can_pause_updates;
|
||||
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().time_and_sales ? _window_type : null;
|
||||
Icon.Freeze();
|
||||
}
|
||||
|
||||
event Action<SecurityCriteria> _security_criteria_changed;
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override UIElement Content => _view;
|
||||
public override double MinWidth => StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
public override double MinWidth => TimeAndSalesSettings.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 override StartupData SaveState => _state = _vm.value.create();
|
||||
public override TimeAndSalesSettings SaveState {
|
||||
get {
|
||||
_ensure_state(true);
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
SecurityCriteria _security_criteria {
|
||||
get => _security_criteria_backing;
|
||||
set {
|
||||
_security_criteria_backing = value;
|
||||
_state = null;
|
||||
_security_criteria_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose() =>
|
||||
_dispose_resources();
|
||||
|
||||
public void thread_init() {
|
||||
_dispose_resources();
|
||||
_vm = new SingleValuePump<TimeAndSalesVM>();
|
||||
var connectable_builder = new ConnectableSeriesBuilder<TimeAndSalesVM>(_vm.observable);
|
||||
_ensure_state();
|
||||
if (!_state.use_defaults)
|
||||
_settings_manager.instance_settings = _state.grid;
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
_vm = new TimeAndSalesVM(_settings_manager, _get_display_qty_divisor);
|
||||
var selected_rows_src = Observable.Create<ImmutableHashSet<TimeAndSalesRowVM>>(o => {
|
||||
_vm.selected_rows_changed += o.OnNext;
|
||||
return Disposable.Create(() => _vm.selected_rows_changed -= o.OnNext);
|
||||
});
|
||||
var current_row_src = Observable.Create<TimeAndSalesRowVM>(o => {
|
||||
if (_vm.current_row != null)
|
||||
o.OnNext(_vm.current_row);
|
||||
_vm.current_row_changed += o.OnNext;
|
||||
return Disposable.Create(() => _vm.current_row_changed -= o.OnNext);
|
||||
});
|
||||
var current_sec_key_src = current_row_src.Select(r => {
|
||||
var result = ImmutableHashSet<uint>.Empty;
|
||||
if (r != null)
|
||||
result = result.Add(r.security_key);
|
||||
return result;
|
||||
});
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
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();
|
||||
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
|
||||
SparkCommand toggle_is_paused_cmd = null;
|
||||
toggle_is_paused_cmd = new SparkCommandCommand(get_is_paused_cmd_title(), Icons.Pause, new DelegateCommand(() => {
|
||||
_vm.is_paused = !_vm.is_paused;
|
||||
toggle_is_paused_cmd.title = get_is_paused_cmd_title();
|
||||
})) {
|
||||
show_in_toolbar = _can_pause_updates,
|
||||
show_in_context_menu = _can_pause_updates
|
||||
};
|
||||
_commands = new SparkCommand[] {
|
||||
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)),
|
||||
// csv export
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
|
||||
// ladder
|
||||
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
|
||||
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
|
||||
// ticket
|
||||
SparkCommands.ShowOrderTicketAutoLookup(SparkCommands.KeyGestures.ShowOrderTicket)
|
||||
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
|
||||
// fills
|
||||
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills)
|
||||
.complete(selected_rows_src, always_true, get_fills_startup_data),
|
||||
// mview
|
||||
SparkCommands.ShowMarketView
|
||||
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria),
|
||||
// pause
|
||||
toggle_is_paused_cmd,
|
||||
// change securities
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
s => s.time_and_sales_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
s => _vm.value.grid_settings_manager.create_vm(
|
||||
s => _vm.grid_settings_manager.create_vm(
|
||||
// we need to get the most up to date GridSettings when launching b/c users may
|
||||
// have changed some settings on the grid directly, like column widths.
|
||||
s.with(_vm.value.columns.Select(c => c.create()))
|
||||
s.with(_vm.columns.Select(c => c.create()))
|
||||
)
|
||||
);
|
||||
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
})),
|
||||
_menu_display_vm.spark_cmd
|
||||
};
|
||||
_subscriptions = Disposable.Create(
|
||||
_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(update_criteria),
|
||||
connectable_builder.connect_all());
|
||||
_initialize();
|
||||
|
||||
_vm.load(_state.filters, _state.sorting_criterion, _state.grid);
|
||||
_vm.request_show_ladder += show_ladder;
|
||||
var security_criteria_src = _bus.subscribe(update_securities_cmd.tag);
|
||||
var subs = new List<IDisposable> {
|
||||
security_criteria_src.Subscribe(sc => _security_criteria = sc),
|
||||
Disposable.Create(() => _vm.request_show_ladder -= show_ladder)
|
||||
};
|
||||
_model = new TimeAndSalesModel(_bus, _consumer, Observable.Create<SecurityCriteria>(o => {
|
||||
o.OnNext(_security_criteria);
|
||||
return security_criteria_src.Subscribe(o);
|
||||
}));
|
||||
subs.Add(_model.trades.OnDispatcher().Subscribe(_vm.on_new_trades));
|
||||
_subscriptions = Disposable.Create(subs.ToArray());
|
||||
if (_view == null)
|
||||
_view = new TimeAndSalesView();
|
||||
_view.DataContext = _vm;
|
||||
// local methods
|
||||
bool always_true<T>(T _) => true;
|
||||
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
|
||||
|
||||
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
|
||||
new FillsSettings(new FillsFilterStates {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
security = rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria
|
||||
});
|
||||
void update_criteria(SecurityCriteria c) {
|
||||
_state = _vm.value.create().with(c);
|
||||
_initialize();
|
||||
|
||||
string get_is_paused_cmd_title() =>
|
||||
$"{(_vm.is_paused ? "Resume" : "Pause")} Updates";
|
||||
|
||||
void show_ladder(TimeAndSalesRowVM row) {
|
||||
if (row != null)
|
||||
_bus.post(SparkTags.ShowLadderAutoLookupTag, ImmutableHashSet.Create(new[] { row.security_key }));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
|
||||
void _dispose_resources() {
|
||||
Disposable.dispose(ref _subscriptions);
|
||||
Disposable.dispose(ref _vm);
|
||||
Disposable.dispose(ref _model);
|
||||
if (_commands != null) {
|
||||
_commands.OfType<IDisposable>().DisposeAll();
|
||||
_commands = null;
|
||||
}
|
||||
Disposable.dispose(ref _model);
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
|
||||
void _initialize() {
|
||||
Disposable.dispose(ref _model);
|
||||
_model = new TimeAndSalesModel(_bus, _consumer, _state.security_criteria);
|
||||
// the previous _vm.value is automatically disposed when _vm.value is set - no need to dispose it here
|
||||
_vm.value = new TimeAndSalesVM(_bus, _settings_manager, _model, _state);
|
||||
if (_view == null)
|
||||
_view = new TimeAndSalesView();
|
||||
_view.DataContext = _vm.value;
|
||||
void _ensure_state(bool force_recreate = false) {
|
||||
if (_state == null || force_recreate) {
|
||||
if (_vm == null) {
|
||||
_state = TimeAndSalesSettings.Default(_security_criteria);
|
||||
} else {
|
||||
var (filters, sorting, grid) = _vm.create();
|
||||
_state = new TimeAndSalesSettings(_security_criteria, filters, grid, sorting.ToList()) {
|
||||
use_defaults = _settings_manager.instance_settings == null
|
||||
};
|
||||
}
|
||||
// should be safe to assume _menu_display_vm and _vm will either both be null, or both be non null,
|
||||
// but redundant null checks don't hurt, and just may prevent a crash.
|
||||
if (_menu_display_vm != null)
|
||||
_state.menu_display = _menu_display_vm.safe_create();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,35 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts.shell.time_and_sales;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.TimeAndSalesView;
|
||||
using Columns = bts.shell.time_and_sales.TimeAndSalesView.Columns;
|
||||
using mbts.config;
|
||||
|
||||
namespace bts.shell {
|
||||
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesWindowFactory.StartupData> {
|
||||
class TimeAndSalesWindowFactory : ShellWindowFactory<TimeAndSalesSettings> {
|
||||
readonly IBus _bus;
|
||||
readonly MDConsumer _consumer;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly TimeAndSalesConfig _time_and_sales_conf;
|
||||
readonly ISecurityAuxiliaryDataModel _security_auxiliary_model;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub) : base(module, "TimeAndSales") {
|
||||
public TimeAndSalesWindowFactory(IShellModule module, IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, ISecurityAuxiliaryDataModel security_auxiliary_model)
|
||||
: base(module, "TimeAndSales") {
|
||||
_bus = bus;
|
||||
_consumer = consumer;
|
||||
_settings_hub = settings_hub;
|
||||
|
||||
_security_auxiliary_model = security_auxiliary_model;
|
||||
_time_and_sales_conf = new TimeAndSalesConfig(Configuration.get<bts.config.TimeAndSalesOptions>());
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
|
||||
@@ -36,91 +38,22 @@ namespace bts.shell {
|
||||
|
||||
public static string col_id(Columns col) => col.ToString();
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
public override Task<IShellWindow> load(TimeAndSalesSettings state) =>
|
||||
Task.FromResult<IShellWindow>(new TimeAndSalesWindow(this, _bus, _consumer, state, SparkCommands.PickTimeAndSales.icon,
|
||||
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g))));
|
||||
new WindowSettingsManager<GridSettings>(_settings_hub, state.grid, s => s.time_and_sales_grid, (s, g) => s.with(time_and_sales:g)),
|
||||
_time_and_sales_conf.can_pause_updates,
|
||||
_security_auxiliary_model.get_display_quantity_divisor));
|
||||
|
||||
public async void show(SecurityCriteria criteria) {
|
||||
var w = await load(new StartupData(criteria));
|
||||
var w = await load(new TimeAndSalesSettings(criteria));
|
||||
_bus.post(SparkTags.ShowWindow, w);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class StartupData {
|
||||
[ProtoMember(1)]
|
||||
public readonly SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
List<SortingCriteria> _sorting_criterion;
|
||||
[ProtoMember(3)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
readonly struct TimeAndSalesConfig {
|
||||
public readonly bool can_pause_updates;
|
||||
|
||||
public StartupData() { }
|
||||
|
||||
public StartupData(SecurityCriteria security_criteria) :
|
||||
this(security_criteria, new FilterStates(), null, null) { }
|
||||
|
||||
public StartupData(SecurityCriteria security_criteria, FilterStates filters, GridSettings grid = default, List<SortingCriteria> sorting_criterion = null) {
|
||||
this.security_criteria = security_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
_sorting_criterion = sorting_criterion;
|
||||
}
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
public List<SortingCriteria> sorting_criterion => _sorting_criterion ?? (_sorting_criterion = DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public static StartupData Default(SecurityCriteria security_criteria) =>
|
||||
new StartupData(security_criteria, new FilterStates(), null, DefaultColumns.default_sorting_criterion.ToList());
|
||||
|
||||
public StartupData with(SecurityCriteria security_criteria) =>
|
||||
new StartupData(security_criteria, filters, grid);
|
||||
|
||||
public static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings time => new GridColumnSettings(col_id(Columns.Time), DefaultColumnHeaders.Time, width: DefaultColumnWidths.Time);
|
||||
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings size => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
|
||||
public static GridColumnSettings aggressor => new GridColumnSettings(col_id(Columns.Aggressor), DefaultColumnHeaders.Aggressor, width: DefaultColumnWidths.Aggressor);
|
||||
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(time.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return time;
|
||||
yield return price;
|
||||
yield return size;
|
||||
yield return aggressor;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
public TimeFilterTuple time;
|
||||
[ProtoMember(2)]
|
||||
public string security;
|
||||
[ProtoMember(3)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(4)]
|
||||
public UIntMinMaxState size;
|
||||
// [ProtoMember(5)]
|
||||
// ProtoMember 5 cannot be used further
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public co2.AggressorSide[] aggressor_sides;
|
||||
public TimeAndSalesConfig(bts.config.TimeAndSalesOptions options) =>
|
||||
can_pause_updates = options.can_pause_updates;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,24 +549,48 @@
|
||||
</u:BindingUpdateGroupCollection>
|
||||
</u:Behavior.BindingUpdateGroups>
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
<StackPanel
|
||||
x:Name="PART_TickFactors_Label"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks"
|
||||
IsEnabled="True"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<c:TickFactorsPicker
|
||||
>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Ticks ("
|
||||
/>
|
||||
<CheckBox
|
||||
Margin="1,0,0,0"
|
||||
IsChecked="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Padding="0"
|
||||
>
|
||||
fractional
|
||||
</CheckBox>
|
||||
<TextBlock VerticalAlignment="Center" Margin="1,0,0,0">)</TextBlock>
|
||||
</StackPanel>
|
||||
<Grid
|
||||
x:Name="PART_TickFactors_Picker"
|
||||
Grid.Column="3"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="True"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
>
|
||||
<c:TickFactorsPicker
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static cv:BooleanToVisibilityConverter.HiddenInstance}}"
|
||||
/>
|
||||
<c:TickUpDownInt
|
||||
HorizontalAlignment="Stretch"
|
||||
Minimum="1"
|
||||
Value="{Binding IntegerTickSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{Binding UseFractionalTicks, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource FalseVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
x:Name="PART_Pricing_Label"
|
||||
Grid.Column="4"
|
||||
|
||||
@@ -52,6 +52,26 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region UseFractionalTicks
|
||||
public static readonly DependencyProperty UseFractionalTicksProperty = WPFHelper.create_dp<bool, ExecutionStrategyEditor>(
|
||||
nameof(UseFractionalTicks), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, true);
|
||||
|
||||
public bool UseFractionalTicks {
|
||||
get => (bool)GetValue(UseFractionalTicksProperty);
|
||||
set => SetValue(UseFractionalTicksProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IntegerTickSize
|
||||
public static readonly DependencyProperty IntegerTickSizeProperty = WPFHelper.create_dp<int, ExecutionStrategyEditor>(
|
||||
nameof(IntegerTickSize), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 1);
|
||||
|
||||
public int IntegerTickSize {
|
||||
get => (int)GetValue(IntegerTickSizeProperty);
|
||||
set => SetValue(IntegerTickSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace bts.shell {
|
||||
|
||||
async void _show_synthetic_create(SecurityCriteria security_criteria) {
|
||||
var found_securities = await security_criteria.securities(_bus);
|
||||
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Count > 0);
|
||||
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Length > 0);
|
||||
if (!respect_clause_security_ordering_is_possible) {
|
||||
_show_synthetic_create(found_securities.Values.ToList());
|
||||
} else {
|
||||
|
||||
@@ -114,6 +114,8 @@ namespace bts.shell {
|
||||
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
|
||||
|
||||
public static QuoteTimeBasedThrottle QuoteTimeBasedThrottle => new QuoteTimeBasedThrottle { DelayToImprove = 0, DelayToRun = 0 };
|
||||
#endregion
|
||||
|
||||
#region PreHedge
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
|
||||
</Grid>
|
||||
|
||||
@@ -20,5 +20,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row, SyntheticManagementView.Columns> { }
|
||||
class SyntheticManagementRowView : DIYGrid.HighPerformanceRow<SyntheticManagementDialogVM.Row> { }
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.spreader;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -27,6 +28,7 @@ namespace mbts.spark {
|
||||
StartupData _state;
|
||||
OrdersModel _orders_model;
|
||||
SyntheticManagementView _view;
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
IDisposable _subscriptions;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -43,7 +45,7 @@ namespace mbts.spark {
|
||||
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 StartupData SaveState => _state = _vm.value.create().with(menu_display: _menu_display_vm.safe_create());
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
@@ -53,6 +55,8 @@ namespace mbts.spark {
|
||||
|
||||
public void thread_init() {
|
||||
Log.notnull(_state);
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
|
||||
var vm_connectable_builder = new ConnectableSeriesBuilder<SyntheticManagementDialogVM>(_vm.observable);
|
||||
var current_row_source = vm_connectable_builder.create(vm => vm.current_row_observable);
|
||||
@@ -75,7 +79,7 @@ namespace mbts.spark {
|
||||
), 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))
|
||||
security_criteria = SecurityCriteria.from_securities(_vm.value.row_vms.Select(r => r.security))
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
@@ -97,7 +101,8 @@ namespace mbts.spark {
|
||||
new SparkCommandCommand(
|
||||
"Import Synthetics...", Icons.Import, FileDialogCommand.open_file(import, extensions: SyntheticSecurityParser.file_extension)) {
|
||||
show_in_toolbar = false
|
||||
}
|
||||
},
|
||||
_menu_display_vm.spark_cmd
|
||||
};
|
||||
|
||||
_subscriptions = Disposable.Create(
|
||||
@@ -129,6 +134,9 @@ namespace mbts.spark {
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
|
||||
struct RowModificationParams {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly bool has_working_orders;
|
||||
|
||||
@@ -48,14 +48,28 @@ namespace bts.shell {
|
||||
public readonly FilterStates filters;
|
||||
[ProtoMember(3)]
|
||||
public readonly GridSettings grid;
|
||||
[ProtoMember(4)]
|
||||
public readonly SparkMenuDisplaySettings menu_display;
|
||||
|
||||
public StartupData() : this(new List<SortingCriteria>(), null, null) { }
|
||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
|
||||
|
||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default)
|
||||
: this(sorting_criteria, filters, grid, SparkMenuDisplaySettings.Default) { }
|
||||
|
||||
public StartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid, SparkMenuDisplaySettings menu_display) {
|
||||
this.sorting_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.grid = grid;
|
||||
this.menu_display = menu_display;
|
||||
}
|
||||
|
||||
public StartupData with(List<SortingCriteria> sorting_criteria = null, FilterStates filters = null, GridSettings grid = null, SparkMenuDisplaySettings? menu_display = null) =>
|
||||
new StartupData(
|
||||
sorting_criteria ?? this.sorting_criteria,
|
||||
filters ?? this.filters,
|
||||
grid ?? this.grid,
|
||||
menu_display ?? this.menu_display);
|
||||
|
||||
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
|
||||
|
||||
static class DefaultColumns {
|
||||
|
||||
@@ -10,13 +10,13 @@ namespace mbts.spark {
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.EnableHedging;
|
||||
leg_tup.leg_params.SendHedge is null || leg_tup.leg_params.SendHedge.Val;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
|
||||
leg_tup.leg_params.EnableHedging = value;
|
||||
|
||||
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
|
||||
!leg_tups.Any(lt => lt.leg_params.EnableHedging);
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (leg_tup.leg_params.SendHedge is null)
|
||||
leg_tup.leg_params.SendHedge = new bts.spark.NullableBoolean();
|
||||
leg_tup.leg_params.SendHedge.Val = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class LeanOffsetTicksModel : RowModelBase<int> {
|
||||
public override string display_name { get; } = "Lean Offset Ticks";
|
||||
public override string help_text { get; } = "Improvement (in ticks) to the lean market";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams?.LeanOffsetTicks ?? 0;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
if (leg_tup.leg_params.QuoteParams is null)
|
||||
leg_tup.leg_params.QuoteParams = new bts.spark.QuoteParams();
|
||||
leg_tup.leg_params.QuoteParams.LeanOffsetTicks = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteTimeThrottleDelayToImproveModel : RowModelBase<uint> {
|
||||
readonly IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Delay to Improve (ms)";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.TimeThrottle };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_throttle(leg_tup).DelayToImprove = _value_from_store(leg_tup);
|
||||
|
||||
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle ?
|
||||
throttle.DelayToImprove :
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle)
|
||||
throttle.DelayToImprove = value;
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_throttle(leg_tup) != null;
|
||||
|
||||
QuoteTimeBasedThrottle _get_throttle(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.TimeBasedThrottle;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteTimeBasedThrottle.DelayToImprove);
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteTimeThrottleDelayToRunModel : RowModelBase<uint> {
|
||||
readonly IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Delay To Run (ms)";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.TimeThrottle };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_throttle(leg_tup).DelayToRun = _value_from_store(leg_tup);
|
||||
|
||||
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle ?
|
||||
throttle.DelayToRun :
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_throttle(leg_tup) is QuoteTimeBasedThrottle throttle)
|
||||
throttle.DelayToRun = value;
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_throttle(leg_tup) != null;
|
||||
|
||||
QuoteTimeBasedThrottle _get_throttle(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.TimeBasedThrottle;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.QuoteTimeBasedThrottle.DelayToRun);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using bts.shell;
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteTimeThrottleEnabledModel : RowModelBase<bool> {
|
||||
readonly QuoteTimeThrottleDelayToRunModel _delay_to_run;
|
||||
readonly QuoteTimeThrottleDelayToImproveModel _delay_to_improve;
|
||||
|
||||
public QuoteTimeThrottleEnabledModel(QuoteTimeThrottleDelayToRunModel delay_to_run, QuoteTimeThrottleDelayToImproveModel delay_to_improve) {
|
||||
_delay_to_run = delay_to_run;
|
||||
_delay_to_improve = delay_to_improve;
|
||||
}
|
||||
|
||||
public override string display_name { get; } = string.Empty;
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.TimeThrottle };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.TimeBasedThrottle != null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.QuoteParams.TimeBasedThrottle == null) {
|
||||
leg_tup.leg_params.QuoteParams.TimeBasedThrottle = SyntheticDataHelper.Defaults.QuoteTimeBasedThrottle;
|
||||
_delay_to_run.apply_value(leg_tup);
|
||||
_delay_to_improve.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.QuoteParams.TimeBasedThrottle = null;
|
||||
}
|
||||
_delay_to_run.set_enabled(leg_tup, value);
|
||||
_delay_to_improve.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,6 +108,10 @@ namespace mbts.spark {
|
||||
"Reduces re-quoting updates by defining a region on either side " +
|
||||
"of the working quoting order, inside which quote updates will not be sent.");
|
||||
|
||||
public static ParameterSection TimeThrottle =>
|
||||
new ParameterSection("Time Throttle", true,
|
||||
"Reduces re-quoting updates by requiring a minimum amount of time to elapse between quotes.");
|
||||
|
||||
public static ParameterSection Conditional =>
|
||||
new ParameterSection("Conditional", true,
|
||||
"Reduces re-quoting updates by only quoting if N ticks away from the opposite " +
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.collections;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -30,8 +29,16 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _has_working_orders, _can_add_leg;
|
||||
|
||||
Security _synthetic;
|
||||
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
|
||||
bool
|
||||
_set_name_automatically,
|
||||
_set_ticks_automatically,
|
||||
_set_quantities_automatically,
|
||||
_set_price_mults_automatically,
|
||||
_is_setting_quantities,
|
||||
_is_setting_price_mults,
|
||||
_use_fractional_ticks = true;
|
||||
TickFactors _tick_factors;
|
||||
int _integer_tick_size = 1;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
|
||||
@@ -44,6 +51,10 @@ namespace mbts.spark {
|
||||
: this(synthetic, strategy, false) {
|
||||
ExceptionAssert.InvalidOperation.That(synthetic.Id != 0, "This constructor may only be used for already-existing synthetic securities (id must be non-zero).");
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
if (TickUtils.is_integer_tick(synthetic.ContractSpec)) {
|
||||
_use_fractional_ticks = false;
|
||||
_integer_tick_size = DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0;
|
||||
}
|
||||
title = $"Edit '{synthetic.Synthetic.Name}'";
|
||||
icon = Icons.ModifySynthetic;
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
|
||||
@@ -74,9 +85,7 @@ namespace mbts.spark {
|
||||
icon = Icons.Synthetics;
|
||||
_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).ToArray()
|
||||
}
|
||||
new SecurityCriteriaClause(exchanges: EnumHelper.get_values_except(Venue.Bts).ToArray())
|
||||
}));
|
||||
contract_picker_model.cancel_action += _close_new_leg_popup;
|
||||
contract_picker_model.ok_action += () => {
|
||||
@@ -131,14 +140,14 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
if (use_fractional_ticks ? (_tick_factors == null || !_tick_factors.is_valid()) : integer_tick_size == 0) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, use_fractional_ticks ? nameof(tick_factors) : nameof(integer_tick_size)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
|
||||
_synthetic.Symbol = _deduce_symbol(_synthetic);
|
||||
_synthetic.ContractSpec = _tick_factors.to_spec();
|
||||
_synthetic.ContractSpec = use_fractional_ticks ? _tick_factors.to_spec() : TickUtils.create_integer_tick_spec(integer_tick_size);
|
||||
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -187,6 +196,9 @@ namespace mbts.spark {
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
|
||||
|
||||
var quote_time_throttle_delay_to_run = new QuoteTimeThrottleDelayToRunModel();
|
||||
var quote_time_throttle_delay_to_improve = new QuoteTimeThrottleDelayToImproveModel();
|
||||
|
||||
var conditional_quoting_ticks = new ConditionalQuotingTicksModel();
|
||||
var conditional_quoting_price_type = new ConditionalQuotingPriceTypeModel();
|
||||
|
||||
@@ -214,17 +226,24 @@ namespace mbts.spark {
|
||||
_quantity_model,
|
||||
_price_mult_model,
|
||||
is_quote_model,
|
||||
//new EnableHedgeModel(), //TAHA_SPREADER
|
||||
new EnableHedgeModel(), //TAHA_SPREADER
|
||||
new HedgeRatioModel(),
|
||||
//new MinLeanQtyModel(), //TAHA_SPREADER
|
||||
new LeanOffsetTicksModel(),
|
||||
//new QuoteHiddenModel(),
|
||||
new QuoteQueueHolderModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new ShowQuantityEnabledModel(show_quantity_quantity),
|
||||
show_quantity_quantity,
|
||||
// Throttle
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
// Time Throttle
|
||||
new QuoteTimeThrottleEnabledModel(quote_time_throttle_delay_to_run, quote_time_throttle_delay_to_improve),
|
||||
quote_time_throttle_delay_to_run,
|
||||
quote_time_throttle_delay_to_improve,
|
||||
|
||||
new ConditionalQuoteEnabledModel(conditional_quoting_ticks, conditional_quoting_price_type),
|
||||
conditional_quoting_ticks,
|
||||
//conditional_quoting_price_type,
|
||||
@@ -295,6 +314,20 @@ namespace mbts.spark {
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
public bool is_new { get; }
|
||||
public bool is_new_leg_popup_open { get; set; }
|
||||
public bool use_fractional_ticks {
|
||||
get => _use_fractional_ticks;
|
||||
set {
|
||||
if (Set(ref _use_fractional_ticks, value))
|
||||
_set_ticks_automatically = false;
|
||||
}
|
||||
}
|
||||
public int integer_tick_size {
|
||||
get => _integer_tick_size;
|
||||
set {
|
||||
if (Set(ref _integer_tick_size, value))
|
||||
_set_ticks_automatically = false;
|
||||
}
|
||||
}
|
||||
public bool can_add_leg => _can_add_leg.Value;
|
||||
public SyntheticContract.Types.PriceCalc price_calc { get => _synthetic.Synthetic.PriceCalc; set => _synthetic.Synthetic.PriceCalc = value; }
|
||||
public ObservableCollection<LegSpreaderParamsTuple> leg_tuples { get; } = new ObservableCollection<LegSpreaderParamsTuple>();
|
||||
@@ -323,6 +356,16 @@ namespace mbts.spark {
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
if (synthetic.HasContractSpec) {
|
||||
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
|
||||
/// rather than the properties <see cref="use_fractional_ticks"/> and <see cref="integer_tick_size"/>
|
||||
/// because the setters of the former flip <see cref="_set_ticks_automatically"/> to false if the value changes.
|
||||
/// This behavior causes the ticks to be automatically deduced as legs are added/removed,
|
||||
/// until the user explicitly enters a different value (as the properties are bound to tick input controls in the view).
|
||||
Set(ref _use_fractional_ticks, !TickUtils.is_integer_tick(synthetic.ContractSpec), nameof(use_fractional_ticks));
|
||||
if (!use_fractional_ticks)
|
||||
Set(ref _integer_tick_size, DoubleHelper.to_optional_int32(synthetic.ContractSpec.LowTick) ?? 0, nameof(integer_tick_size));
|
||||
}
|
||||
RaisePropertyChanged(nameof(name), nameof(tick_factors));
|
||||
}
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace mbts.spark {
|
||||
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)
|
||||
security_criteria = SecurityCriteria.from_securities(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<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.LP1_RP1" Left="{StaticResource HorizontalPush1}" Right="{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}"/>
|
||||
@@ -130,22 +131,6 @@
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.EditPane}}">
|
||||
<VisualState Name="{x:Static u:VisualStates.Active}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Visible}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static u:VisualStates.Inactive}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
@@ -153,13 +138,19 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
x:Name="_tree_view"
|
||||
Grid.RowSpan="2"
|
||||
ItemsSource="{Binding definitions_view}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
MinWidth="180"
|
||||
Background="Transparent"
|
||||
BorderThickness="0,0,1,0"
|
||||
SelectedItem="{Binding selected_definition}"
|
||||
>
|
||||
<ListBox.ItemTemplate>
|
||||
@@ -173,275 +164,176 @@
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
|
||||
<Grid x:Name="edit_pane">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid u:StateManager.VisualStateCollection="{Binding content_visual_states}" Margin="{StaticResource Margin.TP1}" >
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<!--Name-->
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}"/>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static CannotRenameDefinition, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definitions."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static CannotRenameDefinitionWhileTemplatesPresent, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename definition while templates present."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static CannotRenameTemplate, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Name}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(TextBox.ToolTip)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Cannot rename templates."/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<!--Abbreviation-->
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||
<VisualState Name="{x:Static Populated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Unpopulated, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_active_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
|
||||
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static NotSupportedForTemplates, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.Abbreviation}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(ToolTipService.ShowOnDisabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_root" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ColorAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_label" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)">
|
||||
<DiscreteColorKeyFrame KeyTime="0" Value="Gray"/>
|
||||
</ColorAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_not_supported_for_templates_tooltip" Storyboard.TargetProperty="(FrameworkElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<!--Badge Selector-->
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="default_badge" Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0" Value="0.5"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.BadgeSelector}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="default_badge_radio_button" Storyboard.TargetProperty="(ToggleButton.IsChecked)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">Name:</TextBlock>
|
||||
<TextBox
|
||||
x:Name="name_text_box"
|
||||
Grid.Column="1"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
/>
|
||||
<Grid x:Name="abbreviation_root" Grid.Column="2">
|
||||
<Grid.ToolTip>
|
||||
<Grid>
|
||||
<TextBlock x:Name="abbreviation_active_tooltip" Visibility="Collapsed">
|
||||
<TextBlock.Inlines>
|
||||
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
|
||||
<LineBreak/>
|
||||
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
|
||||
</TextBlock.Inlines>
|
||||
<Grid x:Name="edit_pane" Grid.Column="1">
|
||||
<u:StateManager.VisualState>
|
||||
<Binding Path="selected_definition">
|
||||
<Binding.Converter>
|
||||
<cv:ValueMap Default="{x:Static u:VisualStates.Active}">
|
||||
<cv:KeyValue Key="{x:Null}" Value="{x:Static u:VisualStates.Inactive}"/>
|
||||
</cv:ValueMap>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</u:StateManager.VisualState>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState Name="{x:Static u:VisualStates.Active}"/>
|
||||
<VisualState Name="{x:Static u:VisualStates.Inactive}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="custom_badge_color_picker_button" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="name_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="abbreviation_text_box" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="algo_editor_scroll_viewer" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="edit_pane" Storyboard.TargetProperty="(FrameworkElement.ToolTip).(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Visibility="Collapsed">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock FontStyle="Italic">Select an existing algo to edit from the list to the left.</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontStyle="Italic">Or click the</TextBlock>
|
||||
<Image Source="{x:Static u:Icons.AddBoxes}" Height="20" Margin="2,0"/>
|
||||
<TextBlock FontStyle="Italic"> button to create a new one.</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ToolTip>
|
||||
</Grid.ToolTip>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Fill="{x:Static u:BTSColors.dialog_skirt_brush}" UseLayoutRounding="True"/>
|
||||
<Button
|
||||
x:Name="custom_badge_color_picker_button"
|
||||
Background="{Binding selected_definition.badge_fill}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
ToolTip="Change the icon color used for this algo."
|
||||
IsEnabled="True"
|
||||
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
||||
/>
|
||||
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
|
||||
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox
|
||||
x:Name="default_badge_color_checkbox"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="6,4,0,4"
|
||||
IsChecked="{Binding selected_definition.custom_badge_color.enabled, Converter={StaticResource NotConverter}}"
|
||||
>
|
||||
<CheckBox.ToolTip>
|
||||
<TextBlock>
|
||||
<Run>Use the default icon color for this algo.</Run>
|
||||
<LineBreak/>
|
||||
<Run FontStyle="Italic">Hint: the default color is that of the algo-root.</Run>
|
||||
</TextBlock>
|
||||
<TextBlock x:Name="abbreviation_not_supported_for_templates_tooltip" Visibility="Collapsed">
|
||||
Not supported for templates.
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid.ToolTip>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
|
||||
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="param_name_col"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Icon:</TextBlock>
|
||||
<RadioButton
|
||||
x:Name="default_badge_radio_button"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
GroupName="Badge"
|
||||
>
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock>Use the default icon color for this algo.</TextBlock>
|
||||
<StackPanel TextElement.FontStyle="Italic" Orientation="Horizontal">
|
||||
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
|
||||
<usp:OrderTypeItem
|
||||
Margin="2,0"
|
||||
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
DisplayName="{Binding selected_definition.algo_root.display_name}"
|
||||
/>
|
||||
<TextBlock>).</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<Grid x:Name="default_badge_content_root">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Default</TextBlock>
|
||||
<Rectangle
|
||||
x:Name="default_badge"
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
</Grid>
|
||||
</RadioButton>
|
||||
<RadioButton
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="{StaticResource Margin.LP2}"
|
||||
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
|
||||
GroupName="Badge"
|
||||
>
|
||||
<RadioButton.ToolTip>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
</CheckBox.ToolTip>
|
||||
<Grid x:Name="default_badge_content_root">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.ColumnSpan="3">Specify a custom icon color for this algo.</TextBlock>
|
||||
<TextBlock Grid.Row="1" FontStyle="Italic">Hint: Click</TextBlock>
|
||||
<TextBlock>Use Default Icon</TextBlock>
|
||||
<Rectangle
|
||||
Grid.Row="1"
|
||||
x:Name="default_badge"
|
||||
Grid.Column="1"
|
||||
Margin="3,0"
|
||||
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Style="{StaticResource RectangleBadgeStyle}"
|
||||
/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
|
||||
</Grid>
|
||||
</RadioButton.ToolTip>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock>Custom</TextBlock>
|
||||
<Button
|
||||
x:Name="custom_badge_color_picker_button"
|
||||
Grid.Column="1"
|
||||
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
|
||||
/>
|
||||
<Popup x:Name="custom_badge_color_picker_popup" StaysOpen="False" PlacementTarget="{Binding ElementName=custom_badge_color_picker_button}" Placement="Right">
|
||||
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
|
||||
<cp:ColorPickerControl
|
||||
Background="Transparent"
|
||||
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
/>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
</CheckBox>
|
||||
<cp:ColorPickerControl
|
||||
Grid.Row="1"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
Background="Transparent"
|
||||
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
|
||||
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
|
||||
PreviewMouseDown="_on_badge_color_picker_activated"
|
||||
PreviewGotKeyboardFocus="_on_badge_color_picker_activated"
|
||||
/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
<TextBox
|
||||
x:Name="name_text_box"
|
||||
Grid.Column="1"
|
||||
u:TextBoxBehavior.TextPrompt="Name"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="50"
|
||||
IsEnabled="True"
|
||||
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
ToolTip="This algo's full name."
|
||||
/>
|
||||
<TextBox
|
||||
x:Name="abbreviation_text_box"
|
||||
Grid.Column="2"
|
||||
u:TextBoxBehavior.TextPrompt="Abbrev."
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LP1_RP1}"
|
||||
MinWidth="50"
|
||||
IsEnabled="True"
|
||||
Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"
|
||||
>
|
||||
<TextBox.ToolTip>
|
||||
<TextBlock>
|
||||
<TextBlock.Inlines>
|
||||
<Run>Specify an abbreviation for this algo to use in ladder algo 'favorite' buttons.</Run>
|
||||
<LineBreak/>
|
||||
<Run FontStyle="Italic">Hint: To add/remove algos from 'favorites', open the ladder settings dialog and select the 'Algo Favorites' tab.</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</TextBox.ToolTip>
|
||||
</TextBox>
|
||||
<ScrollViewer x:Name="algo_editor_scroll_viewer" Grid.Row="1" Grid.ColumnSpan="3" VerticalScrollBarVisibility="Auto">
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding selected_definition}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
/>
|
||||
<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 selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
<c:DialogSkirt
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
BorderThickness="0"
|
||||
>
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
|
||||
Save Algo
|
||||
</Button>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
|
||||
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="90" ToolTip="{Binding revert_cmd_tooltip}">
|
||||
Revert Algo
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -19,6 +19,17 @@ namespace mbts.ui.spark {
|
||||
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
|
||||
}
|
||||
|
||||
void _on_badge_color_picker_activated(object sender, MouseButtonEventArgs e) =>
|
||||
_on_badge_color_picker_activated();
|
||||
|
||||
void _on_badge_color_picker_activated(object sender, KeyboardFocusChangedEventArgs e) =>
|
||||
_on_badge_color_picker_activated();
|
||||
|
||||
void _on_badge_color_picker_activated() {
|
||||
if (DataContext is AlgoStudioVM vm && vm.selected_definition != null)
|
||||
vm.selected_definition.custom_badge_color.enabled = true;
|
||||
}
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class EditPane {
|
||||
public const string GroupName = nameof(EditPane);
|
||||
@@ -44,8 +55,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
public static readonly string
|
||||
Populated = $"{GroupName}{nameof(Populated)}",
|
||||
Unpopulated = $"{GroupName}{nameof(Unpopulated)}",
|
||||
NotSupportedForTemplates = $"{GroupName}{nameof(NotSupportedForTemplates)}";
|
||||
Unpopulated = $"{GroupName}{nameof(Unpopulated)}";
|
||||
}
|
||||
|
||||
public static class BadgeSelector {
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
@@ -68,23 +69,12 @@ namespace mbts.spark {
|
||||
.Subscribe(sec_map => _view.Resources[ResourceKeys.Securities] = sec_map));
|
||||
// add definition
|
||||
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
|
||||
// clone definition
|
||||
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
|
||||
_commands.Add(clone_selected_definition_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
|
||||
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
|
||||
// save
|
||||
if (_ui_configuration.can_save_specific) {
|
||||
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
|
||||
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
|
||||
if (_vm.selected_definition is AlgoDefinitionVM d)
|
||||
_vm.save_definition(d).ignore();
|
||||
}, content_is_dirty_src));
|
||||
}
|
||||
// save all
|
||||
var save_all_cmd = new ObservantAsyncCommand(_vm.save_all);
|
||||
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, save_all_cmd));
|
||||
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Clone Algo", Icons.CopyBoxes, _vm.clone_selected_definition_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Save Algo", Icons.DiskFile, _vm.save_cmd) {
|
||||
show_in_toolbar = _ui_configuration.can_save_specific
|
||||
});
|
||||
_commands.Add(new SparkCommandCommand("Save All", Icons.Disk, ObservantAsyncCommand.create(_vm.save_all, out var save_all_observer)));
|
||||
_resources.Add(_vm.subscribe_dirty().OnDispatcher().Subscribe(save_all_observer));
|
||||
// undo/redo
|
||||
if (_ui_configuration.can_undo_redo) {
|
||||
// undo
|
||||
@@ -117,30 +107,11 @@ namespace mbts.spark {
|
||||
_resources.Add(can_redo_source.Subscribe(redo_cmd));
|
||||
}
|
||||
// remove
|
||||
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
|
||||
if (value is AlgoDefinitionVM d) {
|
||||
string prompt; MessageBoxImage icon;
|
||||
if (d.key.mode == AlgoKey.Mode.Persisted) {
|
||||
prompt = $"Are you sure you want to delete '{d.name}?'";
|
||||
icon = MessageBoxImage.Question;
|
||||
} else {
|
||||
// algo hasn't even been created yet (just an unsaved placeholder item), no need to get user confirmation in this case.
|
||||
prompt = null;
|
||||
icon = default;
|
||||
}
|
||||
if (prompt != null) {
|
||||
var resp = BTSMessageBox.Show(prompt, "Algo Studio", BTSMessageBoxButtons.Yes | BTSMessageBoxButtons.No, icon);
|
||||
switch (resp) {
|
||||
case BTSMessageBoxButtons.Yes: break;
|
||||
case BTSMessageBoxButtons.No:
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
_vm.remove_definition(d).ignore();
|
||||
}
|
||||
});
|
||||
_commands.Add(remove_spark_cmd);
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
|
||||
_commands.Add(new SparkCommandCommand("Remove Algo", Icons.Trash, ObservantAsyncCommand.create<AlgoDefinitionVM>(
|
||||
vm => BTSMessageBox.Confirm($"Are you sure you want to delete '{vm.name}?'", Title) ? _vm.remove_definition(vm) : Task.CompletedTask,
|
||||
vm => vm != null,
|
||||
out var remove_cmd_observer)));
|
||||
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_cmd_observer));
|
||||
}
|
||||
|
||||
void _dispose_core() {
|
||||
@@ -149,4 +120,4 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace mbts.spark {
|
||||
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
|
||||
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
|
||||
struct AlgoData : IEquatable<AlgoData> {
|
||||
public static readonly Func<AlgoData, AlgoKey> key_fn = a => a.key;
|
||||
|
||||
public readonly CustomOrderDefinitionImmutable value;
|
||||
public readonly string name;
|
||||
public readonly AlgoKey key;
|
||||
|
||||
@@ -128,6 +128,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public bool is_persisted => mode == Mode.Persisted;
|
||||
public bool is_composite => _composite_key.HasValue || _unpersisted.HasValue;
|
||||
|
||||
public uint persisted => _composite_key.Value;
|
||||
public Guid unpersisted => _unpersisted.Value;
|
||||
@@ -199,6 +200,15 @@ namespace mbts.spark {
|
||||
}
|
||||
return EqualityComparer.phase_hash(hash, phase_mult);
|
||||
}
|
||||
public override string ToString() {
|
||||
switch (mode) {
|
||||
case Mode.Dynamic: return $"Dynamic: {dynamic_type}";
|
||||
case Mode.Factory: return $"Factory: {factory}";
|
||||
case Mode.Persisted: return $"Composite: {persisted}";
|
||||
case Mode.Unpersisted: return $"(Composite): {unpersisted}";
|
||||
default: return mode.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(AlgoKey a, AlgoKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoKey a, AlgoKey b) => !a.Equals(b);
|
||||
|
||||
+15
-28
@@ -6,45 +6,39 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export(typeof(IAlgoStudioModel))]
|
||||
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
|
||||
readonly AlgoDefinitionService _service;
|
||||
readonly AdminModel _admin_model;
|
||||
readonly UserTuple _user;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
|
||||
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
|
||||
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, UserTuple user, ErrorReporter error_reporter) {
|
||||
_service = service;
|
||||
this.metadata_service = metadata_service;
|
||||
_admin_model = admin_model;
|
||||
_user = user;
|
||||
_error_reporter = error_reporter;
|
||||
definitions =
|
||||
service.subscribei_algo_definitions()
|
||||
.Filter(r => r.Metadata.IsActive && _admin_model.user.can_access_user_data(r.Metadata.CreatorUserId))
|
||||
.Filter(r => r.Metadata.IsActive && _user.can_access_user_data(r.Metadata.CreatorUserId))
|
||||
.SelectKeyValue(_definition_data, d => d.key)
|
||||
.Table()
|
||||
.Cache();
|
||||
}
|
||||
|
||||
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
|
||||
public IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
|
||||
|
||||
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
|
||||
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
|
||||
|
||||
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
|
||||
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving template");
|
||||
_with_key(new AlgoDefinitionUpdateRequest { UserId = _user.id, Algo = _api_definition(definition_data) }, definition_data.key)),
|
||||
r => r.Success, r => r.ErrorMessage, "saving definition");
|
||||
if (success)
|
||||
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
|
||||
return null;
|
||||
@@ -53,23 +47,22 @@ namespace mbts.spark {
|
||||
public async Task<bool> remove_definition(AlgoKey definition_key) {
|
||||
var req = new AlgoDefinitionRemoveRequest();
|
||||
_set_key(ref req, definition_key);
|
||||
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(() => _service.remove_algo_definition_async(req), r => r.Success, r => r.ErrorMessage, "remove template");
|
||||
(AlgoDefinitionRemoveResponse resp, bool success) = await _error_reporter.safe_execute(
|
||||
() => _service.remove_algo_definition_async(req),
|
||||
r => r.Success,
|
||||
r => r.ErrorMessage,
|
||||
"remove definition");
|
||||
return success;
|
||||
}
|
||||
|
||||
public Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) => Task.FromResult((AlgoTemplateData?)null);
|
||||
|
||||
public Task<bool> remove_template(AlgoKey key) =>
|
||||
Task.FromResult(false);
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
static AlgoDefinition _template(AlgoData definition_data) =>
|
||||
static AlgoDefinition _api_definition(AlgoData definition_data) =>
|
||||
new AlgoDefinition { Name = definition_data.name, Definition = definition_data.value.CloneToMutable() };
|
||||
|
||||
static AlgoDefinitionImmutable _templatei(AlgoData definition_data) =>
|
||||
new AlgoDefinitionImmutable(_template(definition_data));
|
||||
static AlgoDefinitionImmutable _api_definitioni(AlgoData definition_data) =>
|
||||
new AlgoDefinitionImmutable(_api_definition(definition_data));
|
||||
|
||||
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
|
||||
AlgoData.from_record(recordi);
|
||||
@@ -99,11 +92,5 @@ namespace mbts.spark {
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
|
||||
}
|
||||
}
|
||||
|
||||
void _report_error(string present_progressive_desc, string msg) =>
|
||||
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
|
||||
|
||||
void _report_no_user_id(string desc) =>
|
||||
_error_reporter.report_error($"{desc} failed: did not find current user id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,9 @@ using System.Threading.Tasks;
|
||||
namespace mbts.spark {
|
||||
interface IAlgoStudioModel {
|
||||
IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
|
||||
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
|
||||
IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key);
|
||||
IAlgoDefinitionMetadataService metadata_service { get; }
|
||||
Task<AlgoData?> save_definition(AlgoData definition_data);
|
||||
Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data);
|
||||
Task<bool> remove_definition(AlgoKey definition_key);
|
||||
Task<bool> remove_template(AlgoKey template_key);
|
||||
bts.config.AlgoStudioOptions ui_configuration { get; }
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace mbts.spark {
|
||||
/// decouple the client-side key type from the server-side one, so both key types may evolve independently moving forward.
|
||||
/// </summary>
|
||||
struct AlgoDefinitionMetadata : IEquatable<AlgoDefinitionMetadata> {
|
||||
public static readonly Func<AlgoDefinitionMetadata, AlgoKey> key_fn = m => m.id;
|
||||
|
||||
public readonly AlgoKey id, default_template;
|
||||
readonly AlgoKey[] _favorite_templates;
|
||||
public readonly bool is_favorite, is_hidden;
|
||||
|
||||
@@ -35,34 +35,21 @@ namespace mbts.spark {
|
||||
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
|
||||
}).to_comparer().to_safe();
|
||||
|
||||
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> 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)
|
||||
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) =>
|
||||
update_persisted_definitions(target, update.added, update.changed, update.removed.Select(AlgoData.key_fn), factory);
|
||||
|
||||
public static void update_persisted_definitions(ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> target, IEnumerable<AlgoData> added, IEnumerable<AlgoData> changed, IEnumerable<AlgoKey> removed, Func<AlgoData, AlgoDefinitionVM> factory) {
|
||||
foreach (var k in removed)
|
||||
target.Remove(k);
|
||||
foreach (var d in changed)
|
||||
target[d.key].load(d);
|
||||
foreach (var d in update.added) {
|
||||
foreach (var d in added) {
|
||||
if (target.TryGetValue(d.key, out AlgoDefinitionVM def)) {
|
||||
def.load(d);
|
||||
} else target.Add(factory(d));
|
||||
}
|
||||
}
|
||||
|
||||
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 void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
|
||||
algo_param_rows.Select(r => r.param).commit_all();
|
||||
|
||||
|
||||
@@ -42,21 +42,21 @@ namespace mbts.spark {
|
||||
readonly Property<string> _name, _abbreviation;
|
||||
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
AlgoDefinitionMetadata _metadata;
|
||||
IDisposable _loaded_data_hook;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
|
||||
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
|
||||
new AlgoDefinitionVM(model, data, editing_permissions);
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name) =>
|
||||
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
_model = model;
|
||||
_permissions_mask.mask = init_permissions;
|
||||
@@ -68,13 +68,18 @@ namespace mbts.spark {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
// badge color picker palettes
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
var root_algo_color_item = new NamedColorVM(new NamedColor { name = "(Root Algo Type)" }, false);
|
||||
var algo_type_color_palette = new ColorPaletteVM(ColorPalette.factory_order_colors_palette(model.factory));
|
||||
algo_type_color_palette.named_colors.Insert(0, root_algo_color_item);
|
||||
custom_badge_color_palettes.Insert(0, algo_type_color_palette);
|
||||
//
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
// init backing props
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
_selected_format = backing(nameof(selected_format), init_format);
|
||||
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
|
||||
_is_expanded = backing(nameof(is_expanded), true);
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
@@ -91,6 +96,7 @@ namespace mbts.spark {
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
_can_redo.Changed += on_can_redo_changed;
|
||||
_can_undo.Changed += on_can_undo_changed;
|
||||
custom_badge_color.enabled_changed += on_use_custom_badge_color_changed;
|
||||
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
|
||||
// load initial state
|
||||
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
|
||||
@@ -103,16 +109,13 @@ 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)),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm =>
|
||||
vm == null ? null :
|
||||
Disposable.Create(
|
||||
vm.subscribe_is_dirty().Subscribe(v => {
|
||||
if (!v) {
|
||||
_version.increment_version();
|
||||
_on_committed_changed();
|
||||
}
|
||||
}),
|
||||
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
|
||||
_algo_root.observe_current(true).SubscribeEach(vm => {
|
||||
if (vm != null) {
|
||||
root_algo_color_item.assign_color(vm.meta.Color.argb_color());
|
||||
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
|
||||
Disposable.Create(() => {
|
||||
_can_redo.Changed -= on_can_redo_changed;
|
||||
@@ -129,17 +132,6 @@ namespace mbts.spark {
|
||||
break;
|
||||
}
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeEach(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
@@ -153,6 +145,10 @@ namespace mbts.spark {
|
||||
// local methods
|
||||
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_use_custom_badge_color_changed() {
|
||||
if (!custom_badge_color.enabled && algo_root != null)
|
||||
custom_badge_color.value = algo_root.meta.Color.argb_color();
|
||||
}
|
||||
void on_local_changed() {
|
||||
_version.increment_version();
|
||||
if (algo_root != null)
|
||||
@@ -190,12 +186,10 @@ namespace mbts.spark {
|
||||
public AlgoDisplayGroup group => key.display_group();
|
||||
public bool can_redo => _can_redo.Value;
|
||||
public bool can_undo => _can_undo.Value;
|
||||
//public bool use_custom_badge_color
|
||||
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
|
||||
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
|
||||
public Brush badge_fill => _badge_fill;
|
||||
// format
|
||||
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
|
||||
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
|
||||
// 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;
|
||||
@@ -227,12 +221,20 @@ namespace mbts.spark {
|
||||
get => algo_root;
|
||||
set => algo_root = value;
|
||||
}
|
||||
IAlgoVM IAlgoParamVM<IAlgoVM>.display_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;
|
||||
}
|
||||
object IAlgoParamVM.display_value {
|
||||
get => algo_root;
|
||||
set => algo_root = (IAlgoVM)value;
|
||||
}
|
||||
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
|
||||
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
|
||||
|
||||
@@ -253,17 +255,14 @@ namespace mbts.spark {
|
||||
void IObserver<(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo)>.OnError(Exception error) { }
|
||||
#endregion
|
||||
|
||||
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, is_expanded: true));
|
||||
if (!try_select(orig_algo) && !try_select(default_algo))
|
||||
algo_root = algo_roots.FirstOrDefault();
|
||||
//
|
||||
bool try_select(AlgoKey? algo_key) {
|
||||
if (algo_key.HasValue && _algo_roots.TryGetValue(algo_key.Value, out IAlgoVM match)) {
|
||||
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> data, AlgoKey? default_algo) {
|
||||
Disposable.dispose(ref _loaded_data_hook);
|
||||
_algo_roots.SyncWithDictionary(data, d => OrderTypeVMFactory.create(d, is_expanded: true));
|
||||
_loaded_data_hook = ObservableAutoWrapper.connect_item_hooks(algo_roots, vm =>
|
||||
ObservableAutoWrapper.connect_item_actions(vm.child_algo_selectors, s => s.load_algo_data(data, null)));
|
||||
if (algo_root == null || !_algo_roots.ContainsKey(algo_root.id)) {
|
||||
if (default_algo.HasValue && _algo_roots.TryGetValue(default_algo.Value, out IAlgoVM match))
|
||||
algo_root = match;
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +328,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, selected_format, editing_permissions_mask);
|
||||
new AlgoDefinitionVM(_model, def, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
|
||||
@@ -351,7 +350,6 @@ namespace mbts.spark {
|
||||
|
||||
void _assign_from_data(Snapshot state) {
|
||||
_assign_from_data(state.data);
|
||||
_selected_format.set(state.format);
|
||||
_abbreviation.set(state.abbreviation);
|
||||
custom_badge_color.set_nullable(state.custom_badge_color);
|
||||
}
|
||||
@@ -412,41 +410,36 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
Snapshot _snapshot() =>
|
||||
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable(), selected_format);
|
||||
new Snapshot(create_algo_def(), abbreviation, custom_badge_color.get_nullable());
|
||||
|
||||
public struct Snapshot : IEquatable<Snapshot> {
|
||||
public readonly AlgoData data;
|
||||
public readonly string abbreviation;
|
||||
public readonly Color? custom_badge_color;
|
||||
public readonly AlgoFormats format;
|
||||
|
||||
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color, AlgoFormats format) {
|
||||
public Snapshot(AlgoData data, string abbreviation, Color? custom_badge_color) {
|
||||
this.data = data;
|
||||
this.abbreviation = abbreviation;
|
||||
this.custom_badge_color = custom_badge_color;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public bool Equals(Snapshot other) =>
|
||||
data == other.data &&
|
||||
abbreviation == other.abbreviation &&
|
||||
custom_badge_color == other.custom_badge_color &&
|
||||
format == other.format;
|
||||
custom_badge_color == other.custom_badge_color;
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
data.GetHashCode(),
|
||||
abbreviation?.GetHashCode() ?? 0,
|
||||
custom_badge_color?.GetHashCode() ?? 0,
|
||||
(int)format);
|
||||
custom_badge_color?.GetHashCode() ?? 0);
|
||||
#if DEBUG
|
||||
public override string ToString() {
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.Append($"name: {data.name}; ");
|
||||
sb.Append($"abbrev.: {abbreviation}; ");
|
||||
sb.Append($"badge: {custom_badge_color} ");
|
||||
sb.Append($"fmt: {format} ");
|
||||
sb.Append($"def: {data.value}");
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -460,7 +453,6 @@ namespace mbts.spark {
|
||||
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);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
@@ -13,7 +17,6 @@ using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
@@ -38,7 +41,6 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
class AlgoStudioVM : DisposableViewModelBase {
|
||||
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<AlgoDefinitionVM> _selected_definition;
|
||||
readonly Property<IAlgoVM> _editable_content;
|
||||
@@ -71,29 +73,42 @@ namespace mbts.spark {
|
||||
_revert_cmd_tooltip = backing(nameof(revert_cmd_tooltip), get_revert_tooltip);
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
|
||||
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
|
||||
var orig_selected_def = selected_definition;
|
||||
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
|
||||
_model.subscribe_complete_algos(allow_undefined_child: true).OnDispatcher().Subscribe(_definitions_update_buffer),
|
||||
_definitions_update_buffer.OnDispatcher()
|
||||
.SubscribeEach(u => {
|
||||
u.FilterIncremental(a => a.key.is_composite, out var added, out var changes, out var removed);
|
||||
AlgoCollectionHelper.update_persisted_definitions(
|
||||
_definitions, added, changes.New(), removed.Select(AlgoData.key_fn), algo => AlgoDefinitionVM.create(_model, algo));
|
||||
if (selected_definition == null)
|
||||
selected_definition = definitions.FirstOrDefault();
|
||||
// Load the algo building blocks (i.e., factory and dynamic algos):
|
||||
// - Composite algos cannot reference other composite algos - that would create awkward dependency issues as composite algos can be modified/deleted.
|
||||
// - Spreader algos are filtered out as well, because that don't actually mean anything to the algo structure.
|
||||
// Synthetic securities in our system are defined by a set of contract legs, and a [single]strategy.
|
||||
// Note: synthetic strategy templates, however, could be introduced at a later date (no reason why 1 synthetic security couldn't have several ExecutionStrategies).
|
||||
var algo_building_blocks = new FilteredDictionary<AlgoKey, AlgoData>(
|
||||
u.state,
|
||||
a => a.key.mode == AlgoKey.Mode.Dynamic || (a.key.mode == AlgoKey.Mode.Factory && a.key.factory != CustomOrderDefinition.CustomOneofCase.Spreader));
|
||||
return ObservableAutoWrapper.connect_item_actions(_definitions.AsCollection(),
|
||||
d => ((IAlgoSelector)d).load_algo_data(algo_building_blocks, null));
|
||||
}),
|
||||
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
|
||||
_persisted_meta = meta;
|
||||
definitions_view.Refresh();
|
||||
}),
|
||||
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
|
||||
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
} }),
|
||||
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) {
|
||||
val => {
|
||||
_save_cmd_tooltip.refresh();
|
||||
_revert_cmd_tooltip.refresh();
|
||||
}
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
|
||||
// local methods
|
||||
string get_revert_tooltip() {
|
||||
var def = selected_definition;
|
||||
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
|
||||
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
|
||||
(def.is_dirty ? $"Revert changes to '{def.name}'" : "No changes to revert") :
|
||||
null;
|
||||
}
|
||||
async Task save_async(IAlgoStudioContentVM content) {
|
||||
@@ -105,7 +120,7 @@ namespace mbts.spark {
|
||||
if (def != null) {
|
||||
switch (def.key.mode) {
|
||||
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
|
||||
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}'" : "No changes to save";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -125,7 +140,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
|
||||
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
|
||||
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name);
|
||||
_definitions.Add(new_def);
|
||||
selected_definition = new_def;
|
||||
request_view_focus_name?.Invoke(new_def);
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-6
@@ -199,18 +199,30 @@
|
||||
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.Text>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
|
||||
<Binding Path="DataContext.bbo.bid_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<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"
|
||||
/>
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{x:Static cv:DynamicDoubleDivisorConverter.Instance}">
|
||||
<Binding Path="DataContext.bbo.ask_size" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}" Mode="OneWay"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
|
||||
@@ -272,6 +284,14 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--TODO: replace all the param.value bindings with param.display_value.
|
||||
The reason this has not already been done is that the display_value property is new,
|
||||
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.display_value}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
@@ -282,7 +302,7 @@
|
||||
<!--Double-->
|
||||
<usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Enum-->
|
||||
@@ -360,6 +380,22 @@
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<!--TODO: replace all the param.value bindings with param.display_value.
|
||||
The reason this has not already been done is that the display_value property is new,
|
||||
and only necessary for the size param as of 6/8/2020 (slightly more uniform code doesn't warrant rocking the boat).-->
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_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>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</usp:AlgoParamTemplateSelector.KeyedTemplates>
|
||||
<!--Bool-->
|
||||
<usp:AlgoParamTemplateSelector.BoolTemplate>
|
||||
@@ -370,7 +406,7 @@
|
||||
<!--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 x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownDouble.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownDouble.CommandBindings>
|
||||
|
||||
+1
-26
@@ -39,31 +39,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
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>();
|
||||
@@ -85,4 +60,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
|
||||
Content="{Binding algo_root}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
|
||||
@@ -5,6 +5,7 @@ using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
@@ -122,7 +123,7 @@ namespace mbts.spark {
|
||||
add_col(Columns.Account, DefaultColumnHeaders.Account, r => r.account, dv_filter_rx(_model.factory.subscribe_accounts().Select(d => d.Values.Ordered()), r => r.account), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Account);
|
||||
add_col(Columns.Exchange, DefaultColumnHeaders.Exchange, r => r.exchange.ToString(), dv_filter(EnumHelper.get_values<Venue>().ToArray(), r => r.security.exchange_or_default()), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Exchange);
|
||||
add_col(Columns.TimeInForce, DefaultColumnHeaders.TimeInForce, r => r.time_in_force.ToString(), dv_filter(EnumHelper.get_values<TimeInForce>().ToArray(), r => r.order?.time_in_force() ?? TimeInForce.Undefined), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.TimeInForce);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.display_size.ToString(), new DoubleMinMax<FillTuple>(r => r.fill.LastShares), DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), new DoubleMinMax<FillTuple>(r => r.fill.LastPrice), DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, DefaultColumnHeaders.FillKind, r => r.fill_kind.ToString(), dv_filter(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill.FillKind), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
add_col(Columns.FillStatus, DefaultColumnHeaders.FillStatus, r => r.fill_status.ToString(), dv_filter(EnumHelper.get_values<FillStatus>().ToArray(), r => r.fill.FillStatus), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.FillKind);
|
||||
@@ -286,6 +287,7 @@ namespace mbts.spark {
|
||||
Color? _root_algo_badge_color;
|
||||
OrderMetadata _order_metadata;
|
||||
bool _is_selected;
|
||||
double? _display_size;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) :
|
||||
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
|
||||
@@ -306,6 +308,8 @@ namespace mbts.spark {
|
||||
public Side side => _fill.Side;
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public double display_size => _display_size ??
|
||||
(_display_size = size.safe_divide(_model.security_auxiliary_model.get_display_quantity_divisor(security))).Value;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public FillStatus fill_status => _fill.FillStatus;
|
||||
public string fill_key => _fill.FillKey;
|
||||
@@ -330,6 +334,27 @@ namespace mbts.spark {
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(typeof(Row),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(side),
|
||||
nameof(fill_time),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(size),
|
||||
nameof(price),
|
||||
nameof(fill_kind),
|
||||
nameof(fill_status),
|
||||
nameof(fill_key),
|
||||
nameof(order_key),
|
||||
nameof(security_key)
|
||||
});
|
||||
|
||||
public event Action invalidated;
|
||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@ namespace mbts.ui.spark {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row, FillsView.Columns> { }
|
||||
class FillsRowView : DIYGrid.HighPerformanceRow<FillsVM.Row> { }
|
||||
}
|
||||
@@ -3,6 +3,8 @@ using bts.api;
|
||||
using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -24,6 +26,7 @@ namespace mbts.spark {
|
||||
FillsVM _vm;
|
||||
FillsView _view;
|
||||
FillsSettings _state;
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
|
||||
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
|
||||
@@ -38,7 +41,10 @@ namespace mbts.spark {
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
|
||||
public override FillsSettings SaveState => _state = new FillsSettings(_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()) {
|
||||
menu_display = _menu_display_vm.safe_create()
|
||||
};
|
||||
|
||||
public override double MinWidth =>
|
||||
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
@@ -50,10 +56,13 @@ namespace mbts.spark {
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
_vm = new FillsVM(_bus, _orders_model, _state, _settings_manager);
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
@@ -70,7 +79,8 @@ namespace mbts.spark {
|
||||
var dialog = new ShellSettingsDialog { DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
})),
|
||||
_menu_display_vm.spark_cmd
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,5 +88,8 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ namespace bts.shell.fills {
|
||||
public FillsFilterStates filters;
|
||||
[ProtoMember(4)]
|
||||
public GridSettings grid;
|
||||
[ProtoMember(5)]
|
||||
public SparkMenuDisplaySettings menu_display;
|
||||
|
||||
public FillsSettings() { }
|
||||
public FillsSettings(uint[] orders) : this(orders, null, null) { }
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using bts.collections;
|
||||
using bts.persist.algo;
|
||||
using bts.proto;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility.text;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using bts.proto;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using mbts.ui.settings;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
@@ -84,6 +89,15 @@ namespace mbts.spark {
|
||||
public static bool operator !=(LadderColumnSettings x, LadderColumnSettings y) => !(x == y);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class LadderOrderLimits : IEquatable<LadderOrderLimits> {
|
||||
[ProtoMember(1)]
|
||||
public ToggleInt32Proto max_settlement_cross_ticks { get; set; }
|
||||
|
||||
public bool Equals(LadderOrderLimits other) =>
|
||||
other != null && max_settlement_cross_ticks == other.max_settlement_cross_ticks;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class HistogramBarAnimation : IEquatable<HistogramBarAnimation> {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
@@ -154,6 +168,10 @@ namespace mbts.spark {
|
||||
|
||||
[ProtoContract]
|
||||
class LadderMarketBreakLine : IEquatable<LadderMarketBreakLine> {
|
||||
public enum RefreshMode {
|
||||
Auto,
|
||||
Manual
|
||||
}
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(LadderMarketBreakLine).GetHashCode();
|
||||
@@ -161,6 +179,8 @@ namespace mbts.spark {
|
||||
public bool show { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint color { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public RefreshMode refresh_mode { get; set; }
|
||||
|
||||
public uint? color_nullable {
|
||||
get {
|
||||
@@ -173,7 +193,8 @@ namespace mbts.spark {
|
||||
public bool Equals(LadderMarketBreakLine other) =>
|
||||
other != null &&
|
||||
other.show == show &&
|
||||
other.color == color;
|
||||
other.color == color &&
|
||||
other.refresh_mode == refresh_mode;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LadderMarketBreakLine);
|
||||
@@ -182,33 +203,128 @@ namespace mbts.spark {
|
||||
_type_hash;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class FairValueLine : IEquatable<FairValueLine> {
|
||||
public enum PriceType {
|
||||
Bid,
|
||||
Ask,
|
||||
Mid
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public uint security_key { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public PriceType price_type { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public LadderMarketBreakLine display { get; set; }
|
||||
|
||||
public bool Equals(FairValueLine other) {
|
||||
return other != null &&
|
||||
security_key == other.security_key &&
|
||||
price_type == other.price_type &&
|
||||
display.safe_equals(other.display);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
return Equals(obj as FairValueLine);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return (int)security_key;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum LadderFavoriteAlgoFlags {
|
||||
Sticky = 1,
|
||||
AutoRevert = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This struct contains properties for a single algo favorite item.
|
||||
/// The various <see cref="AlgoPersistedKey"/> arrays in <see cref="LadderFavoriteAlgosSettings"/>,
|
||||
/// can be used to create a collection of <see cref="LadderFavoriteAlgoItem"/>s.
|
||||
/// In fact, the method <see cref="LadderFavoriteAlgosSettings.to_algo_items"/> does just that.
|
||||
/// </summary>
|
||||
readonly struct LadderFavoriteAlgoItem : IEquatable<LadderFavoriteAlgoItem> {
|
||||
public readonly AlgoKey key;
|
||||
public readonly int sort_index;
|
||||
public readonly LadderFavoriteAlgoFlags flags;
|
||||
|
||||
public LadderFavoriteAlgoItem(AlgoKey key, int sort_index, LadderFavoriteAlgoFlags flags) {
|
||||
this.key = key;
|
||||
this.sort_index = sort_index;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public bool Equals(LadderFavoriteAlgoItem other) =>
|
||||
key == other.key &&
|
||||
flags == other.flags;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is LadderFavoriteAlgoItem other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
struct LadderFavoriteAlgosSettings : IEquatable<LadderFavoriteAlgosSettings> {
|
||||
[ProtoMember(1)]
|
||||
readonly bts.persist.algo.AlgoPersistedKey[] _favorite_algos;
|
||||
readonly AlgoPersistedKey[] _favorite_algos;
|
||||
[ProtoMember(2)]
|
||||
public readonly int column_count;
|
||||
[ProtoMember(3)]
|
||||
readonly bts.persist.algo.AlgoPersistedKey[] _stickied_algos;
|
||||
readonly AlgoPersistedKey[] _stickied_algos;
|
||||
[ProtoMember(4)]
|
||||
readonly AlgoPersistedKey[] _auto_revert_algos;
|
||||
|
||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos)
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted)),
|
||||
column_count,
|
||||
ArrayHelper.safe_convert(stickied_algos, k => new bts.persist.algo.AlgoPersistedKey(k.persisted))) { }
|
||||
public LadderFavoriteAlgosSettings(AlgoKey[] favorite_algos, int column_count, AlgoKey[] stickied_algos, AlgoKey[] auto_revert_algos)
|
||||
: this(ArrayHelper.safe_convert(favorite_algos, k => new AlgoPersistedKey(k.persisted)),
|
||||
ArrayHelper.safe_convert(stickied_algos, k => new AlgoPersistedKey(k.persisted)),
|
||||
ArrayHelper.safe_convert(auto_revert_algos, k => new AlgoPersistedKey(k.persisted)),
|
||||
column_count) { }
|
||||
|
||||
LadderFavoriteAlgosSettings(bts.persist.algo.AlgoPersistedKey[] favorite_algos, int column_count, bts.persist.algo.AlgoPersistedKey[] stickied_algos) {
|
||||
LadderFavoriteAlgosSettings(AlgoPersistedKey[] favorite_algos, AlgoPersistedKey[] stickied_algos, AlgoPersistedKey[] auto_revert_algos, int column_count) {
|
||||
_favorite_algos = favorite_algos;
|
||||
this.column_count = column_count;
|
||||
_stickied_algos = stickied_algos;
|
||||
_auto_revert_algos = auto_revert_algos;
|
||||
this.column_count = column_count;
|
||||
}
|
||||
|
||||
public IReadOnlyList<AlgoKey> favorite_algos => ArrayHelper.safe_convert(_favorite_algos, k => k.to_algo_key());
|
||||
public IReadOnlyList<AlgoKey> stickied_algos => ArrayHelper.safe_convert(_stickied_algos, k => k.to_algo_key());
|
||||
public IReadOnlyList<AlgoKey> favorite_algos => _favorite_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
|
||||
public IReadOnlyList<AlgoKey> stickied_algos => _stickied_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
|
||||
public IReadOnlyList<AlgoKey> auto_revert_algos => _auto_revert_algos?.WrapSelectorRO(pk => pk.to_algo_key()) ?? Array.Empty<AlgoKey>();
|
||||
|
||||
/// <summary>
|
||||
/// A more sane representation of the data contained in this proto,
|
||||
/// so callers don't need to join the various key lists together to determine which algos are sticky, auto revert, etc.
|
||||
/// </summary>
|
||||
/// <returns>An immutable dictionary of favorite algo data.</returns>
|
||||
public ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> build_map() {
|
||||
var map = ImmutableDictionary.CreateBuilder<AlgoKey, LadderFavoriteAlgoItem>();
|
||||
if (_favorite_algos != null) {
|
||||
var stickied_algo_set = new HashSet<AlgoKey>(stickied_algos);
|
||||
var auto_revert_algo_set = new HashSet<AlgoKey>(auto_revert_algos);
|
||||
for (int i = 0; i < _favorite_algos.Length; i++) {
|
||||
var pk = _favorite_algos[i];
|
||||
var k = pk.to_algo_key();
|
||||
var flags = (LadderFavoriteAlgoFlags)0;
|
||||
if (stickied_algo_set.Contains(k))
|
||||
flags |= LadderFavoriteAlgoFlags.Sticky;
|
||||
if (auto_revert_algo_set.Contains(k))
|
||||
flags |= LadderFavoriteAlgoFlags.AutoRevert;
|
||||
map[k] = new LadderFavoriteAlgoItem(k, i, flags);
|
||||
}
|
||||
}
|
||||
return map.ToImmutable();
|
||||
}
|
||||
|
||||
public bool Equals(LadderFavoriteAlgosSettings other) =>
|
||||
column_count == other.column_count &&
|
||||
favorite_algos.SequenceEqual(other.favorite_algos) &&
|
||||
stickied_algos.SequenceEqual(other.stickied_algos);
|
||||
stickied_algos.SequenceEqual(other.stickied_algos) &&
|
||||
auto_revert_algos.SequenceEqual(other.auto_revert_algos);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
EqualityComparer.equals_obj(this, obj);
|
||||
@@ -217,7 +333,8 @@ namespace mbts.spark {
|
||||
EqualityComparer.aggregate_hash(
|
||||
column_count,
|
||||
EqualityComparer.aggregate_hash_limited(favorite_algos),
|
||||
EqualityComparer.aggregate_hash_limited(stickied_algos));
|
||||
EqualityComparer.aggregate_hash_limited(stickied_algos),
|
||||
EqualityComparer.aggregate_hash_limited(auto_revert_algos));
|
||||
|
||||
public static bool operator ==(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => a.Equals(b);
|
||||
public static bool operator !=(LadderFavoriteAlgosSettings a, LadderFavoriteAlgosSettings b) => !a.Equals(b);
|
||||
@@ -240,12 +357,147 @@ namespace mbts.spark {
|
||||
public static bool operator !=(LadderMiniOrderBookSettings a, LadderMiniOrderBookSettings b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class LadderPanelPricePointSettings : IEquatable<LadderPanelPricePointSettings> {
|
||||
public static LadderPanelPricePointSettings historic_default =>
|
||||
new LadderPanelPricePointSettings {
|
||||
day_change = new DaySettings {
|
||||
// (neutral)
|
||||
font = _default_font,
|
||||
background_color = Colors.Transparent.rgb_color(),
|
||||
// negative
|
||||
negative_font_color = Colors.White.rgb_color(),
|
||||
negative_background_color = Colors.Red.rgb_color(),
|
||||
// positive
|
||||
positive_font_color = Colors.White.rgb_color(),
|
||||
positive_background_color = Colors.Green.rgb_color()
|
||||
},
|
||||
session_low = _default_item,
|
||||
session_high = _default_item,
|
||||
open = _default_item,
|
||||
settlement = _default_item,
|
||||
volume = _default_item
|
||||
};
|
||||
|
||||
public static LadderPanelPricePointSettings tt_default {
|
||||
get {
|
||||
var rv = historic_default;
|
||||
rv.day_change.background_color = Color.FromRgb(113, 201, 69).rgb_color();
|
||||
rv.day_change.font.font_color = Colors.Black.rgb_color();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public static LadderPanelPricePointSettings Default => LadderSettings.mimic_tt ?
|
||||
tt_default : historic_default;
|
||||
|
||||
[ProtoMember(1)]
|
||||
public DaySettings day_change { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public ItemSettings session_low { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public ItemSettings session_high { get; set; }
|
||||
[ProtoMember(4)]
|
||||
public ItemSettings open { get; set; }
|
||||
[ProtoMember(5)]
|
||||
public ItemSettings settlement { get; set; }
|
||||
[ProtoMember(6)]
|
||||
public ItemSettings volume { get; set; }
|
||||
|
||||
public bool Equals(LadderPanelPricePointSettings other) =>
|
||||
other != null &&
|
||||
day_change.safe_equals(other.day_change) &&
|
||||
session_high.safe_equals(other.session_high) &&
|
||||
session_high.safe_equals(other.session_high) &&
|
||||
open.safe_equals(other.open) &&
|
||||
settlement.safe_equals(other.settlement) &&
|
||||
volume.safe_equals(other.volume);
|
||||
|
||||
static FontParamsProto _default_font =>
|
||||
new FontParamsProto {
|
||||
font_color = Colors.Black.rgb_color(),
|
||||
font_size = FontSizes.Regular,
|
||||
typeface = new TypefaceProto(TextUtils.StandardFontFamilyName, FontWeights.Bold.ToOpenTypeWeight())
|
||||
};
|
||||
|
||||
static ItemSettings _default_item =>
|
||||
new ItemSettings {
|
||||
font = _default_font,
|
||||
background_color = Colors.Transparent.rgb_color()
|
||||
};
|
||||
|
||||
[ProtoContract]
|
||||
readonly struct SignedColorPair : IEquatable<SignedColorPair> {
|
||||
[ProtoMember(1)]
|
||||
public uint positive_color { get; }
|
||||
[ProtoMember(2)]
|
||||
public uint negative_color { get; }
|
||||
|
||||
public bool Equals(SignedColorPair other) =>
|
||||
positive_color == other.positive_color &&
|
||||
negative_color == other.negative_color;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class ItemSettings : IEquatable<ItemSettings> {
|
||||
[ProtoMember(1)]
|
||||
public FontParamsProto font { get; set; }
|
||||
[ProtoMember(2)]
|
||||
public uint background_color { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public bool show_label { get; set; }
|
||||
|
||||
public bool Equals(ItemSettings other) =>
|
||||
other != null &&
|
||||
font.safe_equals(other.font) &&
|
||||
background_color == other.background_color &&
|
||||
show_label == other.show_label;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class DaySettings : IEquatable<DaySettings> {
|
||||
[ProtoMember(1)]
|
||||
public FontParamsProto font { get; set; }
|
||||
// [neutral] background
|
||||
[ProtoMember(2)]
|
||||
public uint background_color { get; set; }
|
||||
[ProtoMember(3)]
|
||||
public bool show_label { get; set; }
|
||||
|
||||
// negative
|
||||
[ProtoMember(4)]
|
||||
public uint negative_font_color { get; set; }
|
||||
[ProtoMember(5)]
|
||||
public uint negative_background_color { get; set; }
|
||||
|
||||
// positive
|
||||
[ProtoMember(6)]
|
||||
public uint positive_font_color { get; set; }
|
||||
[ProtoMember(7)]
|
||||
public uint positive_background_color { get; set; }
|
||||
|
||||
public uint neutral_foreground => font?.font_color ?? 0;
|
||||
|
||||
public bool Equals(DaySettings other) =>
|
||||
other != null &&
|
||||
font.safe_equals(other.font) &&
|
||||
background_color == other.background_color &&
|
||||
show_label == other.show_label &&
|
||||
negative_font_color == other.negative_font_color &&
|
||||
negative_background_color == other.negative_background_color &&
|
||||
positive_font_color == other.positive_font_color &&
|
||||
positive_background_color == other.positive_background_color;
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: BE SURE TO ADD ANY NEW PROPERTIES TO THE EQUALS FUNCTION!
|
||||
// 1 important reason at this time:
|
||||
// From the settings dialog, default settings will only be persisted when they are NOT EQUAL to the current default settings.
|
||||
// Settings are liable to change in the near future, so an internal IEqualityComparer simply won't do as it risks becoming out of date.
|
||||
[ProtoContract]
|
||||
class LadderSettings : IEquatable<LadderSettings> {
|
||||
// RESERVED ProtoMember indices
|
||||
// 900-999: dj settings
|
||||
const double _default_order_entry_panel_width = 110.0;
|
||||
const double _default_zoom_factor = 1.0;
|
||||
const bool _default_show_warn_on_cross = true;
|
||||
@@ -255,6 +507,13 @@ namespace mbts.spark {
|
||||
// this class is 100% mutable (there are literally 0 readonly members),
|
||||
// so GetHashCode must return the same hash for every instance.
|
||||
static readonly int _type_hash = typeof(LadderSettings).GetHashCode();
|
||||
uint _ticks_per_row;
|
||||
List<FairValueLine> _fair_value_lines;
|
||||
|
||||
static LadderSettings() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
mimic_tt = knobs.mimic_tt;
|
||||
}
|
||||
|
||||
// Default proto constructor
|
||||
// Added this so that when clients upgrade to a version with new settings,
|
||||
@@ -281,13 +540,15 @@ namespace mbts.spark {
|
||||
order_entry_panel_initial_width = _default_order_entry_panel_width;
|
||||
zoom_factor = _default_zoom_factor;
|
||||
use_defaults = true;
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null);
|
||||
favorite_algos = new LadderFavoriteAlgosSettings(null, 2, null, null);
|
||||
show_warn_on_cross = _default_show_warn_on_cross;
|
||||
enforce_max_clip = _default_enforce_max_clip;
|
||||
status_message_visible = true;
|
||||
}
|
||||
|
||||
public static bool mimic_tt { get; }
|
||||
|
||||
public static LadderSettings Default {
|
||||
public static LadderSettings historic_default {
|
||||
get {
|
||||
LadderColumnSettings
|
||||
orders_column = LadderColumnSettings.DefaultOrderColumnSettings,
|
||||
@@ -297,12 +558,12 @@ namespace mbts.spark {
|
||||
last_column = LadderColumnSettings.Default,
|
||||
piq_column = LadderColumnSettings.DefaultPiqColumnSettings;
|
||||
orders_column.background = BTSColors.LadderSpecial.default_column_background;
|
||||
bid_column.foreground = 0xFFFFFF;
|
||||
bid_column.foreground = 0xFFFFFFFF;
|
||||
bid_column.background = BTSColors.MarketData.bid;
|
||||
ask_column.foreground = 0xFFFFFF;
|
||||
ask_column.foreground = 0xFFFFFFFF;
|
||||
ask_column.background = BTSColors.MarketData.ask;
|
||||
price_column.background = BTSColors.LadderSpecial.default_column_background;
|
||||
last_column.background = 0xFFFFFF;
|
||||
last_column.background = 0xFFFFFFFF;
|
||||
piq_column.background = BTSColors.LadderSpecial.default_column_background;
|
||||
var rv = new LadderSettings {
|
||||
row_height = 22,
|
||||
@@ -346,6 +607,7 @@ namespace mbts.spark {
|
||||
auto_clear_visible = true,
|
||||
clear_visible = true,
|
||||
order_entry_panel_initial_width = _default_order_entry_panel_width,
|
||||
panel_price_point_settings = LadderPanelPricePointSettings.Default,
|
||||
zoom_slider_visible = false,
|
||||
zoom_factor = _default_zoom_factor,
|
||||
net_change = NetChangeTerms.None,
|
||||
@@ -358,6 +620,28 @@ namespace mbts.spark {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public static LadderSettings tt_default {
|
||||
get {
|
||||
var rv = historic_default;
|
||||
rv.orders_column.background = Color.FromRgb(223, 223, 223).rgb_color();
|
||||
// Since the price column sits atop the session range fill in tt
|
||||
// mode (normally it is the other way around), the former is made
|
||||
// partially translucent while the latter is fully opaque (obv the
|
||||
// top layer cannot be opaque, since it would completely hide the
|
||||
// layer beneath it).
|
||||
rv.session_range_fill.color = rv.session_range_fill.color.rgb_color().with(255).rgb_color();
|
||||
rv.price_column.background = Color.FromArgb(226, 166, 166, 166).rgb_color();
|
||||
rv.price_column.foreground = Colors.White.rgb_color();
|
||||
rv.bid_column.background = Color.FromRgb(0, 30, 160).rgb_color();
|
||||
rv.ask_column.background = Color.FromRgb(160, 0, 0).rgb_color();
|
||||
rv.ask_column.foreground = Colors.Black.rgb_color();
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public static LadderSettings Default => mimic_tt ?
|
||||
tt_default : historic_default;
|
||||
|
||||
public enum BadgeStyle { Normal, Alternate }
|
||||
|
||||
@@ -524,6 +808,22 @@ namespace mbts.spark {
|
||||
[ProtoMember(61), DefaultValue(true)]
|
||||
public bool status_message_visible { get; set; }
|
||||
|
||||
[ProtoMember(62)]
|
||||
public uint ticks_per_row {
|
||||
get => Math.Max(_ticks_per_row, 1);
|
||||
set => _ticks_per_row = value;
|
||||
}
|
||||
|
||||
[ProtoMember(900)]
|
||||
public LadderOrderLimits ladder_order_limits { get; set; }
|
||||
[ProtoMember(901)]
|
||||
public LadderPanelPricePointSettings panel_price_point_settings { get; set; }
|
||||
[ProtoMember(902)]
|
||||
public List<FairValueLine> fair_value_lines {
|
||||
get => _fair_value_lines ?? (_fair_value_lines = new List<FairValueLine>());
|
||||
set => _fair_value_lines = value;
|
||||
}
|
||||
|
||||
public void sanitize() =>
|
||||
sanitize(this);
|
||||
|
||||
@@ -557,6 +857,11 @@ namespace mbts.spark {
|
||||
s.mini_ob = new LadderMiniOrderBookSettings(s.mini_ob.value.with(sorting_criterion: bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general.sorting_criterion));
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
if (s.panel_price_point_settings is null)
|
||||
s.panel_price_point_settings = LadderPanelPricePointSettings.Default;
|
||||
if (s.fair_value_lines is null) {
|
||||
s.fair_value_lines = new List<FairValueLine>(0);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -629,7 +934,11 @@ namespace mbts.spark {
|
||||
a.enforce_max_clip == b.enforce_max_clip &&
|
||||
a.max_clip == b.max_clip &&
|
||||
a.price_display_format == b.price_display_format &&
|
||||
a.status_message_visible == b.status_message_visible;
|
||||
a.status_message_visible == b.status_message_visible &&
|
||||
a.ladder_order_limits.safe_equals(b.ladder_order_limits) &&
|
||||
a.panel_price_point_settings.safe_equals(b.panel_price_point_settings) &&
|
||||
a._ticks_per_row == b._ticks_per_row &&
|
||||
EqualityComparer.safe_sequence_equals(a.fair_value_lines, b.fair_value_lines);
|
||||
|
||||
public bool Equals(LadderSettings other) =>
|
||||
profile_equals(this, other) &&
|
||||
|
||||
@@ -70,7 +70,11 @@ namespace bts.shell {
|
||||
if (version < _version_where_stickied_algos_introduced) {
|
||||
// We want to assign any existing favorite algos to stickied algos upon setting upgrade
|
||||
var favorite_algos_array = state.settings.favorite_algos.favorite_algos.ToArray();
|
||||
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(favorite_algos_array, state.settings.favorite_algos.column_count, favorite_algos_array);
|
||||
state.settings.favorite_algos = new LadderFavoriteAlgosSettings(
|
||||
favorite_algos_array,
|
||||
state.settings.favorite_algos.column_count,
|
||||
favorite_algos_array,
|
||||
state.settings.favorite_algos.auto_revert_algos.ToArray());
|
||||
}
|
||||
return load(state);
|
||||
}
|
||||
@@ -96,6 +100,7 @@ namespace bts.shell {
|
||||
ShellWindowVM shell_window_vm = null;
|
||||
IShellWindow shell_window = null;
|
||||
var title_source = new LazyObservable<string>(() => ShellWindowExtensions.literal_title_source(shell_window, shell_window_vm));
|
||||
LadderVM vm = null;
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
// resources
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
@@ -109,7 +114,6 @@ namespace bts.shell {
|
||||
/// In response to that event, 'needs_resync' is set to true. This causes 'last_value' to be refreshed when the next caller requests the latest settings
|
||||
/// (i.e., an IObserver subscribing to the settings_source, or the settings-dialog viewmodel).
|
||||
bool initialized = false, needs_resync = false;
|
||||
LadderVM vm = null;
|
||||
var settings_subject = new Subject<LadderSettings>();
|
||||
var settings_source = Observable.Create<LadderSettings>(o => {
|
||||
ensure_synced();
|
||||
@@ -204,6 +208,10 @@ namespace bts.shell {
|
||||
s => {
|
||||
var svm = new LadderSettingsVM(model.order_gateway, vm.security);
|
||||
svm.load(s);
|
||||
svm.SubscribeDisposed(
|
||||
_bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(sec_map => svm.security_map = sec_map));
|
||||
return svm;
|
||||
},
|
||||
_ => _,
|
||||
@@ -268,10 +276,11 @@ namespace bts.shell {
|
||||
(v, s) => save(_get_state(((LadderView)v).vm, settings_id), s),
|
||||
icon: Icons.Ladder,
|
||||
thread_id: _thread_id_provider.next_id(),
|
||||
vm => {
|
||||
shell_window_vm = vm;
|
||||
swvm => {
|
||||
shell_window_vm = swvm;
|
||||
title_source.connect();
|
||||
});
|
||||
},
|
||||
key_event_args => vm?.key_down(key_event_args));
|
||||
}
|
||||
|
||||
async void _show_windows(IImmutableSet<uint> securities) {
|
||||
@@ -326,4 +335,4 @@ namespace bts.shell {
|
||||
[ProtoMember(3)]
|
||||
public uint settings_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls.color_picker;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FairValueLineSettingsVM : ui.shell.ViewModelBase {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public FairValueLineSettingsVM() {
|
||||
add_item_cmd = new DelegateCommand(() => items.Add(new FairValueLineItemSettingsVM()));
|
||||
remove_item_cmd = new DelegateCommand<FairValueLineItemSettingsVM>(item => items.Remove(item));
|
||||
}
|
||||
|
||||
public ObservableCollection<FairValueLineItemSettingsVM> items { get; } =
|
||||
new ObservableCollection<FairValueLineItemSettingsVM>();
|
||||
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set => Set(ref _security_map, value);
|
||||
}
|
||||
|
||||
public ICommand add_item_cmd { get; }
|
||||
public ICommand remove_item_cmd { get; }
|
||||
}
|
||||
|
||||
class FairValueLineItemSettingsVM : ui.shell.ViewModelBase {
|
||||
uint _selected_security_key;
|
||||
FairValueLine.PriceType _selected_price_type;
|
||||
Color _color;
|
||||
bool _show = true;
|
||||
bool _auto_refresh = true;
|
||||
|
||||
public FairValueLineItemSettingsVM() {
|
||||
palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
}
|
||||
|
||||
public uint selected_security_key {
|
||||
get => _selected_security_key;
|
||||
set => Set(ref _selected_security_key, value);
|
||||
}
|
||||
|
||||
public IEnumerable<FairValueLine.PriceType> price_types =>
|
||||
ui.utility.EnumHelper.get_values<FairValueLine.PriceType>();
|
||||
|
||||
public FairValueLine.PriceType selected_price_type {
|
||||
get => _selected_price_type;
|
||||
set => Set(ref _selected_price_type, value);
|
||||
}
|
||||
|
||||
public Color color { get => _color; set => Set(ref _color, value); }
|
||||
public bool show { get => _show; set => Set(ref _show, value); }
|
||||
public bool auto_refresh {
|
||||
get => _auto_refresh; set {
|
||||
if (Set(ref _auto_refresh, value)) {
|
||||
RaisePropertyChanged(nameof(refresh_mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
public LadderMarketBreakLine.RefreshMode refresh_mode {
|
||||
get => _auto_refresh ? LadderMarketBreakLine.RefreshMode.Auto : LadderMarketBreakLine.RefreshMode.Manual;
|
||||
set => auto_refresh = value == LadderMarketBreakLine.RefreshMode.Auto;
|
||||
}
|
||||
public ObservableCollection<ColorPaletteVM> palettes { get; }
|
||||
|
||||
public void assign_from(FairValueLine settings) {
|
||||
if (settings is null) {
|
||||
settings = new FairValueLine();
|
||||
}
|
||||
selected_security_key = settings.security_key;
|
||||
selected_price_type = settings.price_type;
|
||||
var display = settings.display ?? new LadderMarketBreakLine();
|
||||
show = display.show;
|
||||
color = display.color.rgb_color();
|
||||
refresh_mode = display.refresh_mode;
|
||||
}
|
||||
|
||||
public FairValueLine create() {
|
||||
return new FairValueLine {
|
||||
security_key = selected_security_key,
|
||||
price_type = _selected_price_type,
|
||||
display = new LadderMarketBreakLine {
|
||||
color = color.rgb_color(),
|
||||
show = show,
|
||||
refresh_mode = refresh_mode
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using mbts.co2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FairValueLineVM {
|
||||
NativePriceSize _bid;
|
||||
NativePriceSize _ask;
|
||||
double _settlement;
|
||||
double _frozen;
|
||||
ulong _last_update;
|
||||
|
||||
public FairValueLineVM(uint sec_id) {
|
||||
this.sec_id = sec_id;
|
||||
}
|
||||
|
||||
public FairValueLineVM(FairValueLine fvl, SecurityMD? sec_md = null) {
|
||||
sec_id = fvl.security_key;
|
||||
price_type = fvl.price_type;
|
||||
settings = fvl.display;
|
||||
this.sec_md = sec_md;
|
||||
}
|
||||
|
||||
public FairValueLine.PriceType price_type { get; private set; }
|
||||
public double price { get; private set; }
|
||||
public double price_view {
|
||||
get {
|
||||
if (!settings.show) {
|
||||
return double.NaN;
|
||||
}
|
||||
switch (settings.refresh_mode) {
|
||||
case LadderMarketBreakLine.RefreshMode.Auto:
|
||||
return price;
|
||||
case LadderMarketBreakLine.RefreshMode.Manual:
|
||||
return frozen;
|
||||
default: return double.NaN;
|
||||
}
|
||||
}
|
||||
}
|
||||
public double frozen {
|
||||
get {
|
||||
if (double.IsNaN(_frozen)) {
|
||||
_frozen = price;
|
||||
}
|
||||
return _frozen;
|
||||
}
|
||||
}
|
||||
public Color color { get; set; }
|
||||
public uint sec_id { get; }
|
||||
SecurityMD? _sec_md;
|
||||
public SecurityMD? sec_md {
|
||||
get => _sec_md;
|
||||
set {
|
||||
if (_sec_md == value) {
|
||||
return;
|
||||
}
|
||||
if (value.HasValue && value.Value.security.Id != sec_id) {
|
||||
throw new ArgumentException("'sec_md' security id must be equal to 'sec_id'", nameof(sec_md));
|
||||
}
|
||||
_sec_md = value;
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
public LadderMarketBreakLine settings { get; set; } = new LadderMarketBreakLine();
|
||||
|
||||
public void load(LadderMarketBreakLine settings, FairValueLine.PriceType price_type) {
|
||||
this.settings = settings;
|
||||
if (this.price_type != price_type) {
|
||||
this.price_type = price_type;
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh(bool update_frozen) {
|
||||
if (sec_md.HasValue) {
|
||||
var old_settlement = _settlement;
|
||||
_settlement = sec_md.Value.md.settlement_price;
|
||||
if (sec_md.Value.md.get_bbo(ref _last_update, out _bid, out _ask) || _settlement != old_settlement) {
|
||||
price = _read_price();
|
||||
}
|
||||
} else {
|
||||
price = double.NaN;
|
||||
}
|
||||
if (update_frozen) {
|
||||
_frozen = price;
|
||||
}
|
||||
}
|
||||
|
||||
double _read_price() {
|
||||
if (!_sec_md.HasValue) {
|
||||
return double.NaN;
|
||||
}
|
||||
var bid = _bid.price;
|
||||
var ask = _ask.price;
|
||||
switch (price_type) {
|
||||
case FairValueLine.PriceType.Bid:
|
||||
return LadderPriceLineViewAdapter.bid_line(
|
||||
_bid.price, _ask.price, _settlement, sec_md.Value.security.ContractSpec);
|
||||
case FairValueLine.PriceType.Ask:
|
||||
return _ask.is_nan ? double.NaN : _ask.price;
|
||||
case FairValueLine.PriceType.Mid:
|
||||
if (_bid.is_nan) {
|
||||
if (_ask.is_nan) {
|
||||
return double.NaN;
|
||||
}
|
||||
return _ask.price;
|
||||
}
|
||||
if (_ask.is_nan) {
|
||||
return _bid.price;
|
||||
}
|
||||
return (_bid.price + _ask.price) / 2;
|
||||
default: return double.NaN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -51,9 +55,9 @@ namespace mbts.spark {
|
||||
public event Action piq_changed;
|
||||
|
||||
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)>();
|
||||
ImmutableDictionary<uint, OrderSecurity> _orders = ImmutableDictionary<uint, OrderSecurity>.Empty;
|
||||
|
||||
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoData?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
|
||||
public LadderOrderVM(ImmutableDictionary<uint, OrderSecurity> orders, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
|
||||
_owner = owner;
|
||||
_bus = bus;
|
||||
_bs = bs;
|
||||
@@ -77,8 +81,7 @@ namespace mbts.spark {
|
||||
double get_font_size(LadderSettings s) => s.orders_column.font_size;
|
||||
Brush get_foreground(LadderSettings s) => s.orders_column.foreground.rgb_brush();
|
||||
double get_row_height(LadderSettings s) => s.row_height;
|
||||
//
|
||||
add_order(op);
|
||||
_orders = orders;
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
_bs.Subscribe(badge => {
|
||||
@@ -143,7 +146,7 @@ namespace mbts.spark {
|
||||
string candidate = string.Empty;
|
||||
if (a.HasValue) {
|
||||
var algo_name = a.Value.name;
|
||||
var words = algo_name.Split(delim);
|
||||
var words = algo_name.Split(delim, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (words.Length == 1) {
|
||||
var single_word = words[0];
|
||||
if (single_word.Length > 1) {
|
||||
@@ -152,7 +155,7 @@ namespace mbts.spark {
|
||||
candidate = single_word;
|
||||
}
|
||||
} else {
|
||||
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
|
||||
candidate = string.Join(string.Empty, words.Take(2).Select(w => w.Substring(0, 1)));
|
||||
}
|
||||
}
|
||||
return candidate.ToUpper();
|
||||
@@ -169,45 +172,11 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private void _set_badge_text() {
|
||||
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style);
|
||||
var fmt = MDDisplayHelper.get_value_formatter(_owner.display_quantity_divisor);
|
||||
text_lines = BadgeRender.format(side, quantity, filled, _current_badge_style, fmt);
|
||||
RaisePropertyChanged(nameof(text_lines));
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
// Log.assert(neworder.Key == id, $"OrderVM.update was called with an order with a different Key({neworder.Key}) from original({id})");
|
||||
// using (detect_property_changes()) {
|
||||
// _init(neworder);
|
||||
// }
|
||||
//}
|
||||
|
||||
public uint security_key => _owner.security.Id;
|
||||
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
|
||||
public uint? min_piq {
|
||||
@@ -259,23 +228,28 @@ namespace mbts.spark {
|
||||
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 IReadOnlyList<uint> order_keys => _orders.Keys.ToReadOnlyList();
|
||||
public IEnumerable<OrderRecordImmutable> root_orders => _root_nodes();
|
||||
|
||||
public void sync_orders(ImmutableDictionary<uint, OrderSecurity> new_orders) {
|
||||
_orders = new_orders;
|
||||
min_piq = new_orders.Count == 0 ? null : new_orders.Values.Select(o => o.piq).Min();
|
||||
}
|
||||
|
||||
void _cancel_order() {
|
||||
foreach (var order in _root_nodes()) {
|
||||
model.order_gateway.cancel_order(order).ignore();
|
||||
}
|
||||
}
|
||||
|
||||
// on Stop/Limit order updates
|
||||
// When not triggered, allow for modification to StopPrice
|
||||
// else treat modification as normal (Price)
|
||||
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;
|
||||
if (order.working_quantity() <= 0) return;
|
||||
_phantom_price = new_price;
|
||||
RaisePropertyChanged(nameof(price));
|
||||
++_n_active_price_change_ops;
|
||||
if (await _owner.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, null)) {
|
||||
// wait 3 secs, then give up if price change still hasn't taken effect
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
@@ -293,14 +267,16 @@ namespace mbts.spark {
|
||||
// I checked with Eric and he guesses that what is happening is that the Popup is throwing away the grid, but the binding doesn't remove the columns from that grid
|
||||
//
|
||||
// I think returning a new OrderbookVM is okay at the moment since this Popup window will not be saving any state at this time
|
||||
var price_filter = new filters.DoubleMinMaxState();
|
||||
price_filter.minimum.value = real_price;
|
||||
price_filter.maximum.value = real_price;
|
||||
|
||||
var ticks = _owner.spec.full_ticks_from_zero(real_price);
|
||||
if (!ticks.HasValue) {
|
||||
// this is an unexpected code path, but not necessarily a reason to crash, so log a warning and return null
|
||||
return null;
|
||||
}
|
||||
var min_price = _owner.spec.tickup(0, ticks.Value);
|
||||
var max_price = _owner.spec.tickup(min_price, 1);
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_owner.security),
|
||||
price = price_filter,
|
||||
security_criteria = SecurityCriteria.from_securities(_owner.security),
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
order_types = AlgoID.exchange_algo_keys
|
||||
},
|
||||
@@ -308,7 +284,8 @@ namespace mbts.spark {
|
||||
use_defaults: false
|
||||
);
|
||||
var settings_man = new WindowSettingsManager<OrderBookGridSettings>.DisconnectedImpl(state.orderbook_grid);
|
||||
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true);
|
||||
var obvm = new OrderBookVM(_owner.bus, null, _owner.model.order_gateway, settings_man, true,
|
||||
global_filter: o => o.order.order.ui_price() >= min_price - MathUtils.epsilon && o.order.order.ui_price() <= max_price - MathUtils.epsilon);
|
||||
_owner.settings.Subscribe(s => settings_man.instance_settings = settings_man.instance_settings.with(s.mini_ob.value));
|
||||
obvm.columns.Insert(0, new OrderBookVM.AttachDetachColumn(typeof(MiniOrderBookView)));
|
||||
obvm.columns.Insert(0, new OrderBookVM.CommitColumn());
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
using bts.proto;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using mbts.ui.utility.text;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderPanelPricePointVM : ViewModelBase, IValueFactoryVM<LadderPanelPricePointSettings.ItemSettings> {
|
||||
Color _background_color = Colors.Transparent, _font_color = Colors.Black;
|
||||
FontFamily _font_family = TextUtils.StandardFontFamily;
|
||||
double _font_size = FontSizes.Small;
|
||||
FontWeight _font_weight = FontWeights.Bold;
|
||||
bool _show = true, _show_label = true;
|
||||
|
||||
public Color background_color { get => _background_color; set => Set(ref _background_color, value); }
|
||||
public Color font_color { get => _font_color; set => Set(ref _font_color, value); }
|
||||
public FontFamily font_family { get => _font_family; set => Set(ref _font_family, value); }
|
||||
public double font_size { get => _font_size; set => Set(ref _font_size, value); }
|
||||
public FontWeight font_weight { get => _font_weight; set => Set(ref _font_weight, value); }
|
||||
public bool show {
|
||||
get => _show;
|
||||
set {
|
||||
if (Set(ref _show, value))
|
||||
RaisePropertyChanged(nameof(actual_show_label));
|
||||
}
|
||||
}
|
||||
public bool show_label {
|
||||
get => _show_label;
|
||||
set {
|
||||
if (Set(ref _show_label, value))
|
||||
RaisePropertyChanged(nameof(actual_show_label));
|
||||
}
|
||||
}
|
||||
public bool actual_show_label => _show && _show_label;
|
||||
|
||||
public LadderPanelPricePointSettings.ItemSettings create() =>
|
||||
new LadderPanelPricePointSettings.ItemSettings {
|
||||
font = new FontParamsProto {
|
||||
font_color = font_color.rgb_color(),
|
||||
font_size = font_size,
|
||||
typeface = new TypefaceProto(font_family.name_or_default(), font_weight.ToOpenTypeWeight())
|
||||
},
|
||||
background_color = background_color.rgb_color(),
|
||||
show_label = show_label
|
||||
};
|
||||
|
||||
public void load_from(LadderPanelPricePointSettings.ItemSettings value) {
|
||||
if (value is null)
|
||||
return;
|
||||
load_from(value.font);
|
||||
show_label = value.show_label;
|
||||
background_color = value.background_color.argb_color();
|
||||
}
|
||||
|
||||
protected void load_from(FontParamsProto font) {
|
||||
if (font is null) {
|
||||
font_weight = FontWeights.Bold;
|
||||
font_size = FontSizes.Small;
|
||||
show_label = true;
|
||||
} else {
|
||||
font_family = string.IsNullOrEmpty(font.font_family) ?
|
||||
TextUtils.StandardFontFamily :
|
||||
new FontFamily(font.font_family);
|
||||
font_weight = FontWeight.FromOpenTypeWeight(font.font_weight);
|
||||
font_size = font.font_size;
|
||||
font_color = font.font_color.argb_color();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LadderPanelRelativePricePointVM : LadderPanelPricePointVM, IValueFactoryVM<LadderPanelPricePointSettings.DaySettings> {
|
||||
Color
|
||||
_negative_font_color = Colors.Red,
|
||||
_negative_background_color = Colors.Transparent,
|
||||
_positive_font_color = Colors.Green,
|
||||
_positive_background_color = Colors.Transparent;
|
||||
|
||||
// negative
|
||||
public Color negative_font_color { get => _negative_font_color; set => Set(ref _negative_font_color, value); }
|
||||
public Color negative_background_color { get => _negative_background_color; set => Set(ref _negative_background_color, value); }
|
||||
// positive
|
||||
public Color positive_font_color { get => _positive_font_color; set => Set(ref _positive_font_color, value); }
|
||||
public Color positive_background_color { get => _positive_background_color; set => Set(ref _positive_background_color, value); }
|
||||
|
||||
public void load_from(LadderPanelPricePointSettings.DaySettings value) {
|
||||
if (value is null)
|
||||
return;
|
||||
load_from(value.font);
|
||||
show_label = value.show_label;
|
||||
background_color = value.background_color.argb_color();
|
||||
// negative
|
||||
negative_font_color = value.negative_font_color.argb_color();
|
||||
negative_background_color = value.negative_background_color.argb_color();
|
||||
// positive
|
||||
positive_font_color = value.positive_font_color.argb_color();
|
||||
positive_background_color = value.positive_background_color.argb_color();
|
||||
}
|
||||
|
||||
public new LadderPanelPricePointSettings.DaySettings create() {
|
||||
var base_settings = base.create();
|
||||
return new LadderPanelPricePointSettings.DaySettings {
|
||||
font = base_settings.font,
|
||||
show_label = base_settings.show_label,
|
||||
background_color = base_settings.background_color,
|
||||
// negative
|
||||
negative_font_color = negative_font_color.rgb_color(),
|
||||
negative_background_color = negative_background_color.rgb_color(),
|
||||
// positive
|
||||
positive_font_color = positive_font_color.rgb_color(),
|
||||
positive_background_color = positive_background_color.rgb_color()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.persist.algo;
|
||||
using bts.proto;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.color_picker;
|
||||
@@ -14,7 +16,9 @@ using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
@@ -28,6 +32,10 @@ using TextUtils = mbts.ui.utility.text.TextUtils;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderSettingsVM : DisposableViewModelBase, IWindowSettingsVM<LadderSettings>, IValueFactory<LadderSettings>, IObservable<LadderSettings>, ITemplateSelectorKey {
|
||||
public static Type PanelPricePointType => typeof(PanelPricePointVM);
|
||||
public static Type PanelRelativePricePointType => typeof(PanelRelativePricePointVM);
|
||||
public static Type PanelVisibilityType => typeof(OrderEntryPanelItemVisibilityVM);
|
||||
|
||||
readonly Property<LadderSettings.BadgeStyle> _badge_style;
|
||||
readonly Property<uint> _alternate_order_size, _max_clip;
|
||||
readonly DelegateProperty<double> _row_height;
|
||||
@@ -37,13 +45,19 @@ namespace mbts.spark {
|
||||
readonly ObservableKeyedCollection<AccountTuple?, uint> _accounts = new ObservableKeyedCollection<AccountTuple?, uint>(AccountTuple.nullable_key_fn);
|
||||
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _algo_definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
|
||||
readonly OrdersModel _model;
|
||||
readonly SecurityImmutable _security;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map =
|
||||
DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
uint? _desired_account;
|
||||
AlgoID? _desired_algo;
|
||||
TimeInForce _default_time_in_force;
|
||||
LadderSettings.NetChangeTerms _price_format;
|
||||
LadderSettings.PriceDisplayFormat _price_display_format;
|
||||
double _order_entry_panel_width, _zoom_factor;
|
||||
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip;
|
||||
bool _auto_clear, _show_warn_on_cross, _enforce_max_clip, _enforce_max_settlement_cross_ticks;
|
||||
int _max_settlement_cross_ticks;
|
||||
uint _ticks_per_row;
|
||||
LadderSettings _current;
|
||||
LadderColumnSettingsVM
|
||||
_orders = new OrdersColumnSettingsVM(),
|
||||
@@ -57,17 +71,11 @@ namespace mbts.spark {
|
||||
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
|
||||
_ask_mkt_line = new LadderMarketBreakLineVM("Ask"),
|
||||
_mid_mkt_line = new LadderMarketBreakLineVM("Mid"),
|
||||
_session_low_mkt_line = new LadderMarketBreakLineVM("Session Low", 2, 1),
|
||||
_session_high_mkt_line = new LadderMarketBreakLineVM("Session High", 2, 1, false),
|
||||
_session_range_fill = new LadderMarketBreakLineVM("Session Range", 0, 0, false);
|
||||
_session_low_mkt_line = new LadderMarketBreakLineVM("Session Low", 2, 1, can_edit_refresh_mode: false),
|
||||
_session_high_mkt_line = new LadderMarketBreakLineVM("Session High", 2, 1, false, can_edit_refresh_mode: false),
|
||||
_session_range_fill = new LadderMarketBreakLineVM("Session Range", 0, 0, false, can_edit_refresh_mode: false);
|
||||
OrderEntryPanelItemVisibilityVM
|
||||
_zoom_slider_visbility = new OrderEntryPanelItemVisibilityVM("Zoom Slider"),
|
||||
_day_visibility = new OrderEntryPanelItemVisibilityVM("Day Change"),
|
||||
_high_visibility = new OrderEntryPanelItemVisibilityVM("High"),
|
||||
_low_visibility = new OrderEntryPanelItemVisibilityVM("Low"),
|
||||
_open_visibility = new OrderEntryPanelItemVisibilityVM("Open"),
|
||||
_settle_visibility = new OrderEntryPanelItemVisibilityVM("Settle"),
|
||||
_volume_visibility = new OrderEntryPanelItemVisibilityVM("Volume"),
|
||||
_account_visibility = new OrderEntryPanelItemVisibilityVM("Account"),
|
||||
_tif_visibility = new OrderEntryPanelItemVisibilityVM("Time In Force"),
|
||||
_order_type_visibility = new OrderEntryPanelItemVisibilityVM("Order Type"),
|
||||
@@ -81,10 +89,18 @@ namespace mbts.spark {
|
||||
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
|
||||
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All"),
|
||||
_status_message_visibility = new OrderEntryPanelItemVisibilityVM("Status Message");
|
||||
PanelRelativePricePointVM
|
||||
_day_change = new PanelRelativePricePointVM("Day Change");
|
||||
PanelPricePointVM
|
||||
_session_high = new PanelPricePointVM("High"),
|
||||
_session_low = new PanelPricePointVM("Low"),
|
||||
_open = new PanelPricePointVM("Open"),
|
||||
_settlement = new PanelPricePointVM("Settle"),
|
||||
_volume_panel_item = new PanelPricePointVM("Volume");
|
||||
|
||||
public LadderSettingsVM(OrdersModel model, SecurityImmutable security) {
|
||||
_model = model;
|
||||
is_synthetic = security.ContractCase == Security.ContractOneofCase.Synthetic;
|
||||
_security = security;
|
||||
favorite_algos_vm = new FavoriteAlgosVM(_algo_definitions);
|
||||
size_presets = new ObservableEditableCollection<IMutableValueWrapperVM<uint>>(_create_size_preset_vm);
|
||||
size_presets.CollectionChanged += (s, e) => RaisePropertyChanged(nameof(size_presets_text));
|
||||
@@ -106,29 +122,46 @@ namespace mbts.spark {
|
||||
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
|
||||
CustomSort = Comparer<AccountTuple>.Default.to_nullable().to_non_generic()
|
||||
};
|
||||
// cmds
|
||||
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
|
||||
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
|
||||
// update
|
||||
_update = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.execute;
|
||||
foreach (var c in _observable_components)
|
||||
c.PropertyChanged += on_prop_changed;
|
||||
favorite_algos_vm.items.CollectionChanged += on_favorite_algo_items_CollectionChanged;
|
||||
_algo_definitions.AddRange(_model.factory.default_algos(security).Values.Select(algo_factory));
|
||||
_algo_definitions.AddRange(_model.get_permitted_algos(security).factory_types
|
||||
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
|
||||
.Select(AlgoData.from_custom_def)
|
||||
.Select(algo_factory));
|
||||
_algo_definitions.AddRange(_model.get_permitted_algos(security).dynamic_types
|
||||
.Select(dyn_type => new CustomOrderDefinitionImmutable(new CustomOrderDefinition {
|
||||
Dynamic = _model.dynamic_helper.create_def(dyn_type)
|
||||
}))
|
||||
.Select(AlgoData.from_custom_def)
|
||||
.Select(algo_factory));
|
||||
_dispose = new CompositeDisposable(
|
||||
_model.factory.subscribe_accounts().OnDispatcher().Subscribe(acc_map => {
|
||||
_accounts.SyncWithDictionary(MappedDictionary.create(acc_map, account_factory));
|
||||
_try_select_desired_account();
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, connect_favorite_algo),
|
||||
ObservableAutoWrapper.connect_item_hooks(favorite_algos_vm.items, observe_prop_changes),
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(update => {
|
||||
AlgoCollectionHelper.update_persisted_definitions(_algo_definitions, update, algo_factory);
|
||||
_try_select_desired_algo();
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(fair_value_line_settings_vm.items, item => {
|
||||
_update.execute();
|
||||
var prop_changes_sub = observe_prop_changes(item);
|
||||
return Disposable.Create(_update.execute, prop_changes_sub.Dispose);
|
||||
})
|
||||
) { dispose_resources };
|
||||
// local methods
|
||||
void on_font_size_changed(double oldval, double newval) => _row_height.refresh();
|
||||
void on_prop_changed(object sender, PropertyChangedEventArgs e) => _update.execute();
|
||||
void on_favorite_algo_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => _update.execute();
|
||||
IDisposable connect_favorite_algo(FavoriteAlgoItem item) {
|
||||
IDisposable observe_prop_changes(INotifyPropertyChanged item) {
|
||||
item.PropertyChanged += on_prop_changed;
|
||||
return Disposable.Create(() => item.PropertyChanged -= on_prop_changed);
|
||||
}
|
||||
@@ -141,7 +174,27 @@ namespace mbts.spark {
|
||||
c.PropertyChanged -= on_prop_changed;
|
||||
}
|
||||
AccountTuple? account_factory(uint key, string name) => new AccountTuple(key, name);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data, is_synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange);
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
|
||||
void increment_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < 100; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security.ContractSpec, ticks_per_row + i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void decrement_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < tpr; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security.ContractSpec, tpr - i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
@@ -164,6 +217,16 @@ namespace mbts.spark {
|
||||
get => _alternate_order_size.Value;
|
||||
set => _alternate_order_size.Value = value;
|
||||
}
|
||||
public uint ticks_per_row {
|
||||
get => _ticks_per_row;
|
||||
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
|
||||
}
|
||||
public ICommand tick_up_ticks_per_row_cmd { get; }
|
||||
public ICommand tick_down_ticks_per_row_cmd { get; }
|
||||
// since settings profiles may be shared among ladders
|
||||
// of different securities/tick sizes, the ticks per row setting
|
||||
// may need to be rounded to a number that is a factor of $1.00
|
||||
public uint? rounded_ticks_per_row => TickUtils.fit_ticks_per_row_to_dollars(_security.ContractSpec, ticks_per_row);
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
@@ -189,12 +252,22 @@ namespace mbts.spark {
|
||||
public bool show_warn_on_cross { get => _show_warn_on_cross; set => Set(ref _show_warn_on_cross, value); }
|
||||
public bool enforce_max_clip { get => _enforce_max_clip; set => Set(ref _enforce_max_clip, value); }
|
||||
public uint max_clip { get => _max_clip.Value; set => _max_clip.Value = value; }
|
||||
public bool enforce_max_settlement_cross_ticks { get => _enforce_max_settlement_cross_ticks; set => Set(ref _enforce_max_settlement_cross_ticks, value); }
|
||||
public int max_settlement_cross_ticks { get => _max_settlement_cross_ticks; set => Set(ref _max_settlement_cross_ticks, value); }
|
||||
|
||||
// favorites
|
||||
public FavoriteAlgosVM favorite_algos_vm { get; }
|
||||
|
||||
public double row_height => _row_height.Value;
|
||||
public bool is_synthetic { get; }
|
||||
public bool is_synthetic => _security.ContractCase == Security.ContractOneofCase.Synthetic;
|
||||
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
Set(ref _security_map, value);
|
||||
fair_value_line_settings_vm.security_map = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LadderColumnSettingsVM> column_items {
|
||||
get {
|
||||
@@ -233,15 +306,18 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<OrderEntryPanelItemVisibilityVM> order_entry_panel_items {
|
||||
public FairValueLineSettingsVM fair_value_line_settings_vm { get; } = new
|
||||
FairValueLineSettingsVM();
|
||||
|
||||
public IEnumerable order_entry_panel_items {
|
||||
get {
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _day_visibility;
|
||||
yield return _high_visibility;
|
||||
yield return _low_visibility;
|
||||
yield return _open_visibility;
|
||||
yield return _settle_visibility;
|
||||
yield return _volume_visibility;
|
||||
yield return _day_change;
|
||||
yield return _session_high;
|
||||
yield return _session_low;
|
||||
yield return _open;
|
||||
yield return _settlement;
|
||||
yield return _volume_panel_item;
|
||||
yield return _account_visibility;
|
||||
yield return _tif_visibility;
|
||||
yield return _order_type_visibility;
|
||||
@@ -280,12 +356,12 @@ namespace mbts.spark {
|
||||
yield return _session_range_fill;
|
||||
// left panel
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _day_visibility;
|
||||
yield return _high_visibility;
|
||||
yield return _low_visibility;
|
||||
yield return _open_visibility;
|
||||
yield return _settle_visibility;
|
||||
yield return _volume_visibility;
|
||||
yield return _day_change;
|
||||
yield return _session_high;
|
||||
yield return _session_low;
|
||||
yield return _open;
|
||||
yield return _settlement;
|
||||
yield return _volume_panel_item;
|
||||
yield return _account_visibility;
|
||||
yield return _tif_visibility;
|
||||
yield return _order_type_visibility;
|
||||
@@ -311,6 +387,7 @@ namespace mbts.spark {
|
||||
_current = settings;
|
||||
badge_style = settings.badge_style;
|
||||
alternate_order_size = settings.alternate_order_size;
|
||||
ticks_per_row = settings.ticks_per_row;
|
||||
favorite_algos_vm.load_from(settings.favorite_algos);
|
||||
size_display.load_from(settings.size_display);
|
||||
size_presets.SetItems(settings.calculator_buttons.empty_if_null().Select(s => _create_size_preset_vm((uint)s)));
|
||||
@@ -330,6 +407,14 @@ namespace mbts.spark {
|
||||
_session_low_mkt_line.assign_from(_current.session_low_line);
|
||||
_session_high_mkt_line.assign_from(_current.session_high_line);
|
||||
_session_range_fill.assign_from(_current.session_range_fill);
|
||||
// fair value lines
|
||||
fair_value_line_settings_vm.items.SetItems(
|
||||
_current.fair_value_lines.Select(fvl => {
|
||||
var fvl_vm = new FairValueLineItemSettingsVM();
|
||||
fvl_vm.assign_from(fvl);
|
||||
fvl_vm.selected_security_key = fvl.security_key;
|
||||
return fvl_vm;
|
||||
}));
|
||||
// left-panel (defaults)
|
||||
default_time_in_force = settings.default_time_in_force;
|
||||
// - account
|
||||
@@ -344,12 +429,18 @@ namespace mbts.spark {
|
||||
zoom_factor = settings.zoom_factor;
|
||||
// left-panel (show/hide)
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_day_visibility.show = settings.day_visible;
|
||||
_high_visibility.show = settings.high_visible;
|
||||
_low_visibility.show = settings.low_visible;
|
||||
_open_visibility.show = settings.open_visible;
|
||||
_settle_visibility.show = settings.settle_visible;
|
||||
_volume_visibility.show = settings.volume_visible;
|
||||
_day_change.show = settings.day_visible;
|
||||
_day_change.load_from(settings.panel_price_point_settings.day_change);
|
||||
_session_high.show = settings.high_visible;
|
||||
_session_high.load_from(settings.panel_price_point_settings.session_high);
|
||||
_session_low.show = settings.low_visible;
|
||||
_session_low.load_from(settings.panel_price_point_settings.session_low);
|
||||
_open.show = settings.open_visible;
|
||||
_open.load_from(settings.panel_price_point_settings.open);
|
||||
_settlement.show = settings.settle_visible;
|
||||
_settlement.load_from(settings.panel_price_point_settings.settlement);
|
||||
_volume_panel_item.show = settings.volume_visible;
|
||||
_volume_panel_item.load_from(settings.panel_price_point_settings.volume);
|
||||
_account_visibility.show = settings.account_visible;
|
||||
_tif_visibility.show = settings.tif_visible;
|
||||
_order_type_visibility.show = settings.order_type_visible;
|
||||
@@ -369,6 +460,10 @@ namespace mbts.spark {
|
||||
show_warn_on_cross = settings.show_warn_on_cross;
|
||||
enforce_max_clip = settings.enforce_max_clip;
|
||||
max_clip = settings.max_clip;
|
||||
if (settings.ladder_order_limits != null) {
|
||||
enforce_max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.is_enabled;
|
||||
max_settlement_cross_ticks = settings.ladder_order_limits.max_settlement_cross_ticks.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,6 +483,7 @@ namespace mbts.spark {
|
||||
}
|
||||
clone.badge_style = badge_style;
|
||||
clone.alternate_order_size = alternate_order_size;
|
||||
clone.ticks_per_row = ticks_per_row;
|
||||
clone.favorite_algos = favorite_algos_vm.create();
|
||||
clone.size_display = size_display.create();
|
||||
clone.calculator_buttons = size_presets.Select(vm => (int)vm.value).ToArray();
|
||||
@@ -407,6 +503,20 @@ namespace mbts.spark {
|
||||
clone.session_low_line = _session_low_mkt_line.get_settings();
|
||||
clone.session_high_line = _session_high_mkt_line.get_settings();
|
||||
clone.session_range_fill = _session_range_fill.get_settings();
|
||||
// fair value lines
|
||||
clone.fair_value_lines = fair_value_line_settings_vm.items
|
||||
.Select(fvl => fvl.create())
|
||||
.Where(fvl => fvl.security_key != 0)
|
||||
.ToList();
|
||||
// left-panel price items
|
||||
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
|
||||
day_change = _day_change.create(),
|
||||
open = _open.create(),
|
||||
session_low = _session_low.create(),
|
||||
session_high = _session_high.create(),
|
||||
volume = _volume_panel_item.create(),
|
||||
settlement = _settlement.create(),
|
||||
};
|
||||
// left-panel (defaults)
|
||||
clone.default_time_in_force = default_time_in_force;
|
||||
// - account
|
||||
@@ -421,12 +531,12 @@ namespace mbts.spark {
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.day_visible = _day_visibility.show;
|
||||
clone.high_visible = _high_visibility.show;
|
||||
clone.low_visible = _low_visibility.show;
|
||||
clone.open_visible = _open_visibility.show;
|
||||
clone.settle_visible = _settle_visibility.show;
|
||||
clone.volume_visible = _volume_visibility.show;
|
||||
clone.day_visible = _day_change.show;
|
||||
clone.high_visible = _session_high.show;
|
||||
clone.low_visible = _session_low.show;
|
||||
clone.open_visible = _open.show;
|
||||
clone.settle_visible = _settlement.show;
|
||||
clone.volume_visible = _volume_panel_item.show;
|
||||
clone.account_visible = _account_visibility.show;
|
||||
clone.tif_visible = _tif_visibility.show;
|
||||
clone.order_type_visible = _order_type_visibility.show;
|
||||
@@ -447,6 +557,10 @@ namespace mbts.spark {
|
||||
clone.show_warn_on_cross = show_warn_on_cross;
|
||||
clone.enforce_max_clip = enforce_max_clip;
|
||||
clone.max_clip = max_clip;
|
||||
if (clone.ladder_order_limits == null)
|
||||
clone.ladder_order_limits = new LadderOrderLimits();
|
||||
clone.ladder_order_limits.max_settlement_cross_ticks =
|
||||
new ToggleInt32Proto(max_settlement_cross_ticks, enforce_max_settlement_cross_ticks);
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -546,8 +660,8 @@ namespace mbts.spark {
|
||||
font_size = settings.font_size;
|
||||
width = settings.width;
|
||||
show = settings.show;
|
||||
foreground = settings.foreground.rgb_color();
|
||||
background = settings.background.rgb_color();
|
||||
foreground = settings.foreground.argb_color();
|
||||
background = settings.background.argb_color();
|
||||
}
|
||||
|
||||
public LadderColumnSettings get_settings() {
|
||||
@@ -557,8 +671,8 @@ namespace mbts.spark {
|
||||
font_weight = font_weight.ToOpenTypeWeight(),
|
||||
width = width,
|
||||
show = show,
|
||||
foreground = new BTSColor(foreground).ToUint(),
|
||||
background = new BTSColor(background).ToUint()
|
||||
foreground = foreground.rgb_color(),
|
||||
background = background.rgb_color()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -578,12 +692,14 @@ namespace mbts.spark {
|
||||
public class LadderMarketBreakLineVM : ViewModelBase {
|
||||
readonly Property<Color> _color;
|
||||
readonly Property<bool> _show;
|
||||
LadderMarketBreakLine.RefreshMode _refresh_mode;
|
||||
|
||||
public LadderMarketBreakLineVM(string name, int column_index = 0, int column_span = 5, bool show_session_range = true) {
|
||||
public LadderMarketBreakLineVM(string name, int column_index = 0, int column_span = 5, bool show_session_range = true, bool can_edit_refresh_mode = true) {
|
||||
this.name = name;
|
||||
this.column_index = column_index;
|
||||
this.column_span = column_span;
|
||||
this.show_session_range = show_session_range;
|
||||
this.can_edit_refresh_mode = can_edit_refresh_mode;
|
||||
palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
|
||||
_color = backing(nameof(color), Colors.Black);
|
||||
_show = backing(nameof(show), false);
|
||||
@@ -595,21 +711,25 @@ namespace mbts.spark {
|
||||
public Color color { get => _color.Value; set => _color.Value = value; }
|
||||
public bool show { get => _show.Value; set => _show.Value = value; }
|
||||
public bool show_session_range { get; }
|
||||
public bool can_edit_refresh_mode { get; }
|
||||
public LadderMarketBreakLine.RefreshMode refresh_mode { get => _refresh_mode; set => Set(ref _refresh_mode, value); }
|
||||
public ObservableCollection<ColorPaletteVM> palettes { get; }
|
||||
|
||||
public void assign_from(LadderMarketBreakLine settings) {
|
||||
color = settings.color.rgb_color();
|
||||
show = settings.show;
|
||||
refresh_mode = settings.refresh_mode;
|
||||
}
|
||||
|
||||
public LadderMarketBreakLine get_settings() =>
|
||||
new LadderMarketBreakLine {
|
||||
color = new BTSColor(color).ToUint(),
|
||||
show = show
|
||||
show = show,
|
||||
refresh_mode = refresh_mode
|
||||
};
|
||||
}
|
||||
|
||||
public class OrderEntryPanelItemVisibilityVM : ViewModelBase {
|
||||
public class OrderEntryPanelItemVisibilityVM : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly Property<bool> _show;
|
||||
|
||||
public OrderEntryPanelItemVisibilityVM(string name) {
|
||||
@@ -620,6 +740,63 @@ namespace mbts.spark {
|
||||
public string name { get; }
|
||||
public bool show { get => _show.Value; set => _show.Value = value; }
|
||||
|
||||
string ITemplateSelectorKey.key => LadderSettingsPanelTemplateKeys.VisibilityItem;
|
||||
}
|
||||
|
||||
public class PanelPricePointVM : LadderPanelPricePointVM, ITemplateSelectorKey {
|
||||
ObservableCollection<ColorPaletteVM>
|
||||
_font_color_palettes,
|
||||
_background_color_palettes;
|
||||
|
||||
public PanelPricePointVM(string name) =>
|
||||
this.name = name;
|
||||
|
||||
public string name { get; }
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> font_color_palettes => _font_color_palettes ??
|
||||
(_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> background_color_palettes => _background_color_palettes ??
|
||||
(_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
string ITemplateSelectorKey.key => LadderSettingsPanelTemplateKeys.PricePoint;
|
||||
}
|
||||
|
||||
public class PanelRelativePricePointVM : LadderPanelRelativePricePointVM, ITemplateSelectorKey {
|
||||
ObservableCollection<ColorPaletteVM>
|
||||
_negative_font_color_palettes,
|
||||
_negative_background_color_palettes,
|
||||
_neutral_font_color_palettes,
|
||||
_neutral_background_color_palettes,
|
||||
_positive_font_color_palettes,
|
||||
_positive_background_color_palettes;
|
||||
|
||||
public PanelRelativePricePointVM(string name) =>
|
||||
this.name = name;
|
||||
|
||||
public string name { get; }
|
||||
//negative
|
||||
public ObservableCollection<ColorPaletteVM> negative_font_color_palettes => _negative_font_color_palettes ??
|
||||
(_negative_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> negative_background_color_palettes => _negative_background_color_palettes ??
|
||||
(_negative_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
// neutral
|
||||
public ObservableCollection<ColorPaletteVM> neutral_font_color_palettes => _neutral_font_color_palettes ??
|
||||
(_neutral_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> neutral_background_color_palettes => _neutral_background_color_palettes ??
|
||||
(_neutral_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
// positive
|
||||
public ObservableCollection<ColorPaletteVM> positive_font_color_palettes => _positive_font_color_palettes ??
|
||||
(_positive_font_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
public ObservableCollection<ColorPaletteVM> positive_background_color_palettes => _positive_background_color_palettes ??
|
||||
(_positive_background_color_palettes = ColorPalette.all_presets().to_viewmodels());
|
||||
|
||||
string ITemplateSelectorKey.key => LadderSettingsPanelTemplateKeys.RelativePricePoint;
|
||||
}
|
||||
|
||||
public class VolumeColumnVM : ViewModelBase {
|
||||
@@ -746,15 +923,17 @@ namespace mbts.spark {
|
||||
readonly IObservableCollection<AlgoDefinitionVM> _algo_definitions;
|
||||
readonly ObservableCollection<FavoriteAlgoItem> _raw_items = new ObservableCollection<FavoriteAlgoItem>();
|
||||
int _column_count = 2;
|
||||
HashSet<AlgoPersistedKey> _sticky_algo_keys;
|
||||
IDisposable _sticky_algos_hook;
|
||||
ImmutableHashSet<AlgoPersistedKey>
|
||||
_sticky_algo_keys,
|
||||
_auto_revert_algo_keys;
|
||||
IDisposable _algo_data_hook;
|
||||
|
||||
public FavoriteAlgosVM(IObservableCollection<AlgoDefinitionVM> algo_definitions) {
|
||||
_algo_definitions = algo_definitions;
|
||||
items = new ObservableAggregateCollection<FavoriteAlgoItem>(_raw_items, new ObservableCollection<FavoriteAlgoItem> { FavoriteAlgoItem.AddButtonPlaceholderItem });
|
||||
add_item_cmd = new DelegateCommand(() => _raw_items.Add(_item_factory()));
|
||||
remove_item_cmd = new DelegateCommand<FavoriteAlgoItem>(fai => _raw_items.Remove(fai));
|
||||
_dispose = Disposable.Create(() => Disposable.dispose(ref _sticky_algos_hook));
|
||||
_dispose = Disposable.Create(() => Disposable.dispose(ref _algo_data_hook));
|
||||
}
|
||||
|
||||
public ObservableCollection<FavoriteAlgoItem> items { get; }
|
||||
@@ -771,32 +950,47 @@ namespace mbts.spark {
|
||||
fai.selector.try_select(k);
|
||||
return fai;
|
||||
}));
|
||||
_sticky_algo_keys = new HashSet<AlgoPersistedKey>(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
||||
Disposable.replace(ref _sticky_algos_hook,
|
||||
ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
|
||||
x.selector.subscribe_value().Subscribe(algo => {
|
||||
if (algo != null &&
|
||||
algo.key.is_persisted &&
|
||||
(x.is_sticky = _sticky_algo_keys.Remove(new AlgoPersistedKey(algo.key.persisted))) &&
|
||||
_sticky_algo_keys.Count == 0)
|
||||
//
|
||||
Disposable.dispose(ref _sticky_algos_hook);
|
||||
})));
|
||||
_algo_data_hook?.Dispose();
|
||||
_sticky_algo_keys = ImmutableHashSet.CreateRange(value.stickied_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
||||
_auto_revert_algo_keys = ImmutableHashSet.CreateRange(value.auto_revert_algos.Where(k => k.is_persisted).Select(x => new AlgoPersistedKey(x.persisted)));
|
||||
var sticky_algos = _sticky_algo_keys;
|
||||
var auto_revert_algos = _auto_revert_algo_keys;
|
||||
_algo_data_hook = ObservableAutoWrapper.connect_item_hooks(_raw_items, x =>
|
||||
x.selector.subscribe_value().Subscribe(algo => {
|
||||
if (algo != null && algo.key.is_persisted) {
|
||||
var pk = new AlgoPersistedKey(algo.key.persisted);
|
||||
// sticky
|
||||
x.is_sticky = _sticky_algo_keys.Contains(pk);
|
||||
sticky_algos = sticky_algos.Remove(pk);
|
||||
// auto revert
|
||||
x.auto_revert = _auto_revert_algo_keys.Contains(pk);
|
||||
auto_revert_algos = auto_revert_algos.Remove(pk);
|
||||
if (sticky_algos.Count == 0 && auto_revert_algos.Count == 0)
|
||||
Disposable.dispose(ref _algo_data_hook);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public LadderFavoriteAlgosSettings create() =>
|
||||
new LadderFavoriteAlgosSettings(_raw_items
|
||||
public LadderFavoriteAlgosSettings create() {
|
||||
return new LadderFavoriteAlgosSettings(_raw_items
|
||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||
.Select(k => k.Value)
|
||||
.ToArray(),
|
||||
column_count,
|
||||
// sticky
|
||||
build_filtered_algo_array(fai => fai.is_sticky),
|
||||
// auto revert
|
||||
build_filtered_algo_array(fai => fai.auto_revert));
|
||||
|
||||
AlgoKey[] build_filtered_algo_array(Func<FavoriteAlgoItem, bool> filter) =>
|
||||
_raw_items
|
||||
.Where(fai => fai.is_sticky)
|
||||
.Where(filter)
|
||||
.Select(fai => fai.algo_definition?.key ?? fai.selector.desired_key)
|
||||
.Where(k => k.HasValue && k.Value.is_persisted)
|
||||
.Select(k => k.Value)
|
||||
.ToArray());
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
FavoriteAlgoItem _item_factory() =>
|
||||
new FavoriteAlgoItem(_algo_definitions, new ListCollectionView<AlgoDefinitionVM>(_algo_definitions) {
|
||||
@@ -809,7 +1003,9 @@ namespace mbts.spark {
|
||||
public static readonly FavoriteAlgoItem AddButtonPlaceholderItem = new _AddButtonPlaceholderItem();
|
||||
|
||||
internal readonly KeyedItemSelector<AlgoDefinitionVM, AlgoKey> selector;
|
||||
bool _is_sticky;
|
||||
bool
|
||||
_auto_revert,
|
||||
_is_sticky;
|
||||
|
||||
protected FavoriteAlgoItem() { }
|
||||
|
||||
@@ -820,6 +1016,7 @@ namespace mbts.spark {
|
||||
|
||||
public ICollectionView<AlgoDefinitionVM> algo_definitions_view { get; }
|
||||
public AlgoDefinitionVM algo_definition { get => selector.value; set => selector.value = value; }
|
||||
public bool auto_revert { get => _auto_revert; set => Set(ref _auto_revert, value); }
|
||||
public bool is_sticky { get => _is_sticky; set => Set(ref _is_sticky, value); }
|
||||
|
||||
class _AddButtonPlaceholderItem : FavoriteAlgoItem, ITemplateSelectorKey {
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.proto;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.collections;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
@@ -25,16 +31,25 @@ namespace mbts.spark {
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly LadderModel _model;
|
||||
readonly Property<double> _high, _low, _change, _open, _settlement, _left_panel_width, _zoom_factor;
|
||||
//readonly Property<double> _window_width, _window_height;
|
||||
readonly Property<double> _session_high, _session_low, _day_change, _open, _settlement, _left_panel_width, _zoom_factor;
|
||||
readonly Property<uint> _orders_column_width, _bid_column_width, _price_column_width, _ask_column_width, _last_column_width, _volume_column_width, _piq_column_width;
|
||||
readonly Property<ulong> _volume;
|
||||
readonly Property<bool> _auto_clear;
|
||||
readonly PropertyAutoWrapper<LadderSettings, bool> _show_day, _show_high, _show_low, _show_open, _show_settle, _show_volume, _show_account,
|
||||
_show_tif, _show_order_type, _show_size_preset, _show_auto_clear, _show_clear, _show_cancel_buys,
|
||||
_show_cancel_sells, _show_cancel_all, _show_zoom, _show_status_message;
|
||||
readonly PropertyAutoWrapper<LadderSettings, bool>
|
||||
_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,
|
||||
_show_status_message;
|
||||
readonly PropertyAutoWrapper<LadderSettings, Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
readonly HashSet<AlgoKey> _sticky_algos = new HashSet<AlgoKey>();
|
||||
readonly ObservableCollection<FairValueLine> _fair_value_line_inputs =
|
||||
new ObservableCollection<FairValueLine>();
|
||||
double
|
||||
_position,
|
||||
_day_position,
|
||||
@@ -48,7 +63,11 @@ namespace mbts.spark {
|
||||
ulong _last_trade_time = 0;
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
ImmutableSortedDictionary<double, LadderOrderVM> _order_price_index;
|
||||
IReadOnlyDictionary<uint, OrderSecurity> _cache = DictionaryHelper.empty<uint, OrderSecurity>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem> _favorite_algo_map = ImmutableDictionary<AlgoKey, LadderFavoriteAlgoItem>.Empty;
|
||||
ContractSpecificationImmutable? _spec;
|
||||
|
||||
public static LadderVM create(IBus bus, LadderModel model, LadderSettings init, IObservable<LadderSettings> settings) =>
|
||||
new LadderVM(bus, model, init, settings, new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(model.security_md), settings.Select(s => new InlineOrderProperties(model.order_gateway, s))));
|
||||
@@ -63,13 +82,16 @@ namespace mbts.spark {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
current_settings = init;
|
||||
_order_price_index = ImmutableSortedDictionary.Create<double, LadderOrderVM>(spec.price_comparer());
|
||||
this.settings = settings;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
display_quantity_divisor = model.order_gateway.security_auxiliary_model.get_display_quantity_divisor(model.security);
|
||||
size_calculator.display_divisor = display_quantity_divisor;
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
this.order_properties_vm = order_properties_vm;
|
||||
_high = backing(nameof(high), double.NaN);
|
||||
_low = backing(nameof(low), double.NaN);
|
||||
_change = backing(nameof(change), double.NaN);
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
_open = backing(nameof(open), double.NaN);
|
||||
_settlement = backing(nameof(settlement), double.NaN);
|
||||
// init window-specific settings: these properties should not observe settings updates; they are independent from the settings dialog, so it doesn't make sense that the settings dialog should cause them to change.
|
||||
@@ -100,12 +122,6 @@ namespace mbts.spark {
|
||||
(_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,
|
||||
@@ -118,6 +134,8 @@ namespace mbts.spark {
|
||||
(_show_zoom = bool_prop(nameof(show_zoom), s => s.zoom_slider_visible)).set,
|
||||
(_show_status_message = bool_prop(nameof(show_status_message), s => s.status_message_visible)).set,
|
||||
s => {
|
||||
// invalidate spec in case 'ticks per row' changed
|
||||
_spec = null;
|
||||
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)) |
|
||||
@@ -135,8 +153,33 @@ namespace mbts.spark {
|
||||
_piq_column_width.set(s.piq_column.width);
|
||||
_volume_column_width.set(s.volume_column.general.width);
|
||||
// left panel/zoom
|
||||
day_change_style.load_from(s.panel_price_point_settings.day_change);
|
||||
day_change_style.show = s.day_visible;
|
||||
// open
|
||||
open_style.load_from(s.panel_price_point_settings.open);
|
||||
open_style.show = s.open_visible;
|
||||
// session low
|
||||
session_low_style.load_from(s.panel_price_point_settings.session_low);
|
||||
session_low_style.show = s.low_visible;
|
||||
// session high
|
||||
session_high_style.load_from(s.panel_price_point_settings.session_high);
|
||||
session_high_style.show = s.high_visible;
|
||||
// settlement
|
||||
settlement_style.load_from(s.panel_price_point_settings.settlement);
|
||||
settlement_style.show = s.settle_visible;
|
||||
// volume
|
||||
volume_style.load_from(s.panel_price_point_settings.volume);
|
||||
volume_style.show = s.volume_visible;
|
||||
// left panel gen display
|
||||
_left_panel_width.set(s.order_entry_panel_initial_width);
|
||||
_zoom_factor.set(s.zoom_factor);
|
||||
RaisePropertyChanged(nameof(day_change_font_color), nameof(day_change_background_color));
|
||||
// fair value lines
|
||||
var new_fvls = s.fair_value_lines
|
||||
.DistinctOn(fvl => fvl.security_key)
|
||||
.OrderBy(fvl => fvl.security_key)
|
||||
.ToList();
|
||||
_fair_value_line_inputs.sync_with(new_fvls);
|
||||
}
|
||||
};
|
||||
PropertyAutoWrapper<LadderSettings, bool> bool_prop(string name, Func<LadderSettings, bool> get_bool) =>
|
||||
@@ -155,79 +198,46 @@ namespace mbts.spark {
|
||||
settings_command = new DelegateCommand(() => request_settings_dialog?.Invoke());
|
||||
|
||||
orders = new ObservableCollection<LadderOrderVM>();
|
||||
_day_change.Changed += (o, n) => RaisePropertyChanged(nameof(day_change_font_color), nameof(day_change_background_color));
|
||||
var orders_source = settings.Select(s => s.ticks_per_row).DistinctUntilChanged()
|
||||
.SelectManyInSeries(tpr => {
|
||||
var spec = TickUtils.multiply_tick_size(security.ContractSpec, tpr);
|
||||
var price_comp = Comparer<double>.Create((a, b) =>
|
||||
CompareUtils.nullable_compare(spec.full_ticks_from_zero(a), spec.full_ticks_from_zero(b)));
|
||||
return _order_price_source(model.orders, price_comp, p => TickUtils.normalize_price(spec, p));
|
||||
});
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
settings.OnDispatcher().Subscribe(settings_observer),
|
||||
get_order_properties_editor_hooks(),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
// we need to hook this up to model.orders which is filtered for just the security
|
||||
model.orders.OnDispatcher().Subscribe(working => {
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_security_map),
|
||||
ObservableAutoWrapper.connect(
|
||||
_fair_value_line_inputs,
|
||||
fair_value_lines,
|
||||
fvl => new FairValueLineVM(fvl, _find_sec_md(fvl.security_key))),
|
||||
orders_source.OnDispatcher().Subscribe(tup => {
|
||||
// Here we manage N orders to 1 price slot in the ladder
|
||||
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
|
||||
_cache = working.state;
|
||||
|
||||
foreach (var os in _cache.Values) {
|
||||
var wo = os.order;
|
||||
var slot_price_target = wo.price();
|
||||
var order_type = wo.order_type();
|
||||
var filled = wo.filled();
|
||||
if (order_type == CustomOrderDefinition.CustomOneofCase.Stop) {
|
||||
// if the stop order has been submitted but hasn't been triggered
|
||||
// we draw the badge at the stop/trigger price, else the price price
|
||||
if (filled == 0) { slot_price_target = wo.stop_price(); }
|
||||
} else if (order_type == CustomOrderDefinition.CustomOneofCase.StopLimit) {
|
||||
// consistent behavior as Stop order above.
|
||||
if (filled == 0) { slot_price_target = wo.stop_limit_price(); }
|
||||
}
|
||||
|
||||
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
||||
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
||||
} else {
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, settings.Select(s => s.badge_style).OnDispatcher());
|
||||
_slot_price_vs_orders[slot_price_target] = slot_vm;
|
||||
}
|
||||
|
||||
// keep track of slot prices vs orders; we use this below to remove orders from the LadderOrderVMs
|
||||
// that are not found in this callback run
|
||||
if (!orders_used_in_this_update.TryGetValue(slot_price_target, out HashSet<uint> orders_at_level)) {
|
||||
orders_at_level = new HashSet<uint>();
|
||||
}
|
||||
orders_at_level.Add(wo.Key);
|
||||
orders_used_in_this_update[slot_price_target] = orders_at_level;
|
||||
|
||||
_cache = tup.map;
|
||||
/// remove empty price slots (the vm will be disposed automatically when it is removed from <see cref="orders"/>)
|
||||
var removed_prices = _slot_price_vs_orders.Keys.Except(tup.price_index.Keys).ToList();
|
||||
foreach (var p in removed_prices)
|
||||
_slot_price_vs_orders.Remove(p);
|
||||
// add or update current price slots
|
||||
foreach (var kv in tup.price_index) {
|
||||
_slot_price_vs_orders.AddOrUpdate(kv.Key,
|
||||
order_vm => order_vm.sync_orders(kv.Value),
|
||||
() => new LadderOrderVM(kv.Value, kv.Key, this, _bus, settings.Select(s => s.badge_style).OnDispatcher()));
|
||||
}
|
||||
|
||||
// remove any orders from the lovm that aren't in here
|
||||
foreach(var kvp in orders_used_in_this_update) {
|
||||
var slot_vm = _slot_price_vs_orders[kvp.Key];
|
||||
foreach(var existing_order_key in slot_vm.order_keys) {
|
||||
if (!kvp.Value.Contains(existing_order_key)) {
|
||||
slot_vm.remove_order(existing_order_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update dirty brushes and text
|
||||
foreach (var lovm in _slot_price_vs_orders.Values) {
|
||||
lovm.update();
|
||||
}
|
||||
|
||||
// complete removal of slots that were not updated
|
||||
// orders_used_in_this_update.Keys are the price levels in the update
|
||||
foreach (var slot_price in _slot_price_vs_orders.Keys.ToList()) {
|
||||
if (!orders_used_in_this_update.Keys.Contains(slot_price)) {
|
||||
_slot_price_vs_orders.TryGetValue(slot_price, out LadderOrderVM removal_candidate);
|
||||
if (removal_candidate != null) {
|
||||
removal_candidate.Dispose();
|
||||
}
|
||||
_slot_price_vs_orders.Remove(slot_price);
|
||||
}
|
||||
}
|
||||
|
||||
// sync the collection
|
||||
orders.sync_with(_slot_price_vs_orders.Values);
|
||||
// update badge colors, text, etc.
|
||||
foreach (var lovm in orders)
|
||||
lovm.update();
|
||||
}),
|
||||
WorkLoop.Start($"Ladder '{security}'", _tick),
|
||||
ObservableAutoWrapper.dispose_on_removed(orders, true),
|
||||
Disposable.Create(() => _on_disposed_impl?.Invoke(true))
|
||||
);
|
||||
IDisposable get_order_properties_editor_hooks() {
|
||||
@@ -239,8 +249,8 @@ namespace mbts.spark {
|
||||
.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 => {
|
||||
_sticky_algos.SetItems(s.favorite_algos.stickied_algos);
|
||||
order_properties_vm.on_favorite_algos(s.favorite_algos);
|
||||
_favorite_algo_map = s.favorite_algos.build_map();
|
||||
order_properties_vm.on_favorite_algos(_favorite_algo_map, s.favorite_algos.column_count);
|
||||
}),
|
||||
order_properties_vm,
|
||||
position_src.OnDispatcher().Subscribe(pos => {
|
||||
@@ -260,11 +270,24 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(position_display_text));
|
||||
}));
|
||||
}
|
||||
void on_security_map(IReadOnlyDictionary<uint, SecurityMD> security_map) {
|
||||
_security_map = security_map;
|
||||
foreach (var fvl_vm in fair_value_lines) {
|
||||
if (fvl_vm.sec_md.HasValue) {
|
||||
continue;
|
||||
}
|
||||
fvl_vm.sec_md = _find_sec_md(fvl_vm.sec_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IBus bus => _bus;
|
||||
|
||||
public event Action on_tick, request_settings_dialog, settings_invalidated;
|
||||
public event Action
|
||||
on_tick,
|
||||
request_settings_dialog,
|
||||
request_recenter,
|
||||
settings_invalidated;
|
||||
|
||||
public MDConsumerHandle md => _model.md;
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
@@ -299,16 +322,34 @@ namespace mbts.spark {
|
||||
remove => _on_disposed_impl -= value;
|
||||
}
|
||||
public SecurityImmutable security => _model.security;
|
||||
public ContractSpecificationImmutable spec => _spec ?? (
|
||||
_spec = TickUtils.multiply_tick_size(security.ContractSpec, current_settings?.ticks_per_row ?? 1)).Value;
|
||||
public uint display_quantity_divisor { get; }
|
||||
public LadderSettings current_settings { get; private set; }
|
||||
public double high => _high.Value;
|
||||
public double low => _low.Value;
|
||||
public double change => _change.Value;
|
||||
// left panel values
|
||||
public double session_high => _session_high.Value;
|
||||
public double session_low => _session_low.Value;
|
||||
public double day_change => _day_change.Value;
|
||||
public double open => _open.Value;
|
||||
public double settlement => _settlement.Value;
|
||||
public double position => _position;
|
||||
public double day_position => _day_position;
|
||||
public double raw_day_position => _raw_day_position;
|
||||
public ulong volume => _volume.Value;
|
||||
// left panel styles
|
||||
public LadderPanelRelativePricePointVM day_change_style { get; } = new LadderPanelRelativePricePointVM();
|
||||
public Color day_change_font_color => day_change > 0 ?
|
||||
day_change_style.positive_font_color :
|
||||
(day_change < 0 ? day_change_style.negative_font_color : day_change_style.font_color);
|
||||
public Color day_change_background_color => day_change > 0 ?
|
||||
day_change_style.positive_background_color :
|
||||
(day_change < 0 ? day_change_style.negative_background_color : day_change_style.background_color);
|
||||
public LadderPanelPricePointVM open_style { get; } = new LadderPanelPricePointVM();
|
||||
public LadderPanelPricePointVM settlement_style { get; } = new LadderPanelPricePointVM();
|
||||
public LadderPanelPricePointVM session_high_style { get; } = new LadderPanelPricePointVM();
|
||||
public LadderPanelPricePointVM session_low_style { get; } = new LadderPanelPricePointVM();
|
||||
public LadderPanelPricePointVM volume_style { get; } = new LadderPanelPricePointVM();
|
||||
//
|
||||
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
|
||||
public CalculatorLogic size_calculator { get; } = new CalculatorLogic(0);
|
||||
/// Important: <see cref="order_properties_vm"/> can be null.
|
||||
@@ -323,12 +364,6 @@ namespace mbts.spark {
|
||||
public Brush bid_foreground => _bid_foreground.view;
|
||||
public Brush ask_background => _ask_background.view;
|
||||
public Brush ask_foreground => _ask_foreground.view;
|
||||
public bool show_day => _show_day.view;
|
||||
public bool show_high => _show_high.view;
|
||||
public bool show_low => _show_low.view;
|
||||
public bool show_open => _show_open.view;
|
||||
public bool show_settle => _show_settle.view;
|
||||
public bool show_volume => _show_volume.view;
|
||||
public bool show_account => _show_account.view;
|
||||
public bool show_tif => _show_tif.view;
|
||||
public bool show_order_type => _show_order_type.view;
|
||||
@@ -343,6 +378,11 @@ namespace mbts.spark {
|
||||
public bool show_cancel_all => _show_cancel_all.view;
|
||||
public bool show_zoom => _show_zoom.view;
|
||||
public bool show_status_message => _show_status_message.view;
|
||||
|
||||
public TickDisplaySettings day_change_display_settings =>
|
||||
TickDisplaySettings.Default.with(
|
||||
pad_with_leading_zeros: false,
|
||||
show_zero_integer: false);
|
||||
// left panel scroll props
|
||||
public double left_panel_view_port_height {
|
||||
get => _left_panel_view_port_height;
|
||||
@@ -376,18 +416,22 @@ namespace mbts.spark {
|
||||
get {
|
||||
string rv = null;
|
||||
if (show_day_position)
|
||||
rv = SparkUtils.fmt_position(day_position);
|
||||
rv = SparkUtils.fmt_position(day_position / display_quantity_divisor);
|
||||
if (show_raw_day_position) {
|
||||
var raw_day_text = SparkUtils.fmt_position(raw_day_position);
|
||||
var raw_day_text = SparkUtils.fmt_position(raw_day_position / display_quantity_divisor);
|
||||
rv = rv == null ? raw_day_text : $"{rv} / {raw_day_text}";
|
||||
}
|
||||
if (show_account_position) {
|
||||
var acc_text = SparkUtils.fmt_position(position);
|
||||
rv = rv == null ? SparkUtils.fmt_position(position) : $"{rv} ({acc_text})";
|
||||
var acc_text = SparkUtils.fmt_position(position / display_quantity_divisor);
|
||||
rv = rv == null ? acc_text : $"{rv} ({acc_text})";
|
||||
}
|
||||
return rv ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<FairValueLineVM> fair_value_lines { get; } =
|
||||
new ObservableCollection<FairValueLineVM>();
|
||||
|
||||
//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); }
|
||||
@@ -397,7 +441,8 @@ namespace mbts.spark {
|
||||
public uint last_column_width { get => _last_column_width.Value; set => _last_column_width.set(value); }
|
||||
public uint piq_column_width { get => _piq_column_width.Value; set => _piq_column_width.set(value); }
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
public uint alternate_order_size_from_settings => current_settings.alternate_order_size;
|
||||
public uint actual_alternate_order_size =>
|
||||
(current_settings?.alternate_order_size ?? 0) * Math.Max(1, display_quantity_divisor);
|
||||
public bool show_warn_on_cross_from_settings => current_settings.show_warn_on_cross;
|
||||
public bool enforce_max_clip_from_settings => current_settings.enforce_max_clip;
|
||||
public uint max_clip_from_settings => current_settings.max_clip;
|
||||
@@ -407,11 +452,11 @@ namespace mbts.spark {
|
||||
double _max_left_panel_scroll_position =>
|
||||
Math.Max(0, _left_panel_extent_height - _left_panel_view_port_height);
|
||||
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
internal OrderRecordImmutable get_root_at_price(OrderSecurity op) {
|
||||
var order = op.order;
|
||||
double p = order.price();
|
||||
double p = _normalize_price(order.ui_price());
|
||||
foreach (var o in lineage(order.ParentKey)) {
|
||||
if (_equality.Equals(p, o.price()))
|
||||
if (_equality.Equals(p, _normalize_price(o.ui_price())))
|
||||
order = o;
|
||||
}
|
||||
return order;
|
||||
@@ -445,21 +490,41 @@ namespace mbts.spark {
|
||||
if (order_properties_vm.selected_algo_definition != null)
|
||||
rv.default_algo = AlgoID.from_algo_key(order_properties_vm.selected_algo_definition.key);
|
||||
}
|
||||
rv.panel_price_point_settings = new LadderPanelPricePointSettings {
|
||||
day_change = day_change_style.create(),
|
||||
open = open_style.create(),
|
||||
settlement = settlement_style.create(),
|
||||
session_low = session_low_style.create(),
|
||||
session_high = session_high_style.create(),
|
||||
volume = volume_style.create()
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void key_down(KeyEventArgs e) {
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
if (KeyboardExtensions.IsDown(Key.Space)) {
|
||||
_bus?.post(SparkTags.RequestRecenterLadder, null);
|
||||
}
|
||||
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
|
||||
if (!KeyboardExtensions.IsCtrlDown()) return;
|
||||
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
|
||||
if (new_algo != null) {
|
||||
order_properties_vm.selected_algo_definition = new_algo;
|
||||
e.Handled = true;
|
||||
switch (e.Key) {
|
||||
case Key.Space:
|
||||
if (KeyboardExtensions.IsShiftDown()) {
|
||||
// only recenter this ladder
|
||||
request_recenter?.Invoke();
|
||||
} else {
|
||||
// recenter all ladders
|
||||
_bus?.post(SparkTags.RequestRecenterLadder, null);
|
||||
}
|
||||
e.Handled = true;
|
||||
break;
|
||||
default:
|
||||
// the ctrl modifier requirement helps avoid keyboard-input collisions w/ algo-templates popup
|
||||
if (KeyboardExtensions.IsCtrlDown()) {
|
||||
var new_algo = order_properties_vm.algo_definitions.FirstOrDefault(o => o.algo_root.key_match(e));
|
||||
if (new_algo != null) {
|
||||
order_properties_vm.selected_algo_definition = new_algo;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,11 +541,11 @@ namespace mbts.spark {
|
||||
public Task send_order_async((double price, Side? side, MouseButton button) args) {
|
||||
if (order_properties_vm != null && args.button != MouseButton.Middle) {
|
||||
var size_from_alternate = args.button == MouseButton.Right;
|
||||
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || current_settings.alternate_order_size > 0)) {
|
||||
if (!DoubleHelper.IsNaNOrInfinity(args.price) && args.side.HasValue && !HasErrors && !order_properties_vm.HasErrors && (!size_from_alternate || actual_alternate_order_size > 0)) {
|
||||
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
uint size = size_from_alternate ? current_settings.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
uint size = size_from_alternate ? actual_alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
|
||||
// enforce max clip
|
||||
if (enforce_max_clip_from_settings && size > max_clip_from_settings) {
|
||||
size = max_clip_from_settings;
|
||||
@@ -493,6 +558,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public async Task send_order_async(Side side, uint size, double price) {
|
||||
if (!_validate_price(side, price, out var invalid_price_msg)) {
|
||||
status_update(new Activity { security = security, message = invalid_price_msg, category = ActivityLogCategory.order });
|
||||
return;
|
||||
}
|
||||
var order = order_properties_vm.create(side);
|
||||
if (order.CustomDef != null) {
|
||||
if (order.CustomDef.Stop != null) {
|
||||
@@ -510,28 +579,109 @@ namespace mbts.spark {
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
var fav_algo = order_properties_vm.selected_favorite_algo_definition;
|
||||
if (fav_algo != null && !_sticky_algos.Contains(fav_algo.key) && !order_properties_vm.try_pop_selected_algo_definition()) {
|
||||
var key = new AlgoKey(OrderDataHelper.get_default_algo(OrderDataHelper.get_algo_format(security.ContractCase)));
|
||||
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
|
||||
order_properties_vm.selected_algo_definition = r;
|
||||
if (fav_algo != null) {
|
||||
if (_favorite_algo_map.TryGetValue(fav_algo.key, out var d)) {
|
||||
if (!d.flags.HasFlag(LadderFavoriteAlgoFlags.Sticky) && !order_properties_vm.try_pop_selected_algo_definition()) {
|
||||
var key = new AlgoKey(OrderDataHelper.get_default_algo(security.ContractCase));
|
||||
if (order_properties_vm.try_get_algo_definition(key, out var r)) {
|
||||
order_properties_vm.selected_algo_definition = r;
|
||||
}
|
||||
}
|
||||
if (d.flags.HasFlag(LadderFavoriteAlgoFlags.AutoRevert))
|
||||
fav_algo.algo_root.revert_all_params();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IObservable<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderSecurity>> price_index, IReadOnlyDictionary<uint, OrderSecurity> map)> _order_price_source(
|
||||
IObservable<TableUpdate<OrderSecurity, uint>> source,
|
||||
IComparer<double> price_comp,
|
||||
Func<double, double> normalize_price) {
|
||||
//
|
||||
return Observable.Create<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderSecurity>>, IReadOnlyDictionary<uint, OrderSecurity>)>(o => {
|
||||
var state = ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderSecurity>>(price_comp);
|
||||
var sub = source.Subscribe(table => {
|
||||
var bd = state.ToBuilder();
|
||||
// removes
|
||||
var removes = table.removed.Concat(table.changes.Where(price_changed).Old());
|
||||
foreach (var old_order in removes) {
|
||||
var price = get_price(old_order);
|
||||
if (bd.TryGetValue(price, out var index)) {
|
||||
var new_index = index.Remove(old_order.order.Key);
|
||||
if (new_index.Count == 0) {
|
||||
bd.Remove(price);
|
||||
} else {
|
||||
bd[price] = new_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
// changes
|
||||
var changes = table.changes.Where(ch => !price_changed(ch)).New();
|
||||
foreach (var changed_order in changes) {
|
||||
var price = get_price(changed_order);
|
||||
bd[price] = bd[price].SetItem(changed_order.order.Key, changed_order);
|
||||
}
|
||||
// adds
|
||||
var adds = table.added.Concat(table.changes.Where(price_changed).New());
|
||||
foreach (var new_order in adds) {
|
||||
var price = get_price(new_order);
|
||||
if (bd.TryGetValue(price, out var index)) {
|
||||
bd[price] = index.Add(new_order.order.Key, new_order);
|
||||
} else {
|
||||
bd[price] = ImmutableDictionary.CreateRange(
|
||||
new[] { new KeyValuePair<uint, OrderSecurity>(new_order.order.Key, new_order) });
|
||||
}
|
||||
}
|
||||
o.OnNext((state = bd.ToImmutable(), table.state));
|
||||
});
|
||||
return sub;
|
||||
});
|
||||
|
||||
bool price_changed(Change<OrderSecurity> ch) =>
|
||||
price_comp.Compare(ch.New.order.ui_price(), ch.Old.order.ui_price()) != 0;
|
||||
|
||||
double get_price(OrderSecurity order) {
|
||||
var p = order.order.ui_price();
|
||||
if (!(normalize_price is null))
|
||||
p = normalize_price(p);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
double _normalize_price(double price) =>
|
||||
TickUtils.normalize_price(spec, price);
|
||||
|
||||
bool _validate_price(Side side, double price, out string msg) {
|
||||
msg = null;
|
||||
if (!price.is_real()) {
|
||||
msg = "not a real number";
|
||||
} else if (settlement.is_real()) {
|
||||
var ticks_from_settle = security.ContractSpec.tickdiff(price, settlement);
|
||||
if (ticks_from_settle.HasValue) {
|
||||
var max_cross = (current_settings?.ladder_order_limits?.max_settlement_cross_ticks)?.as_nullable();
|
||||
if (max_cross.HasValue) {
|
||||
if (side == Side.Sell ? ticks_from_settle < (-1 * max_cross.Value) : ticks_from_settle > max_cross.Value)
|
||||
msg = $"exceeds settle cross ticks limit ({ticks_from_settle.Value})";
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg == null;
|
||||
}
|
||||
|
||||
void _update_header() {
|
||||
bool last_updated = md.get_last_trade(ref _last_trade_time, out NativePriceSize last, out DateTime time, out ulong volume);
|
||||
if (_misc_time != _model.md.misc_timestamp || last_updated) {
|
||||
_high.set(md.session_high);
|
||||
_low.set(md.session_low);
|
||||
_session_high.set(md.session_high);
|
||||
_session_low.set(md.session_low);
|
||||
_open.set(md.open_price);
|
||||
_settlement.set(md.settlement_price);
|
||||
if (!double.IsNaN(last.price)) {
|
||||
_change.set(last.price - md.settlement_price);
|
||||
_day_change.set(last.price - md.settlement_price);
|
||||
} else {
|
||||
ulong unused = 0;
|
||||
_model.md.get_bbo(ref unused, out NativePriceSize bid, out NativePriceSize ask);
|
||||
double mid = TickUtils.mid_tick(security.ContractSpec, bid.price, ask.price);
|
||||
_change.set(mid - md.settlement_price);
|
||||
_day_change.set(mid - md.settlement_price);
|
||||
}
|
||||
_volume.set(volume);
|
||||
_misc_time = _model.md.misc_timestamp;
|
||||
@@ -542,7 +692,7 @@ namespace mbts.spark {
|
||||
void _show_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
security_criteria = SecurityCriteria.from_securities(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
@@ -556,5 +706,12 @@ namespace mbts.spark {
|
||||
_update_header();
|
||||
on_tick?.Invoke();
|
||||
}
|
||||
|
||||
SecurityMD? _find_sec_md(uint sec_key) {
|
||||
if (_security_map.TryGetValue(sec_key, out var sec_md)) {
|
||||
return sec_md;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,12 +46,12 @@ namespace mbts.spark {
|
||||
void _reset_ladder_vm() {
|
||||
if (_value == null && !_security_md.HasValue)
|
||||
return;
|
||||
Disposable.replace(ref _value,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
|
||||
_value?.Dispose();
|
||||
_value = _security_md.HasValue ?
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null;
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.FairValueLineSettingsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mbts="clr-namespace:mbts"
|
||||
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:stg="clr-namespace:mbts.ui.settings"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Thickness x:Key="CellPadding">10,6</Thickness>
|
||||
<Style x:Key="DisabledColumnHeader" TargetType="GridViewColumnHeader">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GridViewColumnHeader">
|
||||
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{x:Static u:BTSColors.header_brush}"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
Grid.Row="0"
|
||||
Grid.IsSharedSizeScope="True"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1"
|
||||
Rows="{Binding items}"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<c:DIYGrid.Columns>
|
||||
<c:DIYGridColumnCollection>
|
||||
<usp:FairValueLineSettingsColumn key="Delete" HeaderContainerStyle="{StaticResource DisabledColumnHeader}" Width="23" is_visible="True"/>
|
||||
<usp:FairValueLineSettingsColumn key="Security" Header="Security" Width="130" is_visible="True"/>
|
||||
<usp:FairValueLineSettingsColumn key="Show" Header="Show" Width="40" is_visible="True"/>
|
||||
<usp:FairValueLineSettingsColumn key="AutoRefresh" Header="Auto Refresh" Width="80" is_visible="True"/>
|
||||
<usp:FairValueLineSettingsColumn key="Color" Header="Color" Width="80" is_visible="True"/>
|
||||
<usp:FairValueLineSettingsColumn key="PriceType" Header="Type" Width="80" is_visible="True"/>
|
||||
</c:DIYGridColumnCollection>
|
||||
</c:DIYGrid.Columns>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate DataType="{x:Type sp:FairValueLineItemSettingsVM}">
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
|
||||
<!--Delete-->
|
||||
<Button
|
||||
c:DIYGrid.ColumnKey="Delete"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding DataContext.remove_item_cmd,
|
||||
RelativeSource={RelativeSource AncestorType=usp:FairValueLineSettingsView}}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource RoundCloseButton}"
|
||||
ToolTip="Delete"
|
||||
/>
|
||||
<!--Security-->
|
||||
<c:ContractPickerPopup
|
||||
c:DIYGrid.ColumnKey="Security"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
CloseOnOK="True"
|
||||
CloseOnCancel="True"
|
||||
IsEditMode="False"
|
||||
MaxWidth="180"
|
||||
SecurityMap="{Binding DataContext.security_map,
|
||||
RelativeSource={RelativeSource AncestorType=usp:FairValueLineSettingsView}}"
|
||||
SelectedSecurityKey="{Binding selected_security_key}"
|
||||
Header="{Binding SelectedSecurity,
|
||||
RelativeSource={RelativeSource Self},
|
||||
Converter={StaticResource SelectedSecurityTextConverter}}"
|
||||
/>
|
||||
<!--Show-->
|
||||
<CheckBox
|
||||
c:DIYGrid.ColumnKey="Show"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding show}"
|
||||
/>
|
||||
<!--AutoRefresh-->
|
||||
<CheckBox
|
||||
c:DIYGrid.ColumnKey="AutoRefresh"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding auto_refresh}"
|
||||
/>
|
||||
<!--Color-->
|
||||
<c:ColorPickerPopup
|
||||
c:DIYGrid.ColumnKey="Color"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
Palettes="{Binding color_palettes}"
|
||||
SelectedColor="{Binding color}"
|
||||
/>
|
||||
<!--PriceType-->
|
||||
<ComboBox
|
||||
c:DIYGrid.ColumnKey="PriceType"
|
||||
Margin="{StaticResource CellPadding}"
|
||||
ItemsSource="{Binding price_types}"
|
||||
SelectedItem="{Binding selected_price_type}"
|
||||
/>
|
||||
<Rectangle Grid.Column="0" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="1" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="2" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="3" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
<Rectangle Grid.Column="4" HorizontalAlignment="Right" Width="1" Fill="Gray"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<TextBlock Grid.Row="1" HorizontalAlignment="Left" Margin="4,8">
|
||||
<Hyperlink Command="{Binding add_item_cmd}">
|
||||
Click to Add Item
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,40 @@
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for FairValueLineSettingsView.xaml
|
||||
/// </summary>
|
||||
partial class FairValueLineSettingsView : UserControl {
|
||||
public FairValueLineSettingsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public enum Columns {
|
||||
None = 0,
|
||||
Delete,
|
||||
Security,
|
||||
Show,
|
||||
AutoRefresh,
|
||||
Color,
|
||||
PriceType
|
||||
}
|
||||
}
|
||||
|
||||
class FairValueLineSettingsColumn : DIYGrid.ColumnBase<FairValueLineSettingsView.Columns> {
|
||||
protected override void on_key_initialized(FairValueLineSettingsView.Columns key) =>
|
||||
id = key.ToString();
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,12 @@ using System.Windows.Media.Animation;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
const int _min_row_count = 501, _max_row_count = 2001, _column_min_width = 8;
|
||||
const int _min_row_count = 501, _max_row_count = 2001;
|
||||
static readonly bool _show_scrollbar;
|
||||
static readonly bool _lighten_market_cells;
|
||||
static readonly Color? _bid_market_cell_background;
|
||||
static readonly Color? _ask_market_cell_background;
|
||||
|
||||
int _row_count = _min_row_count;
|
||||
|
||||
readonly VisualHost _visualHost = new VisualHost();
|
||||
@@ -52,7 +57,7 @@ namespace mbts.spark {
|
||||
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container =
|
||||
new RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>>();
|
||||
|
||||
IDisposable _settings_sub, _volume_sub, _tick_sub;
|
||||
IDisposable _settings_sub, _volume_sub, _tick_sub, _fair_value_lines_sub, _vm_sub;
|
||||
ScrollViewer _scrollViewer;
|
||||
LadderSettings _settings;
|
||||
LadderVM _vm;
|
||||
@@ -65,7 +70,8 @@ namespace mbts.spark {
|
||||
double _last_origin_price;
|
||||
MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_size = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default());
|
||||
Func<uint, string> _fmt_mkt_quantity_impl = v => v.ToString();
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
@@ -73,6 +79,19 @@ namespace mbts.spark {
|
||||
ToolTip tooltip_view = new ToolTip();
|
||||
IDisposable _parameterized_order_popup;
|
||||
|
||||
static LadderGridHost() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
_show_scrollbar = knobs.show_scrollbar;
|
||||
_lighten_market_cells = knobs.lighten_market_cells;
|
||||
// For market cell backgrounds, 0 means null. Note that the full
|
||||
// range of colors can still be expressed, as zero is merely one of
|
||||
// 2^24 possible representations of 'transparent.'
|
||||
if (knobs.bid_market_cell_background != 0)
|
||||
_bid_market_cell_background = knobs.bid_market_cell_background.argb_color();
|
||||
if (knobs.ask_market_cell_background != 0)
|
||||
_ask_market_cell_background = knobs.ask_market_cell_background.argb_color();
|
||||
}
|
||||
|
||||
public LadderGridHost() {
|
||||
AllowDrop = true;
|
||||
UseLayoutRounding = true;
|
||||
@@ -80,6 +99,8 @@ namespace mbts.spark {
|
||||
_ordershost = new LadderOrdersHost(this) { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Stretch };
|
||||
var scrollViewerFactory = new FrameworkElementFactory(typeof(ScrollViewer));
|
||||
scrollViewerFactory.SetValue(FocusableProperty, false);
|
||||
if (!_show_scrollbar)
|
||||
scrollViewerFactory.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden);
|
||||
Template = new ControlTemplate() {
|
||||
VisualTree = scrollViewerFactory
|
||||
};
|
||||
@@ -108,6 +129,8 @@ namespace mbts.spark {
|
||||
|
||||
public event Action visible_columns_changed;
|
||||
|
||||
public static double ColumnMinWidth => 6;
|
||||
|
||||
public IEnumerable<ColumnType> visible_column_types =>
|
||||
columns.Where(c => c.settings.show)
|
||||
.Select(c => c.column_type);
|
||||
@@ -292,6 +315,18 @@ namespace mbts.spark {
|
||||
|
||||
Column[] columns { get; set; } = new Column[0];
|
||||
IEnumerable<Column> _visible_columns => columns.Where(c => c.settings.show);
|
||||
ContractSpecificationImmutable? _spec_backing;
|
||||
ContractSpecificationImmutable _spec {
|
||||
get {
|
||||
if (_spec_backing.HasValue) {
|
||||
return _spec_backing.Value;
|
||||
} else if (_vm != null) {
|
||||
_spec_backing = TickUtils.multiply_tick_size(security.ContractSpec, _settings?.ticks_per_row ?? 1);
|
||||
return _spec_backing.Value;
|
||||
}
|
||||
return SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
}
|
||||
}
|
||||
|
||||
public void drag_out_of_bounds() =>
|
||||
_tactile_adorner?.drag_out_of_bounds();
|
||||
@@ -299,7 +334,7 @@ namespace mbts.spark {
|
||||
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) {
|
||||
if (_spec.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))
|
||||
@@ -349,7 +384,9 @@ namespace mbts.spark {
|
||||
return;
|
||||
}
|
||||
} else if (_vm != null) {
|
||||
uint size = e.ChangedButton == MouseButton.Right ? _vm.alternate_order_size_from_settings : _vm.size_calculator.value ?? 0;
|
||||
uint size = e.ChangedButton == MouseButton.Right ?
|
||||
_vm.actual_alternate_order_size :
|
||||
_vm.size_calculator.value ?? 0;
|
||||
|
||||
// does the size violate the local max clip
|
||||
if (_vm.enforce_max_clip_from_settings && size > _vm.max_clip_from_settings) {
|
||||
@@ -465,7 +502,7 @@ namespace mbts.spark {
|
||||
security_id: _vm.model.security.Id,
|
||||
price_level: price_for_point(point)
|
||||
);
|
||||
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
|
||||
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query, fmt_mbo_qty);
|
||||
// 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) {
|
||||
@@ -486,6 +523,8 @@ namespace mbts.spark {
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
string fmt_mbo_qty(int value) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
@@ -525,20 +564,50 @@ namespace mbts.spark {
|
||||
_price_list == null ? double.NaN : _price_list[row];
|
||||
|
||||
uint? _bid_size_from_row(int row) =>
|
||||
_size_from_row(row, _price_list, _bids, _vm.security.ContractSpec);
|
||||
_size_from_row(row, _price_list, _bids, _spec);
|
||||
|
||||
uint? _ask_size_from_row(int row) =>
|
||||
_size_from_row(row, _price_list, _asks, _vm.security.ContractSpec);
|
||||
_size_from_row(row, _price_list, _asks, _spec);
|
||||
|
||||
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);
|
||||
}
|
||||
const int max_tries = 3;
|
||||
var errors = new List<string>(0);
|
||||
int num_tries = 0;
|
||||
for (; num_tries < max_tries; num_tries++) {
|
||||
try {
|
||||
var rv = impl();
|
||||
if (num_tries > 0) {
|
||||
log_errors(true);
|
||||
}
|
||||
return rv;
|
||||
} catch (Exception ex) {
|
||||
errors.Add(ex.ToString());
|
||||
}
|
||||
}
|
||||
log_errors(false);
|
||||
return 0;
|
||||
|
||||
void log_errors(bool succeeded) {
|
||||
// Only log distinct errors to avoid useless noise.
|
||||
// It's likely that the exception will always be the same (IndexOutOfRange),
|
||||
// however, this bug is not fully understood, so assume nothing.
|
||||
var error_msg = string.Join("; ", errors.Distinct());
|
||||
if (succeeded) {
|
||||
Log.Ui.error($"Ladder '{security}' got volume for row at index '{row}' after {num_tries + 1} attempts (price_list array length is {_price_list.Length}). The following exceptions were observed: {error_msg}");
|
||||
} else {
|
||||
Log.Ui.error($"Ladder '{security}' failed to get volume for row at index '{row}' after {max_tries} attempts (price_list array length is {_price_list.Length}): {error_msg}");
|
||||
}
|
||||
}
|
||||
|
||||
uint impl() {
|
||||
if (_price_list.SafeTryGetAt(row, out var price)) {
|
||||
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;
|
||||
}
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
@@ -572,7 +641,7 @@ namespace mbts.spark {
|
||||
case ColumnType.Price: new_col_width = (int)price_column_width; break;
|
||||
case ColumnType.Ask: new_col_width = (int)ask_column_width; break;
|
||||
case ColumnType.Volume: new_col_width = (int)VolumeColumnWidth; break;
|
||||
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, _column_min_width); break;
|
||||
default: new_col_width = (int)Math.Max((double)column.settings.width + adjustment, ColumnMinWidth); break;
|
||||
}
|
||||
int true_adj = new_col_width - (int)column.settings.width;
|
||||
column.settings.width = (uint)new_col_width;
|
||||
@@ -584,7 +653,9 @@ namespace mbts.spark {
|
||||
if (_price_list != null) {
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
|
||||
case ColumnType.Bid: render_market_col(_bids); break;
|
||||
case ColumnType.Bid:
|
||||
render_market_col(_bids, _bid_market_cell_background);
|
||||
break;
|
||||
case ColumnType.Price:
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
@@ -596,7 +667,7 @@ namespace mbts.spark {
|
||||
_vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
|
||||
column.settings,
|
||||
security.ContractSpec);
|
||||
_spec);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
@@ -605,11 +676,13 @@ namespace mbts.spark {
|
||||
_settings.row_height,
|
||||
_price_list,
|
||||
column.settings,
|
||||
security.ContractSpec,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
break;
|
||||
case ColumnType.Ask: render_market_col(_asks); break;
|
||||
case ColumnType.Ask:
|
||||
render_market_col(_asks, _ask_market_cell_background);
|
||||
break;
|
||||
case ColumnType.Volume:
|
||||
_volume_container.mark_dirty();
|
||||
_refresh_volume_at_price();
|
||||
@@ -621,9 +694,9 @@ namespace mbts.spark {
|
||||
if (_mkt_break_lines.high_render_index.HasValue)
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
|
||||
}
|
||||
void render_market_col(NativePriceSize[] depth) {
|
||||
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
|
||||
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, security.ContractSpec, column.settings, _fmt_size);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
|
||||
}
|
||||
}
|
||||
_apply_column_visual_offset();
|
||||
@@ -658,13 +731,32 @@ namespace mbts.spark {
|
||||
} 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));
|
||||
// invalidate the contract spec here, as well as in the 'settings update' callback.
|
||||
// While it is assumed that the settings IObservable will always fire at least once (on subscribe),
|
||||
// this serves as a redundancy/fail-safe; it ensures that the vm's security's
|
||||
// contract spec will be used, even if the settings sequence is empty.
|
||||
_invalidate_spec();
|
||||
_price_comparer = Comparer<double>.Create((x, y) => _spec.tick_equals(_normalize_price(x), _normalize_price(y)) ? 0 : y.CompareTo(x));
|
||||
_price_dirty = true;
|
||||
_piq_dirty = true;
|
||||
_vm.on_tick += _tick;
|
||||
// **use local vm to protect against re-assignment,
|
||||
// so the events are unhooked from the same instance to which they were added
|
||||
// when the subs are disposed.
|
||||
var local_vm = _vm;
|
||||
local_vm.on_tick += _tick;
|
||||
local_vm.request_recenter += on_request_recenter;
|
||||
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
|
||||
_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);
|
||||
// **unhook from local_vm.request_recenter.
|
||||
// todo: organize these subs into a single entity, prob _vm_subs.
|
||||
// This hasn't already been done bc the ladder a bit fragile, so changes must be carefully implemented,
|
||||
// with meticulous attention to detail.
|
||||
_tick_sub = Disposable.Create(() => local_vm.on_tick -= _tick);
|
||||
_vm_sub = Disposable.Create(() => local_vm.request_recenter -= on_request_recenter);
|
||||
void on_request_recenter() {
|
||||
RecenterCommand?.ExecuteIfCan(null);
|
||||
_refresh_frozen_mkt_lines();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,7 +793,7 @@ namespace mbts.spark {
|
||||
_hide_tool_tip();
|
||||
}
|
||||
|
||||
tooltip_view.Content = $"{volume}";
|
||||
tooltip_view.Content = _fmt_mkt_quantity(volume);
|
||||
tooltip_view.IsOpen = true;
|
||||
this.ToolTip = tooltip_view;
|
||||
} else {
|
||||
@@ -733,6 +825,15 @@ namespace mbts.spark {
|
||||
|
||||
#endregion
|
||||
|
||||
void _refresh_frozen_mkt_lines() {
|
||||
if (_price_list is null)
|
||||
return;
|
||||
_mkt_break_lines.refresh_frozen();
|
||||
_refresh_bid_mkt_break();
|
||||
_refresh_ask_mkt_break();
|
||||
_refresh_mid_mkt_break();
|
||||
_refresh_fair_value_lines(true);
|
||||
}
|
||||
|
||||
void _on_vm_disposed(bool obj) =>
|
||||
_dispose_resources();
|
||||
@@ -741,6 +842,7 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _settings_sub);
|
||||
Disposable.dispose(ref _volume_sub);
|
||||
Disposable.dispose(ref _tick_sub);
|
||||
Disposable.dispose(ref _fair_value_lines_sub);
|
||||
if (_vm != null)
|
||||
_vm.OnDisposed -= _on_vm_disposed;
|
||||
}
|
||||
@@ -764,6 +866,7 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_scroll_to_row(_row_count / 2);
|
||||
}
|
||||
_refresh_frozen_mkt_lines();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,6 +899,7 @@ namespace mbts.spark {
|
||||
_refresh_bid_mkt_break();
|
||||
_refresh_ask_mkt_break();
|
||||
_refresh_mid_mkt_break();
|
||||
_refresh_fair_value_lines(true);
|
||||
_refresh_session_low_line();
|
||||
_refresh_session_high_line();
|
||||
_refresh_session_range();
|
||||
@@ -808,6 +912,7 @@ namespace mbts.spark {
|
||||
_refresh_ask_mkt_break();
|
||||
if (_mkt_break_lines.mid_dirty)
|
||||
_refresh_mid_mkt_break();
|
||||
_refresh_fair_value_lines(false);
|
||||
if (_mkt_break_lines.session_low_dirty)
|
||||
_refresh_session_low_line();
|
||||
if (_mkt_break_lines.session_high_dirty)
|
||||
@@ -817,7 +922,7 @@ namespace mbts.spark {
|
||||
if (last_trade_changed) {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
}
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
@@ -838,13 +943,36 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _refresh_bid_mkt_break() =>
|
||||
_render_mkt_break(_bid_mkt_break_vis, _mkt_break_lines.bid, _settings.bid_break_line.color);
|
||||
_render_mkt_break(
|
||||
_bid_mkt_break_vis,
|
||||
_settings.bid_break_line.refresh_mode == LadderMarketBreakLine.RefreshMode.Manual ?
|
||||
_mkt_break_lines.frozen_bid :
|
||||
_mkt_break_lines.bid,
|
||||
_settings.bid_break_line.color);
|
||||
|
||||
void _refresh_ask_mkt_break() =>
|
||||
_render_mkt_break(_ask_mkt_break_vis, _mkt_break_lines.ask, _settings.ask_break_line.color);
|
||||
_render_mkt_break(
|
||||
_ask_mkt_break_vis,
|
||||
_settings.ask_break_line.refresh_mode == LadderMarketBreakLine.RefreshMode.Manual ?
|
||||
_mkt_break_lines.frozen_ask :
|
||||
_mkt_break_lines.ask,
|
||||
_settings.ask_break_line.color);
|
||||
|
||||
void _refresh_mid_mkt_break() =>
|
||||
_render_mkt_break(_mid_mkt_break_vis, _mkt_break_lines.mid, _settings.mid_break_line.color);
|
||||
_render_mkt_break(
|
||||
_mid_mkt_break_vis,
|
||||
_settings.mid_break_line.refresh_mode == LadderMarketBreakLine.RefreshMode.Manual ?
|
||||
_mkt_break_lines.frozen_mid :
|
||||
_mkt_break_lines.mid,
|
||||
_settings.mid_break_line.color);
|
||||
|
||||
void _refresh_fair_value_lines(bool force) {
|
||||
foreach (var fvl in _mkt_break_lines.fair_value_line_views) {
|
||||
if (force || fvl.is_dirty) {
|
||||
_render_mkt_break(fvl.vis, fvl.price_view, fvl.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_session_low_line() {
|
||||
if (_settings.session_low_line.show && _mkt_break_lines.low_render_index.HasValue)
|
||||
@@ -859,21 +987,21 @@ namespace mbts.spark {
|
||||
void _refresh_last_trade() {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column);
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
}
|
||||
|
||||
void _refresh_piq() {
|
||||
if (!_settings.piq_column.show)
|
||||
return;
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
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);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
Log.assert_d(_price_list != null);
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, security.ContractSpec, _settings.bid_column, _fmt_size);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, security.ContractSpec, _settings.ask_column, _fmt_size);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -895,7 +1023,17 @@ namespace mbts.spark {
|
||||
if (vapls != null) {
|
||||
Log.assert_d(_price_list != null);
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
LadderRender.render_volumes(_volume_vis_tup.visual, _volume_vis_tup.glyph_context, _price_list, vapls, _settings.row_height, security.ContractSpec, _settings.volume_column, last_index, _get_last_arrow_string());
|
||||
LadderRender.render_volumes(
|
||||
_volume_vis_tup.visual,
|
||||
_volume_vis_tup.glyph_context,
|
||||
_price_list,
|
||||
vapls,
|
||||
_settings.row_height,
|
||||
_spec,
|
||||
_settings.volume_column,
|
||||
last_index,
|
||||
_get_last_arrow_string(),
|
||||
_fmt_mkt_quantity);
|
||||
_old_last_trade = _last_trade;
|
||||
}
|
||||
|
||||
@@ -918,7 +1056,7 @@ namespace mbts.spark {
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_last_trade.size} ";
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -928,8 +1066,8 @@ namespace mbts.spark {
|
||||
void _refresh_prices() {
|
||||
var prev_price_list = _price_list;
|
||||
_price_list = (!(_settings.net_change == LadderSettings.NetChangeTerms.None)) ?
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec) :
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, security.ContractSpec, _settings.price_display_format);
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _vm.md.settlement_price, _settings.net_change == LadderSettings.NetChangeTerms.Ticks, _row_count, _settings.row_height, _settings.price_column, _spec) :
|
||||
LadderRender.render_prices(_price_vis_tup.visual, _price_vis_tup.glyph_context, _mkt_break_lines.origin, _row_count, _settings.row_height, _settings.price_column, _spec, _settings.price_display_format);
|
||||
_ordershost.update_settings(_price_list, _price_comparer, _settings.row_height, _settings.orders_column);
|
||||
if (prev_price_list != null) {
|
||||
// maintain previous visible price range if possible
|
||||
@@ -957,8 +1095,8 @@ namespace mbts.spark {
|
||||
SparkUtils.lazy_warn(!double.IsNaN(_mkt_break_lines.origin) && _mkt_break_lines.origin >= low && _mkt_break_lines.origin <= high,
|
||||
() => fmt_log_msg($"market break ({_mkt_break_lines.origin}) is unacceptable for the market range ({low} - {high})"));
|
||||
int prev_row_count = _row_count;
|
||||
int n_ticks_to_low = security.ContractSpec.tickdiff(_mkt_break_lines.origin, low) ?? 0;
|
||||
int n_ticks_to_high = security.ContractSpec.tickdiff(high, _mkt_break_lines.origin) ?? 0;
|
||||
int n_ticks_to_low = _spec.tickdiff(_mkt_break_lines.origin, low) ?? 0;
|
||||
int n_ticks_to_high = _spec.tickdiff(high, _mkt_break_lines.origin) ?? 0;
|
||||
int desired_row_count = Math.Max(n_ticks_to_low, n_ticks_to_high) * 2 + 1;
|
||||
_row_count = Math.Min(_max_row_count, desired_row_count);
|
||||
if (_row_count != prev_row_count) {
|
||||
@@ -974,7 +1112,7 @@ namespace mbts.spark {
|
||||
_last_price_range_calc_time = now;
|
||||
//
|
||||
string fmt_log_msg(string msg) =>
|
||||
$"Ladder {security} (id:{security.Id}, low/high tick:{security.ContractSpec.LowTick}/{security.ContractSpec.HighTick}): {msg}.";
|
||||
$"Ladder {security} (id:{security.Id}, low/high tick:{_spec.LowTick}/{_spec.HighTick}): {msg}.";
|
||||
}
|
||||
return prices_changed;
|
||||
}
|
||||
@@ -1007,6 +1145,7 @@ namespace mbts.spark {
|
||||
_refresh_bid_mkt_break();
|
||||
_refresh_ask_mkt_break();
|
||||
_refresh_mid_mkt_break();
|
||||
_refresh_fair_value_lines(true);
|
||||
_refresh_session_low_line();
|
||||
_refresh_session_high_line();
|
||||
_refresh_session_range();
|
||||
@@ -1022,29 +1161,29 @@ namespace mbts.spark {
|
||||
double _get_bid_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return double.IsNaN(best_bid) ?
|
||||
double.IsNaN(best_ask) ? _vm.low : security.ContractSpec.tickdown(best_ask) :
|
||||
best_bid;
|
||||
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
}
|
||||
|
||||
double _normalize_price(double price) =>
|
||||
TickUtils.normalize_price(_spec, price);
|
||||
|
||||
double _get_ask_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return double.IsNaN(best_ask) ?
|
||||
double.IsNaN(best_bid) ? security.ContractSpec.tickdown(_vm.high) : best_bid :
|
||||
security.ContractSpec.tickdown(best_ask);
|
||||
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
}
|
||||
|
||||
double _get_mid_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return double.IsNaN(best_bid) ?
|
||||
double.IsNaN(best_ask) ? 0.5 * (_vm.low + _vm.high) : security.ContractSpec.tickdown(best_ask) :
|
||||
double.IsNaN(best_ask) ? best_bid : TickUtils.mid_tick(security.ContractSpec, best_bid, best_ask);
|
||||
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
}
|
||||
|
||||
double _get_session_low() => _vm.md.session_low;
|
||||
double _get_session_high() => _vm.md.session_high;
|
||||
double _get_session_low() => _normalize_price(_get_session_low_raw());
|
||||
double _get_session_high() => _normalize_price(_get_session_high_raw());
|
||||
|
||||
double _get_session_low_raw() => _vm.md.session_low;
|
||||
double _get_session_high_raw() => _vm.md.session_high;
|
||||
|
||||
void _render_mkt_break(DrawingVisual vis, double price, uint color) =>
|
||||
LadderRender.render_mkt_break(vis, _row_for_price(price), _settings.row_height, color);
|
||||
@@ -1053,6 +1192,9 @@ namespace mbts.spark {
|
||||
LadderRender.render_single_grid_line(vis, row, _settings.row_height, color, _price_column.x_left, _settings.price_column.width);
|
||||
#endregion
|
||||
|
||||
void _invalidate_spec() =>
|
||||
_spec_backing = null;
|
||||
|
||||
void _settings_updated(LadderSettings s) {
|
||||
bool init;
|
||||
double prev_row_height;
|
||||
@@ -1066,6 +1208,7 @@ namespace mbts.spark {
|
||||
/// The viewmodel will always have the most up-to-date settings, so ignore <see cref="s"/> and just get it from <see cref="_vm"/>.
|
||||
/// This is a workaround for the global settings-defaults issue: when defaults are updated, some properties are inadvertantly overwritten, such as column-widths, etc.
|
||||
_settings = _vm.get_save_state();
|
||||
_invalidate_spec();
|
||||
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
|
||||
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
|
||||
_price_vis_tup.glyph_context = glyph_context(s.price_column);
|
||||
@@ -1076,14 +1219,30 @@ namespace mbts.spark {
|
||||
GlyphContext glyph_context(LadderColumnSettings cs) =>
|
||||
GlyphContext.create(cs.font_family, cs.font_weight);
|
||||
|
||||
_fmt_size = MDDisplayHelper.get_formatter_nps(_settings.size_display);
|
||||
var mkt_qty_display_divisor = _vm?.display_quantity_divisor ?? 1;
|
||||
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(_settings.size_display, mkt_qty_display_divisor);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(mkt_qty_display_divisor);
|
||||
_set_columns();
|
||||
_apply_column_visual_offset();
|
||||
if (prev_row_height != _settings.row_height)
|
||||
_reset_height();
|
||||
_price_dirty = _settings.price_column.show;
|
||||
Disposable.dispose(ref _fair_value_lines_sub);
|
||||
_mkt_break_lines.fair_value_line_views.Clear();
|
||||
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
|
||||
_visualHost.Clear();
|
||||
if (LadderSettings.mimic_tt) {
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visualHost.AddVisualChild(_session_range_vis);
|
||||
}
|
||||
// Renders the grid lines and column backgrounds
|
||||
_visualHost.AddVisualChild(_gridvis);
|
||||
// Orders column
|
||||
@@ -1108,12 +1267,23 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
_refresh_frozen_mkt_lines();
|
||||
_visualHost.AddVisualChild(_mid_mkt_break_vis);
|
||||
_visualHost.AddVisualChild(_ask_mkt_break_vis);
|
||||
_visualHost.AddVisualChild(_bid_mkt_break_vis);
|
||||
_fair_value_lines_sub = ui.utility.ObservableAutoWrapper.connect(
|
||||
_vm.fair_value_lines,
|
||||
_mkt_break_lines.fair_value_line_views,
|
||||
vm => {
|
||||
var with_vis = new FairValueLineWithVis(vm);
|
||||
_visualHost.AddVisualChild(with_vis.vis);
|
||||
return with_vis;
|
||||
},
|
||||
with_vis => _visualHost.RemoveVisualChild(with_vis.vis));
|
||||
_visualHost.AddVisualChild(_session_low_vis);
|
||||
_visualHost.AddVisualChild(_session_high_vis);
|
||||
_visualHost.AddVisualChild(_session_range_vis);
|
||||
if (!LadderSettings.mimic_tt)
|
||||
_visualHost.AddVisualChild(_session_range_vis);
|
||||
_render_grid_background();
|
||||
_init_tactile_adorner();
|
||||
// this effectively sets the background of the "Last" column
|
||||
@@ -1125,6 +1295,22 @@ namespace mbts.spark {
|
||||
}
|
||||
visible_columns_changed?.Invoke();
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
string _fmt_mkt_depth_item(NativePriceSize value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_depth_item_impl;
|
||||
return impl is null ? MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default())?.ToString() ?? "Error" : impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
|
||||
string _fmt_mkt_quantity(uint value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_quantity_impl;
|
||||
return impl is null ? value.ToString() : impl(value);
|
||||
}
|
||||
|
||||
void _init_tactile_adorner() {
|
||||
Disposable.dispose(ref _tactile_adorner);
|
||||
@@ -1183,11 +1369,30 @@ namespace mbts.spark {
|
||||
!(x == y);
|
||||
}
|
||||
|
||||
class FairValueLineWithVis {
|
||||
readonly FairValueLineVM _vm;
|
||||
double _price_view = double.NaN;
|
||||
|
||||
public FairValueLineWithVis(FairValueLineVM vm) =>
|
||||
_vm = vm ?? throw new ArgumentNullException(nameof(vm));
|
||||
|
||||
public DrawingVisual vis { get; } = new DrawingVisual();
|
||||
public double price_view => _price_view;
|
||||
public uint color => _vm.settings.color;
|
||||
public bool is_dirty { get; private set; }
|
||||
|
||||
public void refresh(bool update_frozen) {
|
||||
_vm.refresh(update_frozen);
|
||||
is_dirty = ConvertUtils.update_value_type(ref _price_view, _vm.price_view);
|
||||
}
|
||||
}
|
||||
|
||||
// This class represents interesting price points as they should be rendered on the LadderGridHost
|
||||
// Note: The values contained here do NOT necessarily represent true data, as they will be NaN/null if their respective visual is hidden in LadderSettings
|
||||
class MarketBreakLines {
|
||||
readonly LadderGridHost _owner;
|
||||
double _bid, _ask, _mid, _origin, _session_low, _session_high;
|
||||
double _bid = double.NaN, _ask = double.NaN, _mid = double.NaN, _origin, _session_low, _session_high;
|
||||
double _frozen_bid = double.NaN, _frozen_ask = double.NaN, _frozen_mid = double.NaN, _frozen_origin = double.NaN;
|
||||
int? _low_index, _high_index;
|
||||
|
||||
public MarketBreakLines(LadderGridHost owner) =>
|
||||
@@ -1201,6 +1406,11 @@ namespace mbts.spark {
|
||||
public bool session_range_dirty => session_low_dirty || session_high_dirty;
|
||||
public bool origin_dirty { get; private set; }
|
||||
|
||||
public double frozen_bid => double.IsNaN(_frozen_bid) ? (_frozen_bid = _bid) : _frozen_bid;
|
||||
public double frozen_ask => double.IsNaN(_frozen_ask) ? (_frozen_ask = _ask) : _frozen_ask;
|
||||
public double frozen_mid => double.IsNaN(_frozen_mid) ? (_frozen_mid = _mid) : _frozen_mid;
|
||||
public double frozen_origin => double.IsNaN(_frozen_origin) ? (_frozen_origin = _origin) : _frozen_origin;
|
||||
|
||||
public double bid => _bid;
|
||||
public double ask => _ask;
|
||||
public double mid => _mid;
|
||||
@@ -1239,17 +1449,30 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<FairValueLineWithVis> fair_value_line_views { get; } =
|
||||
new ObservableCollection<FairValueLineWithVis>();
|
||||
|
||||
int _get_session_range_size() =>
|
||||
_owner.security.spec_or_default().tickdiff(_session_high, _session_low) ?? 0;
|
||||
_owner._spec.tickdiff(_session_high, _session_low) ?? 0;
|
||||
|
||||
LadderSettings _settings => _owner._settings;
|
||||
|
||||
public void refresh_frozen() {
|
||||
_refresh(true);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
_refresh(false);
|
||||
}
|
||||
void _refresh(bool update_frozen) {
|
||||
bid_dirty = ConvertUtils.update_value_type(ref _bid, _settings.bid_break_line.show ? _owner._get_bid_market_break() : double.NaN);
|
||||
ask_dirty = ConvertUtils.update_value_type(ref _ask, _settings.ask_break_line.show ? _owner._get_ask_market_break() : double.NaN);
|
||||
mid_dirty = ConvertUtils.update_value_type(ref _mid, _settings.mid_break_line.show ? _owner._get_mid_market_break() : double.NaN);
|
||||
session_low_dirty = ConvertUtils.update_value_type(ref _session_low, (_settings.session_low_line.show || _settings.session_range_fill.show) ? _owner._get_session_low() : double.NaN);
|
||||
session_high_dirty = ConvertUtils.update_value_type(ref _session_high, (_settings.session_high_line.show || _settings.session_range_fill.show) ? _owner._get_session_high() : double.NaN);
|
||||
foreach (var fvlv in fair_value_line_views) {
|
||||
fvlv.refresh(update_frozen);
|
||||
}
|
||||
if (session_low_dirty)
|
||||
_low_index = null;
|
||||
if (session_high_dirty)
|
||||
@@ -1264,6 +1487,11 @@ namespace mbts.spark {
|
||||
if (double.IsNaN(new_origin))
|
||||
new_origin = _owner._get_bid_market_break();
|
||||
origin_dirty = ConvertUtils.update_value_type(ref _origin, new_origin);
|
||||
if (update_frozen) {
|
||||
_frozen_bid = _bid;
|
||||
_frozen_ask = _ask;
|
||||
_frozen_mid = _mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,8 @@ namespace mbts.spark {
|
||||
if (e.ChangedButton == MouseButton.Middle) {
|
||||
// create the new popup window in here
|
||||
var obvm = order.create_orderbook_vm();
|
||||
if (obvm is null)
|
||||
return;
|
||||
await Task.Delay(100); // Wait for obvm to load rows collection before showing UI to avoid a flash
|
||||
var view = new MiniOrderBookView(obvm);
|
||||
var popup = new System.Windows.Controls.Primitives.Popup {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using bts.api;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderPriceLineViewAdapter {
|
||||
public static double bid_line(
|
||||
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ask_line(
|
||||
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double mid_line(
|
||||
double bid, double ask, double settlement, ContractSpecificationImmutable? spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (double.IsNaN(bid)) {
|
||||
if (double.IsNaN(ask)) {
|
||||
return settlement;
|
||||
}
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (double.IsNaN(ask)) {
|
||||
return bid;
|
||||
}
|
||||
if (spec.HasValue) {
|
||||
return TickUtils.mid_tick(spec.Value, bid, ask);
|
||||
}
|
||||
return bid;
|
||||
}
|
||||
}
|
||||
|
||||
static double _try_tick_down(ContractSpecificationImmutable? spec, double value) {
|
||||
if (spec.HasValue) {
|
||||
return spec.Value.tickdown(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,20 +12,24 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using bts.utils;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class BadgeRender {
|
||||
// IMPORTANT: the return values for this method MUST BE IN SYNC with the "format" method, else "measure_height" and "LadderRender.measure_row_height" will be broken.
|
||||
static int _line_count_for_style(LadderSettings.BadgeStyle badge_style) => badge_style == LadderSettings.BadgeStyle.Alternate ? 2 : 1;
|
||||
// IMPORTANT: at the risk of being redundant, see above comment.
|
||||
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style) {
|
||||
public static IReadOnlyList<string> format(Side side, uint quantity, uint filled, LadderSettings.BadgeStyle style, Func<uint, string> fmt_value = null) {
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
switch (style) {
|
||||
case LadderSettings.BadgeStyle.Alternate:
|
||||
var working_qty = quantity > filled ? quantity - filled : 0;
|
||||
return new[] {
|
||||
$"{side.abbreviate()}:{filled}",
|
||||
$"W:{quantity - filled}"
|
||||
$"{side.abbreviate()}:{fmt_value(filled)}",
|
||||
$"W:{fmt_value(working_qty)}"
|
||||
};
|
||||
default: return new[] { $"{filled}/{quantity}" };
|
||||
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +43,12 @@ namespace mbts.spark {
|
||||
static class LadderRender {
|
||||
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
|
||||
// (see how it is incorporated in measure_row_height() below)
|
||||
public static readonly Thickness row_padding = new Thickness(6);
|
||||
public static Thickness row_padding => LadderSettings.mimic_tt ?
|
||||
new Thickness(4, 2, 4, 2) : new Thickness(6);
|
||||
|
||||
const string tick_separator_period = ".";
|
||||
const string tick_separator_dash = "-";
|
||||
const int GRID_LINE_HEIGHT = 1;
|
||||
|
||||
// IMPORTANT: this method ONLY WORKS if Badges use the BadgeRender.margin/border_thickness/padding values.
|
||||
// TODO: elminate the dependency on BadgeRender.margin/border_thickness/padding by integrating them into settings.
|
||||
@@ -54,11 +61,20 @@ namespace mbts.spark {
|
||||
public static int index_of(double[] prices, double price, IComparer<double> comparer) =>
|
||||
Array.BinarySearch(prices, price, comparer);
|
||||
|
||||
public static GlyphRun format_text(string text, int row, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) =>
|
||||
public static GlyphRun format_text(
|
||||
string text,
|
||||
int row,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
GlyphContext glyph_context,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) =>
|
||||
glyph_context.get_glyph_run(
|
||||
// long term there is probably a better way to get rid of the separator, such as in the TickExtensions._format_tick
|
||||
price_display_format == LadderSettings.PriceDisplayFormat.Integer ? text.Replace(tick_separator_period, string.Empty).Replace(tick_separator_dash, string.Empty) : text,
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Center, 0, VerticalAlignment.Center, row * row_height, true);
|
||||
price_display_format == LadderSettings.PriceDisplayFormat.Integer ?
|
||||
text.Replace(tick_separator_period, string.Empty).Replace(tick_separator_dash, string.Empty) :
|
||||
text,
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
|
||||
-4, VerticalAlignment.Center, row * row_height, true);
|
||||
|
||||
public static double[] gen_prices(double mid, int row_count, ContractSpecificationImmutable spec) {
|
||||
// require odd number
|
||||
@@ -81,7 +97,7 @@ namespace mbts.spark {
|
||||
public static GlyphRun[] format_prices(double[] prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec) {
|
||||
var result = new GlyphRun[prices.Length];
|
||||
Parallel.For(0, prices.Length, i =>
|
||||
result[i] = format_text(spec.format(prices[i], reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
public static GlyphRun[] format_prices(double[] prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, ContractSpecificationImmutable spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
@@ -145,14 +161,13 @@ namespace mbts.spark {
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
// draw column backgrounds
|
||||
foreach (var col in columns) {
|
||||
var background = col.settings.background.rgb_brush();
|
||||
background.Freeze();
|
||||
var background = col.settings.background.argb_brush();
|
||||
dc.DrawRectangle(background, null, new Rect(col.x_left, 0, col.settings.width, rowcount * row_height));
|
||||
}
|
||||
// draw grid lines
|
||||
for (int row = 1; row < rowcount; ++row) {
|
||||
double y = row * row_height;
|
||||
dc.DrawRectangle(brush, null, new Rect(0, y, 500, 1));
|
||||
dc.DrawRectangle(brush, null, new Rect(0, y, 500, GRID_LINE_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,9 +181,18 @@ namespace mbts.spark {
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<NativePriceSize, string> fmt
|
||||
) {
|
||||
Func<NativePriceSize, string> fmt,
|
||||
bool lighten_market_cells,
|
||||
Color? market_cell_background) {
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
Brush market_cell_background_brush;
|
||||
if (market_cell_background.HasValue) {
|
||||
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
|
||||
} else if (lighten_market_cells) {
|
||||
market_cell_background_brush = settings.background.rgb_color().blend_with(Colors.White).to_brush();
|
||||
} else {
|
||||
market_cell_background_brush = null;
|
||||
}
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Length * row_height));
|
||||
@@ -177,15 +201,32 @@ namespace mbts.spark {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
for (int i = 0; i < rungs.Length; i++) {
|
||||
if (rungs[i].is_nan) break;
|
||||
double price = rungs[i].price;
|
||||
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
|
||||
var row_tick_groups = rungs
|
||||
.TakeWhile(nps => !nps.is_nan)
|
||||
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
|
||||
(aggr, rung) => rung.with(
|
||||
direct_size: aggr.direct_size + rung.direct_size,
|
||||
implied_size: aggr.implied_size + rung.implied_size),
|
||||
rung => {
|
||||
//spec.round_to_tick(rung.price);
|
||||
var ticks = spec.full_ticks_from_zero(rung.price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
});
|
||||
foreach (var rung in row_tick_groups) {
|
||||
//double price = spec.round_to_tick(rung.price);
|
||||
var ticks = spec.full_ticks_from_zero(rung.price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.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(fmt(rungs[i]), curr_index, row_height, settings, glyph_context);
|
||||
if (market_cell_background_brush != null)
|
||||
dc.DrawRectangle(market_cell_background_brush, null,
|
||||
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
|
||||
settings.width, row_height - GRID_LINE_HEIGHT));
|
||||
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
@@ -193,16 +234,17 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public static void render_volumes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
double[] prices,
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction
|
||||
) {
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
double[] prices,
|
||||
IEnumerable<VolumeAtPriceLevelImmutable> data,
|
||||
double row_height,
|
||||
ContractSpecificationImmutable spec,
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction,
|
||||
Func<uint, string> fmt_value) {
|
||||
//
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
@@ -224,6 +266,8 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
@@ -255,11 +299,11 @@ namespace mbts.spark {
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(v.ToString());
|
||||
numbers_text.Add(fmt_value(v));
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(vap.Volume.ToString());
|
||||
numbers_text.Add(fmt_value(vap.Volume));
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
@@ -309,19 +353,33 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings) {
|
||||
internal static void render_last(DrawingVisual dv, GlyphContext glyph_context, NativePriceSize last, int index, double row_height, LadderColumnSettings settings, Func<uint, string> fmt_value) {
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
var gr = format_text(last.size.ToString(), index, row_height, settings, glyph_context);
|
||||
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
|
||||
}
|
||||
}
|
||||
|
||||
// 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, IEnumerable<(double price, uint piq)> pvp, 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,
|
||||
Func<uint, string> fmt_value) {
|
||||
//
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
|
||||
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
|
||||
return;
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<uint>();
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
@@ -332,7 +390,7 @@ namespace mbts.spark {
|
||||
curr_index += tick_diff;
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Length) {
|
||||
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
|
||||
var gr = format_text(fmt_value(entry.piq), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
@@ -353,7 +411,7 @@ namespace mbts.spark {
|
||||
public static void render_session_range(DrawingVisual dv, uint color, LadderColumnSettings settings, int low_index, int high_index, double[] prices, double row_height) {
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Length))
|
||||
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));
|
||||
dc.DrawRectangle(new SolidColorBrush(color.argb_color()), 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) {
|
||||
@@ -371,4 +429,4 @@ namespace mbts.spark {
|
||||
using (dv.RenderOpen()) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,18 @@
|
||||
<cv:KeyValue Key="{u:Int 0}" Value="{u:Visibility Collapsed}"/>
|
||||
</cv:ValueMap>
|
||||
<c:BadgeTextLinesConverter x:Key="BadgeTextLinesConverter"/>
|
||||
<cv:ValueMap x:Key="RefreshModeToBooleanConverter">
|
||||
<cv:KeyValue Key="{x:Static Manual, MemberType={u:Nested sp:LadderMarketBreakLine.RefreshMode}}">
|
||||
<cv:KeyValue.Value>
|
||||
<sys:Boolean>false</sys:Boolean>
|
||||
</cv:KeyValue.Value>
|
||||
</cv:KeyValue>
|
||||
<cv:KeyValue Key="{x:Static Auto, MemberType={u:Nested sp:LadderMarketBreakLine.RefreshMode}}">
|
||||
<cv:KeyValue.Value>
|
||||
<sys:Boolean>true</sys:Boolean>
|
||||
</cv:KeyValue.Value>
|
||||
</cv:KeyValue>
|
||||
</cv:ValueMap>
|
||||
|
||||
<Style x:Key="ColumnBackground" TargetType="Rectangle">
|
||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
||||
@@ -161,6 +173,191 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelPriceFontColorPopup" TargetType="c:PopupControl">
|
||||
<Setter Property="Width" Value="60"/>
|
||||
<Setter Property="Margin" Value="4,2"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelPricePopupLabel" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Margin" Value="2,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelPricePopup" TargetType="c:PopupControl">
|
||||
<Setter Property="Background" Value="{StaticResource ComboBox.Static.Background}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Padding" Value="6,2"/>
|
||||
<Setter Property="PopupContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="Border" BasedOn="{StaticResource PopupContainerStyleBase}">
|
||||
<Setter Property="Padding" Value="4"/>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="MinHeight" Value="24"/>
|
||||
<Setter Property="Header" Value="..."/>
|
||||
<Setter Property="Content" Value="{Binding}"/>
|
||||
<Setter Property="ShowExpanderArrow" Value="False"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:FontPicker
|
||||
Grid.ColumnSpan="2"
|
||||
SelectedFontFamily="{Binding font_family}"
|
||||
SelectedFontSize="{Binding font_size}"
|
||||
SelectedFontWeight="{Binding font_weight}"
|
||||
/>
|
||||
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}" Margin="4,0">Font Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding font_color_palettes}"
|
||||
SelectedColor="{Binding font_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
<TextBlock Grid.Row="4" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding background_color_palettes}"
|
||||
SelectedColor="{Binding background_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
<CheckBox Grid.Row="6" IsChecked="{Binding show_label}">Show Label</CheckBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelRelativePricePopup" TargetType="c:PopupControl" BasedOn="{StaticResource LeftPanelPricePopup}">
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:FontPicker
|
||||
SelectedFontFamily="{Binding font_family}"
|
||||
SelectedFontSize="{Binding font_size}"
|
||||
SelectedFontWeight="{Binding font_weight}"
|
||||
/>
|
||||
<GroupBox Grid.Row="2" Header="Negative">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Style="{StaticResource LeftPanelPricePopupLabel}">Font Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding negative_font_color_palettes}"
|
||||
SelectedColor="{Binding negative_font_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding negative_background_color_palettes}"
|
||||
SelectedColor="{Binding negative_background_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="4" Header="Zero">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Style="{StaticResource LeftPanelPricePopupLabel}">Font Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding font_color_palettes}"
|
||||
SelectedColor="{Binding font_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding background_color_palettes}"
|
||||
SelectedColor="{Binding background_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Row="6" Header="Positive">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Label"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Style="{StaticResource LeftPanelPricePopupLabel}">Font Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding positive_font_color_palettes}"
|
||||
SelectedColor="{Binding positive_font_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
<TextBlock Grid.Row="2" Style="{StaticResource LeftPanelPricePopupLabel}">Background Color</TextBlock>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Palettes="{Binding positive_background_color_palettes}"
|
||||
SelectedColor="{Binding positive_background_color}"
|
||||
Style="{StaticResource LeftPanelPriceFontColorPopup}"
|
||||
/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<CheckBox Grid.Row="8" IsChecked="{Binding show_label}">Show Label</CheckBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LadderColumnSample" TargetType="Border">
|
||||
<Setter Property="Grid.Column" Value="6"/>
|
||||
<Setter Property="Background" Value="{Binding background, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
@@ -455,6 +652,68 @@
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
|
||||
<u:KeyedTemplateSelector x:Key="OrderEntryPanelItemTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsPanelTemplateKeys.VisibilityItem}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
|
||||
<ColumnDefinition Width="*" SharedSizeGroup="B"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsPanelTemplateKeys.PricePoint}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
|
||||
<ColumnDefinition Width="*" SharedSizeGroup="B"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
<c:PopupControl Grid.Column="1" Margin="4,2" Style="{StaticResource LeftPanelPricePopup}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static usp:LadderSettingsPanelTemplateKeys.RelativePricePoint}">
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="A"/>
|
||||
<ColumnDefinition Width="*" SharedSizeGroup="B"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
<c:PopupControl Grid.Column="1" Margin="4,2" Style="{StaticResource LeftPanelRelativePricePopup}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<TabControl>
|
||||
<TabControl.ItemContainerStyle>
|
||||
@@ -525,10 +784,12 @@
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition SharedSizeGroup="c5"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="6,0" Text="Show" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Color" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" HorizontalAlignment="Center" Margin="6,0" Text="Auto Refresh" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" HorizontalAlignment="Center" Margin="6,0" Text="Color" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="4" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="6,0" Text="Sample" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding mkt_break_items}" Margin="8,0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
@@ -540,20 +801,28 @@
|
||||
<ColumnDefinition SharedSizeGroup="c2"/>
|
||||
<ColumnDefinition SharedSizeGroup="c3"/>
|
||||
<ColumnDefinition SharedSizeGroup="c4"/>
|
||||
<ColumnDefinition SharedSizeGroup="c5"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="{u:BTSColor header}" Padding="6,3">
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding name}" Foreground="{u:BTSColor header_text}"/>
|
||||
</Border>
|
||||
<CheckBox Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding show}"/>
|
||||
<c:ColorPickerPopup
|
||||
<CheckBox
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding refresh_mode, Converter={StaticResource RefreshModeToBooleanConverter}, Mode=TwoWay}"
|
||||
IsEnabled="{Binding can_edit_refresh_mode}"
|
||||
/>
|
||||
<c:ColorPickerPopup
|
||||
Grid.Column="3"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
PopupPlacement="Left"
|
||||
Palettes="{Binding palettes}"
|
||||
SelectedColor="{Binding color}"
|
||||
/>
|
||||
<Grid Grid.Column="3" Grid.ColumnSpan="2" Height="27">
|
||||
<Grid Grid.Column="4" Grid.ColumnSpan="2" Height="27">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
@@ -623,13 +892,16 @@
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
</Grid>
|
||||
<Rectangle Grid.Column="1" Grid.ColumnSpan="5" Fill="DimGray" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
|
||||
<Rectangle Grid.Column="1" Grid.ColumnSpan="6" Fill="DimGray" Height="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Fair Value Lines" VerticalAlignment="Stretch">
|
||||
<usp:FairValueLineSettingsView HorizontalAlignment="Left" MaxHeight="300" DataContext="{Binding fair_value_line_settings_vm}"/>
|
||||
</TabItem>
|
||||
<!-- Market Price & Size Tab -->
|
||||
<TabItem Header="Market Price and Size Display">
|
||||
<Grid Margin="6" Grid.IsSharedSizeScope="True">
|
||||
@@ -656,7 +928,7 @@
|
||||
ItemsSource="{Binding format_options}"
|
||||
SelectedItem="{Binding selected_format}"
|
||||
/>
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<TextBlock
|
||||
Margin="{StaticResource MarginIndentChild}"
|
||||
Text="when implied is > 0"
|
||||
@@ -670,6 +942,7 @@
|
||||
IsEnabled="{Binding can_choose_alternate_implied_format}"
|
||||
ItemsSource="{Binding alternate_implied_format_options}"
|
||||
SelectedItem="{Binding selected_alternate_implied_format}"
|
||||
VerticalAlignment="Top"
|
||||
/>
|
||||
</StackPanel>
|
||||
<Border Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="12,4,0,0" Background="Transparent" BorderBrush="DimGray" BorderThickness="1" ToolTip="Sample Output">
|
||||
@@ -728,7 +1001,38 @@
|
||||
Visibility="{Binding is_synthetic, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}"
|
||||
Padding="4"
|
||||
>
|
||||
<c:RadioButtonsControl ItemsSource="{Binding price_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_format}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock>Ticks Per Row</TextBlock>
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:TickUpDownUInt
|
||||
Maximum="100"
|
||||
TickUpCommand="{Binding DataContext.tick_up_ticks_per_row_cmd, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
TickDownCommand="{Binding DataContext.tick_down_ticks_per_row_cmd, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
Value="{Binding DataContext.ticks_per_row, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}}"
|
||||
/>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="2,0,0,0"
|
||||
Text="{Binding DataContext.rounded_ticks_per_row, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, StringFormat='({0})'}"
|
||||
ToolTip="The ticks per row parameter may need to be adjusted to fit evenly into dollars."
|
||||
/>
|
||||
</Grid>
|
||||
<c:RadioButtonsControl Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding price_format_options}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding price_format}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
Grid.Row="1"
|
||||
@@ -828,6 +1132,7 @@
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c1"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c2"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Command="{Binding DataContext.remove_item_cmd, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"/>
|
||||
<ComboBox Grid.Column="1" ItemsSource="{Binding algo_definitions_view}" SelectedItem="{Binding algo_definition}">
|
||||
@@ -837,7 +1142,12 @@
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Column="2" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}"> Stay Enabled?</CheckBox>
|
||||
<CheckBox Grid.Column="2" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding is_sticky}" ToolTip="Stay Enabled" Margin="4,0">
|
||||
Stay Enabled
|
||||
</CheckBox>
|
||||
<CheckBox Grid.Column="3" FontSize="10" VerticalAlignment="Center" VerticalContentAlignment="Center" IsChecked="{Binding auto_revert}" ToolTip="Auto Revert" Margin="0,0,4,0">
|
||||
Auto Revert
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
@@ -903,27 +1213,23 @@
|
||||
<TextBlock VerticalAlignment="Center">Left Panel Width</TextBlock>
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource SliderValueTextWidth}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Slider Grid.Column="2" Minimum="{x:Static sp:LadderView.MinLeftPanelWidth}" Maximum="{x:Static sp:LadderView.MaxLeftPanelWidth}" Value="{Binding order_entry_panel_width}"/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Text="{Binding order_entry_panel_width, StringFormat='0.00'}"/>
|
||||
<Slider Grid.Column="0" Minimum="{x:Static sp:LadderView.MinLeftPanelWidth}" Maximum="{x:Static sp:LadderView.MaxLeftPanelWidth}" Value="{Binding order_entry_panel_width}"/>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding order_entry_panel_width, StringFormat='0.00'}"/>
|
||||
</Grid>
|
||||
<!--Zoom Factor-->
|
||||
<TextBlock Grid.Row="2" VerticalAlignment="Center">Zoom Factor</TextBlock>
|
||||
<Grid Grid.Row="2" Grid.Column="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource SliderValueTextWidth}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Slider Grid.Column="2" Minimum="{x:Static sp:LadderView.MinZoomFactor}" Maximum="{x:Static sp:LadderView.MaxZoomFactor}" Value="{Binding zoom_factor}"/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Text="{Binding zoom_factor, StringFormat='0.00'}"/>
|
||||
<Slider Grid.Column="0" Minimum="{x:Static sp:LadderView.MinZoomFactor}" Maximum="{x:Static sp:LadderView.MaxZoomFactor}" Value="{Binding zoom_factor}"/>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding zoom_factor, StringFormat='0.00'}"/>
|
||||
</Grid>
|
||||
<!--
|
||||
- Note the 'Delays' in the 'SelectedItem' bindings; this is a workaround for a wpf quirk when the DataContext switches (i.e., when the selected Profile changes in the ShellSettingsDialog).
|
||||
@@ -938,7 +1244,7 @@
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" ItemsSource="{Binding time_in_force_options}" SelectedItem="{Binding default_time_in_force, Delay=1}"/>
|
||||
<!--Algo-->
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" VerticalAlignment="Center">Algo</TextBlock>
|
||||
<ComboBox Grid.Row="8" Grid.Column="6" ItemsSource="{Binding Source={StaticResource AlgoDefinitionsViewSource}}" SelectedItem="{Binding default_algo, Delay=1}">
|
||||
<ComboBox Grid.Row="8" Grid.Column="2" ItemsSource="{Binding Source={StaticResource AlgoDefinitionsViewSource}}" SelectedItem="{Binding default_algo, Delay=1}">
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
@@ -960,30 +1266,16 @@
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
<GroupBox Grid.Column="2" Header="Show/Hide" Padding="8,4">
|
||||
<ItemsControl Grid.IsSharedSizeScope="True" ItemsSource="{Binding order_entry_panel_items}">
|
||||
<ItemsControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
ItemsSource="{Binding order_entry_panel_items}"
|
||||
ItemTemplateSelector="{StaticResource OrderEntryPanelItemTemplateSelector}"
|
||||
>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="A" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Margin="0,5,5,0">
|
||||
<CheckBox Content="{Binding name}" FlowDirection="RightToLeft" IsChecked="{Binding show}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight" />
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</GroupBox>
|
||||
<!--
|
||||
@@ -1013,45 +1305,47 @@
|
||||
<Grid HorizontalAlignment="Left" Margin="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush3}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- market cross -->
|
||||
<CheckBox Name="warn" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding show_warn_on_cross}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
<CheckBox Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding show_warn_on_cross}">
|
||||
<TextBlock
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding show_warn_on_cross}"
|
||||
Text="Warn on Market Cross"
|
||||
/>
|
||||
</CheckBox>
|
||||
<TextBlock Grid.Column="1"
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding ElementName=warn, Path=IsChecked}"
|
||||
Text="Warn on Market Cross" />
|
||||
<!-- max order qty -->
|
||||
<CheckBox Name="enforce_max_clip" Grid.Row="1" Grid.Column="0" FlowDirection="RightToLeft" IsChecked="{Binding enforce_max_clip}">
|
||||
<CheckBox.Resources>
|
||||
<Style TargetType="{x:Type Path}">
|
||||
<Setter Property="FlowDirection" Value="LeftToRight"/>
|
||||
</Style>
|
||||
</CheckBox.Resources>
|
||||
<CheckBox Grid.Row="2" Grid.Column="0" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_clip}">
|
||||
<TextBlock
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding enforce_max_clip}"
|
||||
Text="Max Order Quantity"
|
||||
/>
|
||||
</CheckBox>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
|
||||
MinWidth="{Binding ElementName=warn, Path=ActualWidth}"
|
||||
Text="Max Order Quantity"/>
|
||||
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2"
|
||||
IsEnabled="{Binding ElementName=enforce_max_clip, Path=IsChecked}"
|
||||
Value="{Binding max_clip}" />
|
||||
<c:TickUpDownUInt
|
||||
Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding enforce_max_clip}"
|
||||
Value="{Binding max_clip}"
|
||||
Width="60"
|
||||
/>
|
||||
<CheckBox Grid.Row="4" VerticalContentAlignment="Center" IsChecked="{Binding enforce_max_settlement_cross_ticks}">
|
||||
<TextBlock
|
||||
Style="{StaticResource EnableDisableFeatureText}"
|
||||
IsEnabled="{Binding enforce_max_settlement_cross_ticks}"
|
||||
Text="Max Ticks from Settlement"
|
||||
/>
|
||||
</CheckBox>
|
||||
<c:TickUpDownInt Grid.Row="4" Grid.Column="2" IsEnabled="{Binding enforce_max_settlement_cross_ticks}" Value="{Binding max_settlement_cross_ticks}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Mini Order Book">
|
||||
|
||||
@@ -37,4 +37,10 @@ namespace mbts.ui.spark {
|
||||
public static uint GetVolumeComponentValue(DependencyObject d) => (uint)d.GetValue(VolumeComponentValueProperty);
|
||||
public static void SetVolumeComponentValue(DependencyObject d, uint value) => d.SetValue(VolumeComponentValueProperty, value);
|
||||
}
|
||||
|
||||
class LadderSettingsPanelTemplateKeys {
|
||||
public static string VisibilityItem = nameof(VisibilityItem);
|
||||
public static string PricePoint = nameof(PricePoint);
|
||||
public static string RelativePricePoint = nameof(RelativePricePoint);
|
||||
}
|
||||
}
|
||||
@@ -126,9 +126,12 @@ namespace mbts.spark {
|
||||
return Cursors.Arrow;
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Bid:
|
||||
case ColumnType.Ask: return _can_send_orders ? Cursors.Hand : Cursors.No;
|
||||
default: return Cursors.Arrow;
|
||||
case ColumnType.Ask:
|
||||
if (!_can_send_orders)
|
||||
return Cursors.No;
|
||||
break;
|
||||
}
|
||||
return Cursors.Arrow;
|
||||
}
|
||||
|
||||
#region UI Event Handlers
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<converters:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="OpenTextConverter" LongText="Open:" ShortText="O:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="SettleTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="SettlementTextConverter" LongText="Settle:" ShortText="S:" Threshold="99" />
|
||||
<converters:ResponsiveTextConverter x:Key="VolumeTextConverter" LongText="Volume:" ShortText="V:" Threshold="99" />
|
||||
<converters:DoubleSubtractionConverter x:Key="DoubleSubtractionConverter" Value="4" />
|
||||
<converters:DoubleSubtractionConverter x:Key="HeightDoubleSubtractionConverter" Value="6" />
|
||||
@@ -48,7 +48,7 @@
|
||||
<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="MinWidth" Value="{x:Static spark:LadderGridHost.ColumnMinWidth}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
|
||||
@@ -141,7 +141,61 @@
|
||||
<Setter Property="Margin" Value="2,0,2,5"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
</Style>
|
||||
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{mbts:BTSColor UI.Button.normal_border}"
|
||||
Background="{mbts:BTSColor UI.Button.normal_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{mbts:BTSColor UI.Button.mouseover_background}" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel Buys</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel Sells</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="0" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">Cancel Buys</TextBlock>
|
||||
@@ -177,6 +231,7 @@
|
||||
</Style>
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Background" Value="{mbts:BTSColor MarketData.ask}" />
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">Cancel Sells</TextBlock>
|
||||
@@ -210,39 +265,6 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{mbts:BTSColor UI.Button.normal_border}"
|
||||
Background="{mbts:BTSColor UI.Button.normal_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{mbts:BTSColor UI.Button.mouseover_background}" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{mbts:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -277,6 +299,23 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Left Panel Price Points -->
|
||||
<Style x:Key="LeftPanelPricePointLabel" TargetType="TextBlock">
|
||||
<Setter Property="Margin" Value="0,1,0,4"/>
|
||||
<Setter Property="VerticalAlignment" Value="Top"/>
|
||||
</Style>
|
||||
<Style x:Key="LeftPanelPricePointBorder" TargetType="Border">
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="0,0,0,2"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
</Style>
|
||||
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{mbts:BTSColor UI.default_text}"/>
|
||||
</Style>
|
||||
|
||||
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
|
||||
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
|
||||
</ResourceDictionary>
|
||||
@@ -345,90 +384,188 @@
|
||||
<ColumnDefinition Width="6"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Day -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_day, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="0" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Foreground="{Binding change, Converter={StaticResource NumericSignBrushConverter}}"
|
||||
Visibility="{Binding show_day, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="change"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- High -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_high, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="1" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_high, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Low -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_low, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="2" Foreground="{mbts:BTSColor UI.default_text}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_low, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Open -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="3"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_open, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="3" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_open, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Settle -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="4"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource SettleTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_settle, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="4" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_settle, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<!-- Day Change -->
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
|
||||
Visibility="{Binding day_change_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}"
|
||||
/>
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Background="{Binding day_change_background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Visibility="{Binding day_change_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Day Change"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}"
|
||||
>
|
||||
<TextBlock
|
||||
FontFamily="{Binding day_change_style.font_family}"
|
||||
FontSize="{Binding day_change_style.font_size}"
|
||||
FontWeight="{Binding day_change_style.font_weight}"
|
||||
Foreground="{Binding day_change_font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="day_change"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
<Binding Path="day_change_display_settings"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<!-- Volume -->
|
||||
<TextBlock Grid.Column="0" Grid.Row="5"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock Grid.Column="2" Grid.Row="5" Text="{Binding volume, Mode=OneWay}" FontWeight="Bold"
|
||||
Margin="0,0,0,4"
|
||||
Visibility="{Binding show_volume, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Visibility="{Binding volume_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}"
|
||||
/>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Background="{Binding volume_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Visibility="{Binding volume_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Volume"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}"
|
||||
>
|
||||
<TextBlock
|
||||
FontFamily="{Binding volume_style.font_family}"
|
||||
FontSize="{Binding volume_style.font_size}"
|
||||
FontWeight="{Binding volume_style.font_weight}"
|
||||
Foreground="{Binding volume_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<converters:MultiValueConverterPipe>
|
||||
<x:Static MemberType="{x:Type converters:DynamicDoubleDivisorConverter}" Member="Instance"/>
|
||||
<x:Static MemberType="{x:Type converters:ValueConverters}" Member="SimpleString"/>
|
||||
</converters:MultiValueConverterPipe>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="volume" Mode="OneWay"/>
|
||||
<Binding Path="display_quantity_divisor" Mode="OneTime"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<!-- High -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
|
||||
Visibility="{Binding session_high_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}"
|
||||
/>
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Background="{Binding session_high_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Visibility="{Binding session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session High"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}"
|
||||
>
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_high_style.font_family}"
|
||||
FontSize="{Binding session_high_style.font_size}"
|
||||
FontWeight="{Binding session_high_style.font_weight}"
|
||||
Foreground="{Binding session_high_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<!-- Low -->
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
|
||||
Visibility="{Binding session_low_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}"
|
||||
/>
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Background="{Binding session_low_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Visibility="{Binding session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session Low"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}"
|
||||
>
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_low_style.font_family}"
|
||||
FontSize="{Binding session_low_style.font_size}"
|
||||
FontWeight="{Binding session_low_style.font_weight}"
|
||||
Foreground="{Binding session_low_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<!-- Open -->
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
|
||||
Visibility="{Binding open_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}"
|
||||
/>
|
||||
<Border
|
||||
Grid.Row="4"
|
||||
Background="{Binding open_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Visibility="{Binding open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Open"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}"
|
||||
>
|
||||
<TextBlock
|
||||
FontFamily="{Binding open_style.font_family}"
|
||||
FontSize="{Binding open_style.font_size}"
|
||||
FontWeight="{Binding open_style.font_weight}"
|
||||
Foreground="{Binding open_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<!-- Settlement -->
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource SettlementTextConverter}}"
|
||||
Visibility="{Binding settlement_style.actual_show_label, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}"
|
||||
/>
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
Background="{Binding settlement_style.background_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Visibility="{Binding settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Settlement"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}"
|
||||
>
|
||||
<TextBlock
|
||||
FontFamily="{Binding settlement_style.font_family}"
|
||||
FontSize="{Binding settlement_style.font_size}"
|
||||
FontWeight="{Binding settlement_style.font_weight}"
|
||||
Foreground="{Binding settlement_style.font_color, Converter={x:Static converters:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}"
|
||||
>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!-- Account combobox -->
|
||||
<ComboBox DataContext="{Binding order_properties_vm}" Margin="2,10,2,2"
|
||||
@@ -507,7 +644,7 @@
|
||||
mbts:TextBoxBehavior.IncrementDecrementOnMouseClick="True"
|
||||
HorizontalContentAlignment="Right"
|
||||
Margin="2"
|
||||
Text="{Binding size_calculator.value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Text="{Binding size_calculator.display_value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ContextMenu="{x:Null}"
|
||||
/>
|
||||
<!-- Qty quick buttons -->
|
||||
@@ -522,7 +659,14 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" Content="{Binding}" Focusable="False"/>
|
||||
<Button Margin="2" Command="{Binding Path=DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Content="{Binding}" Focusable="False">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:DynamicDoubleMultiplierConverter.Instance}">
|
||||
<Binding/>
|
||||
<Binding Path="DataContext.display_quantity_divisor" RelativeSource="{RelativeSource AncestorType=spark:LadderView}"/>
|
||||
</MultiBinding>
|
||||
</Button.CommandParameter>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
@@ -547,15 +691,20 @@
|
||||
</ItemsPanelTemplate>
|
||||
</uag:AlgoTemplateFavoriteButtonsView.ItemsPanel>
|
||||
</uag:AlgoTemplateFavoriteButtonsView>
|
||||
<StackPanel Margin="0,17,0,0" VerticalAlignment="Top">
|
||||
<Grid Margin="0,17,0,0" VerticalAlignment="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Cancel Buttons -->
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource CancelBuys}"
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Bids" Style="{StaticResource {x:Static spark:LadderView.CancelBuysButtonStyleKey}}"
|
||||
Visibility="{Binding show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource CancelSells}"
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="Asks" Style="{StaticResource {x:Static spark:LadderView.CancelSellsButtonStyleKey}}"
|
||||
Visibility="{Binding show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource CancelAll}"
|
||||
<Button Command="{Binding cancel_command}" CommandParameter="All" Style="{StaticResource {x:Static spark:LadderView.CancelAllButtonStyleKey}}"
|
||||
Visibility="{Binding show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="1" Margin="0,17,0,0" VerticalAlignment="Bottom">
|
||||
<!-- Settings Button -->
|
||||
<Button Command="{Binding settings_command}" Style="{StaticResource SettingsButton}"/>
|
||||
|
||||
@@ -22,6 +22,22 @@ namespace mbts.spark {
|
||||
MaxLeftPanelWidth = 150,
|
||||
MinZoomFactor = 0.5,
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
static LadderView() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
MimicTT = knobs.mimic_tt;
|
||||
}
|
||||
|
||||
public static bool MimicTT { get; }
|
||||
|
||||
public static string CancelBuysButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelBuys" : "CancelBuys";
|
||||
|
||||
public static string CancelSellsButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelSells" : "CancelSells";
|
||||
|
||||
public static string CancelAllButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelAll" : "CancelAll";
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
|
||||
@@ -93,6 +109,7 @@ namespace mbts.spark {
|
||||
_ui_scaler.MouseDoubleClick += _ui_scaler_MouseDoubleClick;
|
||||
this.PreviewMouseWheel += LadderView_PreviewMouseWheel;
|
||||
this.PreviewMouseDown += LadderView_PreviewMouseDown;
|
||||
PreviewKeyDown += (s, e) => vm.key_down(e);
|
||||
}
|
||||
|
||||
IDisposable _connect_left_panel_scroll() {
|
||||
@@ -252,4 +269,4 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using bts.api.position;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -25,24 +26,27 @@ namespace bts.shell.manual_fills {
|
||||
readonly Property<bool> _is_dirty;
|
||||
readonly Property<EditableItemState> _editable_item_state;
|
||||
readonly DelegateProperty<bool> _is_valid;
|
||||
readonly DisplayQuantityMultiplier<uint> _size = DisplayQuantityMultiplier.create_uint();
|
||||
readonly Func<SecurityImmutable?, uint> _get_display_qty_divisor;
|
||||
SecurityImmutable? _security;
|
||||
AccountTuple? _account;
|
||||
UserTuple? _user;
|
||||
Side _side;
|
||||
uint _size;
|
||||
double _price;
|
||||
bool _is_selected;
|
||||
ManualFillData? _data;
|
||||
IDisposable _committed_snapshot;
|
||||
|
||||
public ManualFillRow(ManualFillData data) {
|
||||
public ManualFillRow(ManualFillData data, Func<SecurityImmutable?, uint> get_display_qty_divisor) {
|
||||
_data = data;
|
||||
_get_display_qty_divisor = get_display_qty_divisor ?? new Func<SecurityImmutable?, uint>(sec => 1);
|
||||
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;
|
||||
_size.actual_value = data.value.manual_fill.Quantity;
|
||||
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(data.value.security), QuantityConversionData.OpType.DivideActual);
|
||||
_price = data.value.manual_fill.Price;
|
||||
if (placeholder_key.HasValue) {
|
||||
_is_dirty = backing(nameof(is_dirty), false);
|
||||
@@ -53,6 +57,7 @@ namespace bts.shell.manual_fills {
|
||||
_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));
|
||||
_dispose = _size.connect_owner(this, nameof(size), nameof(display_size));
|
||||
}
|
||||
|
||||
public event Action<bool> is_selected_changed;
|
||||
@@ -65,8 +70,10 @@ namespace bts.shell.manual_fills {
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (_set(ref _security, value))
|
||||
if (_set(ref _security, value)) {
|
||||
_size.conversion_data = new QuantityConversionData(_get_display_qty_divisor(value), QuantityConversionData.OpType.DivideActual);
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}
|
||||
}
|
||||
}
|
||||
public ContractSpecificationImmutable? spec => security.spec_or_default();
|
||||
@@ -75,7 +82,8 @@ namespace bts.shell.manual_fills {
|
||||
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 uint size { get => _size.actual_value; set => _size.actual_value = value; }
|
||||
public uint display_size { get => _size.display_value; set => _size.display_value = value; }
|
||||
public double price { get => _price; set => _set(ref _price, value); }
|
||||
// is selected
|
||||
public bool is_selected {
|
||||
|
||||
Loaded 100 of 755 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user