Compare commits
670
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e974edfc6e | ||
|
|
3b7740cee0 | ||
|
|
258d4a3fc1 | ||
|
|
c53f6425cd | ||
|
|
e5d77593d1 | ||
|
|
6580f3883b | ||
|
|
a66eae66bb | ||
|
|
090a9dfb17 | ||
|
|
27aa43f44d | ||
|
|
19e087aa69 | ||
|
|
167c8c23a0 | ||
|
|
d235f4be41 | ||
|
|
ec43dbd82d | ||
|
|
f07998d6a6 | ||
|
|
b3dc2cdcd4 | ||
|
|
0963aee137 | ||
|
|
c2f8e92b10 | ||
|
|
7ae0142dff | ||
|
|
db0ffba8ed | ||
|
|
a274a214b4 | ||
|
|
bbcfa3c75a | ||
|
|
89229d65f1 | ||
|
|
0e75cde409 | ||
|
|
42fa6dfaff | ||
|
|
400143c84d | ||
|
|
e915945880 | ||
|
|
ca6673e98f | ||
|
|
479f3dccff | ||
|
|
9601ebab20 | ||
|
|
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 | ||
|
|
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 | ||
|
|
cb0dfa50b3 | ||
|
|
2429e6019c | ||
|
|
e0bb48b0c7 | ||
|
|
a38b0dbc79 | ||
|
|
73065ab48a | ||
|
|
bd07c96504 | ||
|
|
dc4033875a | ||
|
|
130794c538 | ||
|
|
b1743f0b1c | ||
|
|
33492c9131 | ||
|
|
d434778a82 | ||
|
|
4c6cc9b3e0 | ||
|
|
6aaeddb7e9 | ||
|
|
1685a13994 | ||
|
|
54633dd6db | ||
|
|
510f163e5b | ||
|
|
ee4779da73 | ||
|
|
28e98259f6 | ||
|
|
8e6a17cd0c | ||
|
|
17b314ea3a | ||
|
|
cbde36e9e0 | ||
|
|
6fc20924bd | ||
|
|
381b8064fc | ||
|
|
fc1a3ff7dc | ||
|
|
5fd3d13cfe | ||
|
|
1b38822459 | ||
|
|
8b7ecc430b | ||
|
|
d9d7a08fd6 | ||
|
|
2383c00bb9 | ||
|
|
dcce738d53 | ||
|
|
0daa3e0685 | ||
|
|
82bc402216 | ||
|
|
ff70e9694b | ||
|
|
e06daa349c | ||
|
|
525d0eda30 | ||
|
|
e9e85b44c9 | ||
|
|
13aa693da1 | ||
|
|
f8afa7fc57 | ||
|
|
2f6a66a2ce | ||
|
|
b4674a9120 | ||
|
|
d76eb1153b | ||
|
|
0934c0014d | ||
|
|
19da3e1390 | ||
|
|
2654b2e446 | ||
|
|
61d0ae5257 | ||
|
|
ebea6a8f50 | ||
|
|
f0b3baaf3f | ||
|
|
956837d3f3 | ||
|
|
be45938b51 | ||
|
|
663f9ecc52 | ||
|
|
a6edbbe7b5 | ||
|
|
51276d161c | ||
|
|
3fa699b4c9 | ||
|
|
c9396bd6af | ||
|
|
b80f96fa5e | ||
|
|
b7dea6817c | ||
|
|
f4dc550f1a | ||
|
|
bc978ed065 | ||
|
|
202c5faffd | ||
|
|
a565dcbab7 | ||
|
|
65d55a7618 | ||
|
|
57c3ec34c2 | ||
|
|
4e85218d02 | ||
|
|
f6784f1196 | ||
|
|
7f05a2c2af | ||
|
|
c3d2101dcd | ||
|
|
8855ea44b0 | ||
|
|
51f70ad5ed | ||
|
|
5aa3f1a684 | ||
|
|
5cd3a710bd | ||
|
|
6d8b4636ab | ||
|
|
64a75c4b48 | ||
|
|
143b6ce5d8 | ||
|
|
aa5c0a5613 | ||
|
|
d5fc0c5a56 | ||
|
|
7c4daa0f4b | ||
|
|
ab430e4afb | ||
|
|
29f6ea6d48 | ||
|
|
e46372d08e | ||
|
|
1d2faeba3e | ||
|
|
23cffc0630 | ||
|
|
90b597fc70 | ||
|
|
5ea2a8d6b6 | ||
|
|
6e64f7c6df | ||
|
|
8a210f39b6 | ||
|
|
2aacd141ad | ||
|
|
5c01e6a99a | ||
|
|
c6cd5fcf93 | ||
|
|
177b96b453 | ||
|
|
292f66a774 | ||
|
|
7ccd9134c4 | ||
|
|
54d82e1773 | ||
|
|
9483962e7b | ||
|
|
f94c5bc542 | ||
|
|
ce985f4b44 | ||
|
|
032f2253a7 | ||
|
|
4b4eb66ef0 | ||
|
|
8c837f9f64 | ||
|
|
a71914d566 | ||
|
|
bd00183f7a | ||
|
|
2674e526be | ||
|
|
4ea2c08cc3 | ||
|
|
99432ad7ac | ||
|
|
8c210bff34 | ||
|
|
e566a94380 | ||
|
|
85f97ae552 | ||
|
|
7ca0d10eb0 | ||
|
|
07da9fd296 | ||
|
|
436dbd217c | ||
|
|
0937381a3f | ||
|
|
d42f6b6fff | ||
|
|
03940b291b | ||
|
|
2a72b03afc | ||
|
|
98caeb5410 | ||
|
|
73cb56a661 | ||
|
|
88932081b7 | ||
|
|
ca8aed1f2b | ||
|
|
a5f27d0d35 | ||
|
|
ce7ff0d14f | ||
|
|
d08571e862 | ||
|
|
b32d63fcda | ||
|
|
54d9d5e2f3 | ||
|
|
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
+3
-3
@@ -1,7 +1,5 @@
|
||||
nohup.out
|
||||
build/
|
||||
build
|
||||
cme_data/
|
||||
cmake-build-debug/
|
||||
.idea/
|
||||
.dir-locals.el
|
||||
@@ -36,5 +34,7 @@ trace_data/
|
||||
/doc/user/public/*
|
||||
.ionide/*
|
||||
/.ignore
|
||||
*.rej
|
||||
/build
|
||||
/analysis_data
|
||||
.clangd/
|
||||
*CMakeDeps.cmake
|
||||
Vendored
+8
-3
@@ -5,7 +5,9 @@
|
||||
"includePath": [
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceRoot}"
|
||||
"${workspaceRoot}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceRoot}/externals/linux/include"
|
||||
],
|
||||
"defines": [],
|
||||
"intelliSenseMode": "clang-x64",
|
||||
@@ -13,13 +15,16 @@
|
||||
"path": [
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceRoot}"
|
||||
"${workspaceRoot}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceRoot}/externals/linux/include"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": ""
|
||||
},
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17"
|
||||
"cppStandard": "c++17",
|
||||
"compileCommands": "${workspaceFolder}/build/ninja_debug/compile_commands.json"
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
|
||||
Vendored
+37
-131
@@ -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/",
|
||||
@@ -46,7 +74,7 @@
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "(gdb) Launch",
|
||||
"type": "cppdbg",
|
||||
@@ -98,46 +126,6 @@
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "11/20/2019",
|
||||
"--rend", "11/29/2019",
|
||||
"--rsymbol", "NG",
|
||||
"--rsecuritytype", "Synthetic",
|
||||
"--post_process_db", "NG_201911_W4",
|
||||
"--post_process_db_host", "bigblue",
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd simulation",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "11/26/2019",
|
||||
"--rend", "11/26/2019",
|
||||
"--rsymbol", "ZN",
|
||||
"--rsecuritytype", "Synthetic",
|
||||
"--algo_security", "ZNH0;ZNZ9-ZNH0;ZNZ9",
|
||||
"--algo_type", "RoundTrip",
|
||||
"--algo_params", "size=10;tick_size=0.015625,0.0078125,0.015625;hedge_factors=1,1,-1",
|
||||
"--skip_preanalysis", "true",
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Excel Launch",
|
||||
"type": "clr",
|
||||
@@ -171,18 +159,6 @@
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": ".NET Framework Attach",
|
||||
"type": "clr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
@@ -394,19 +370,15 @@
|
||||
"justMyCode": true
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd vsdbg (c#)",
|
||||
"name": ".NET Core Remote Launch",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "sparkcmd",
|
||||
"program": "/usr/bin/dotnet",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "11/20/2019",
|
||||
"--rend", "11/20/2019",
|
||||
"--rsymbol", "CL",
|
||||
"--rsecuritytype", "Spread",
|
||||
"--post_process_db", "mbo_history",
|
||||
"sparkcmd.dll",
|
||||
"test",
|
||||
"test_join_create_and_takeout_market"
|
||||
],
|
||||
"env": {"LD_LIBRARY_PATH" : "."},
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server",
|
||||
"pipeTransport": {
|
||||
@@ -417,77 +389,11 @@
|
||||
"debuggerPath": "/home/bts/vsdbg/vsdbg",
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"quoteArgs": true
|
||||
},
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
},
|
||||
"justMyCode": false
|
||||
},
|
||||
{
|
||||
"name": "sparkcmd gdb (c++)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkcmd",
|
||||
"args": [
|
||||
"historical",
|
||||
"--rstart", "10/21/2019",
|
||||
"--rend", "10/21/2019",
|
||||
"--rsymbol", "CL",
|
||||
"--rsecuritytype", "Future",
|
||||
"--algo_security", "CLX9",
|
||||
"--algo_type", "TWAP",
|
||||
"--algo_params", "'size=100;profit_ticks=1;tick_size=1,1,1;;hedge_factors=1,1,-1'",
|
||||
"--skip_preanalysis", "true",
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Skip STL",
|
||||
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "LD LIBRARY",
|
||||
"text": "set env LD_LIBRARY_PATH=.",
|
||||
"ignoreFailures": false
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Jupyter notebook",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "jupyter",
|
||||
"args": ["notebook", "--notebook-dir=c:\\code\\bts\\spark\\src\\client\\spark_historical\\Notebooks"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
"console": "internalConsole",
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
]
|
||||
}
|
||||
Vendored
+19
-10
@@ -14,24 +14,21 @@
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "server_build",
|
||||
"label": "server_build",
|
||||
"type": "shell",
|
||||
"command": ".\\tools\\buildsparkd.cmd",
|
||||
"problemMatcher": {
|
||||
"base": "$gcc",
|
||||
"problemMatcher": {
|
||||
"base": "$gcc",
|
||||
"owner": "cpp",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${workspaceFolder}"
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"pattern": {
|
||||
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
||||
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
||||
"file": 8,
|
||||
"line": 9,
|
||||
"column": 10,
|
||||
@@ -49,6 +46,18 @@
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "linux build (Debug)",
|
||||
"type": "shell",
|
||||
"command": "cd build/ninja_debug; time ninja",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+3
-12
@@ -41,6 +41,7 @@ else ()
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
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)
|
||||
@@ -48,7 +49,6 @@ else ()
|
||||
add_subdirectory(doc/user)
|
||||
add_subdirectory(src/sdk)
|
||||
add_subdirectory(src/data_files)
|
||||
add_subdirectory(src/client)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||
@@ -56,20 +56,11 @@ 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 spark_client_dir)
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
add_custom_command(TARGET server_layout POST_BUILD
|
||||
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
|
||||
else()
|
||||
add_custom_command(TARGET server_layout POST_BUILD
|
||||
COMMAND ln -fs ${CMAKE_BINARY_DIR}/../${CMAKE_BUILD_TYPE}/bin/netcoreapp3.0/linux-x64/publish/sparkcmd ${CMAKE_BINARY_DIR}/server/ )
|
||||
endif()
|
||||
|
||||
endif()
|
||||
@@ -1,6 +1,6 @@
|
||||
+++
|
||||
title="Client API"
|
||||
template="client.html"
|
||||
redirect_to = "client/getting-started/overview/"
|
||||
sort_by="weight"
|
||||
+++
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Getting Started"
|
||||
template="client.html"
|
||||
sort_by="weight"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
+++
|
||||
title = "Install Prerequisites"
|
||||
template="client.html"
|
||||
weight = 1
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
## Coming Soon
|
||||
# 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://www.nuget.org/packages/spark_sdk/) to get started.
|
||||
|
||||
```
|
||||
dotnet new console -lang c#
|
||||
dotnet add package spark_sdk
|
||||
dotnet build
|
||||
```
|
||||
|
||||
# C++
|
||||
|
||||
The easiest way to get started with C++ is to use the Spark Server SDK to develop your application. Follow the instructions to [download and install the Spark Server SDK](/server/getting-started/install) and open the client_sdk sample project. If you want to run your C++ Client SDK application on Windows [contact us](mailto://api@bluetradesys.com) to get the required headers and binaries.
|
||||
|
||||
# JavaScript
|
||||
|
||||
We use JavaScript internally with the Spark Client API over a websocket connection but the documentation and packaging isn't ready yet. If this is something you're interested in get in touch with us at [api@bluetradesys.com](mailto:api@bluetradesys.com).
|
||||
|
||||
|
||||
|
||||
* Install .net core
|
||||
@@ -0,0 +1,31 @@
|
||||
+++
|
||||
title = "Overview"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
The Spark Client SDK is a high level command and control API which can be used to build scripts or integrate Spark into your existing ecosystem. The API is divided into several independent **"services"** responsible for different aspects of the system. A Service can define both [remote procedure call (RPC)](/client/getting-started/services#rpc) and incremental [streaming](/client/getting-started/services#streams) data.
|
||||
|
||||
The [Spark Desktop](https://www.bluetradingsystems.com/bts-spark/) is built on top of this API so you can be assured that it is complete and well tested.
|
||||
|
||||
Spark uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) as an interface definition language similar to [GRPC](https://grpc.io) but backed by our custom high-performance TCP protocol. We currently support C#/.NET and C++ <!--and Javascript--> but the architecture is designed for easy extension so contact us about your specific business needs.
|
||||
|
||||
## Orders
|
||||
|
||||
Use the [OrderService](/client/services/orderservice) to send and manage orders including native exchange orders, spreader orders, and custom Algo orders created using the [Spark Server SDK](https://api.bluetradesys.com/server/).
|
||||
|
||||
## Spreader
|
||||
|
||||
Use the [SyntheticSecurityService](/client/services/syntheticsecurityservice) to create and manage synthetic spreads. Once created these spreads show up in the security and market data feeds just like exchange listed spreads.
|
||||
|
||||
## Position
|
||||
|
||||
Use the [PositionService](/client/services/positionservice) to get your position and P&L reports or add fills manually to keep the Spark system up to date with an external system.
|
||||
|
||||
## Administration
|
||||
|
||||
Use the [AdminService](/client/services/adminservice) to manage users and control limits.
|
||||
|
||||
## Market Data
|
||||
|
||||
For .NET subscribe to market data though the high level MarketDataClient interface. From C++ or JavaScript use the MarketDataRelay service directly.
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
+++
|
||||
title = "Services"
|
||||
weight = 20
|
||||
+++
|
||||
|
||||
The Spark Client SDK uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) to define the interface for each service in a language independent manner. Services have both functions which return a single result and streams of data which return values continuously while subscribed. This page describes the mapping from the Protocol Buffer definition to the concrete definition in each supported language.
|
||||
|
||||
# ServiceContext
|
||||
|
||||
To connect your client to the Spark Server you need to get or create a `ServiceContext` instance. A `ServiceContext` represents a connection to the Spark Server and a background thread for handling messages on that connection. Generally there should be a single `ServiceContext` per process. All of the methods on the `ServiceContext` are safe thread safe.
|
||||
|
||||
Construct a ServiceContext by calling the default constructor. The ServiceContext will find the Spark Server by loading the server configuration over an HTTP connection. To determine the URL it will search the following locations and use the first one that is present.
|
||||
|
||||
* A command line argument `-configserver <url>`
|
||||
* An environment variable `BTSCONFIG`
|
||||
* `%APPDATA%/bts/conf/spark.conf` (Windows)
|
||||
* /sparkdata/conf/spark.conf (Linux)
|
||||
* $HOME/sparkdata/conf/spark.conf (Linux)
|
||||
|
||||
If your application is running on a system with the BTS Client installed this will probably work without any further configuration. If you plan to run your client application on a separate machine we recommend setting the `BTSCONFIG` environment variable.
|
||||
|
||||
<div class="info">
|
||||
<i class="fa fa-lightbulb-o fa-fw" aria-hidden="true"></i>
|
||||
If you are using the Spark Server SDK to run a local copy of the Spark server set <p><strong>BTSCONFIG = http://localhost:9000/config/</strong></p>
|
||||
</div>
|
||||
|
||||
To get a proxy for a given service call ServiceContext.locate<T>("qualifier") where T is the type of the service and "qualifier" is an optional qualifier used to disambiguate between 2 instances of the same service. The locate call cannot fail but it may return a disconnected proxy. When you make a call on the proxy you should check the call result to see if the method call succeeded.
|
||||
|
||||
# RPC
|
||||
|
||||
Here is the OrderService reduced to a single method definition. A RPC method always takes exactly 1 argument and returns 1 value. In this example the `create_order` call takes a `Create` instance and returns a `Response`.
|
||||
|
||||
```C
|
||||
service OrderService {
|
||||
rpc create_order(Create) returns (Response);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The `Create` and `Response` types are also defined as protocol buffer messages. Each field can either be a primitive type another message or a list or map.
|
||||
|
||||
```C
|
||||
message Create {
|
||||
OrderDefinition new_order = 1;
|
||||
double price = 2;
|
||||
uint32 quantity = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
uint32 order_key = 1;
|
||||
OrderError error_code = 2;
|
||||
string msg = 3;
|
||||
}
|
||||
```
|
||||
|
||||
Each RPC call generates both synchronous (blocking) and asynchronous variants. In C# the asynchronous calls have the `_async` suffix and return a `Task`. In C++ the call is overloaded to either return a value synchronously or accept a callback function to be called on the ServiceContext thread when the call completes. <!-- In Javascript only async methods returning a Promise are generated because it never makes sense to make a blocking call in the browser. -->
|
||||
|
||||
To call the resulting service locate a proxy using the [ServiceContext](#servicecontext) and then call `cancel_order` or `cancel_order_async`. If you are calling the Spark API from a GUI application you should always prefer the `_async` version of the calls.
|
||||
|
||||
|
||||
### C# example
|
||||
```c#
|
||||
var ctx = new ServiceContext();
|
||||
var orders = ctx.locate<OrderService>();
|
||||
var response =
|
||||
await orders.create_order_async(
|
||||
new Create { price = 2.25, quantity = 42 });
|
||||
if (response.success) {
|
||||
Console.WriteLine($"success: {response.value}");
|
||||
}
|
||||
else {
|
||||
Console.WriteLine($"Error: {response.error_message}");
|
||||
}
|
||||
```
|
||||
### C++ example
|
||||
```cpp
|
||||
auto ctx = ServiceContext{};
|
||||
auto orders = ctx.locate<OrderService>();
|
||||
Create c;
|
||||
c.set_price(2.25);
|
||||
c.set_quantity(42);
|
||||
// Instead of providing an _async method C++ uses overloading.
|
||||
// To make a syncronous call simply omit the callback
|
||||
orders.create_order(c, [=](const Result<Response>& response) {
|
||||
if(response.success()) {
|
||||
std::cout << "success.";
|
||||
}
|
||||
else {
|
||||
std::cout << "error: " << response.error_message();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
# Streams
|
||||
|
||||
Streams allow you to subscribe to live updating data. Each stream represents a changing collection of protocol buffer objects. Similar to a database row each item has an `id` which uniquely identifies it within the collection. Updates from the Spark server can add, remove or replace entries in the collection. Since only the changed items are sent over the wire the stream can remain responsive even when it represents a large collection.
|
||||
|
||||
Streams are implemented using the language bindings for [ReativeX](http://reactivex.io/) which provide utility functions to drill down to the specific data your application needs. In C# we provide both mutable and immutable versions of the generated data types (C++ users can rely on `const`).
|
||||
|
||||
When defined in a proto file streams use the `stream` keyword on the result type.
|
||||
```C
|
||||
service OrderService {
|
||||
...
|
||||
rpc fills (FillsSub) returns (stream Fill);
|
||||
}
|
||||
```
|
||||
|
||||
### C# example
|
||||
```c#
|
||||
var sc = new ServiceContext();
|
||||
var orders = sc.locate<OrderService>();
|
||||
var fills_observable = orders.subscribei_fills();
|
||||
var unsub = fills_observable.Subscribe(update => {
|
||||
Console.WriteLine(
|
||||
"Added {0} items, Removed {1} items, Updated {2} items. Total items {3}",
|
||||
update.added.Count(),
|
||||
update.removed.Count(),
|
||||
update.changes.Count(),
|
||||
update.all.Count());
|
||||
});
|
||||
|
||||
Thread.Sleep(TimeSpan.FromMinutes(5));
|
||||
unsub.Dispose();
|
||||
```
|
||||
### 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));
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title="Samples"
|
||||
sort_by="weight"
|
||||
weight = 30
|
||||
+++
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "AdminService"
|
||||
+++
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "OrderService"
|
||||
+++
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "PositionService"
|
||||
+++
|
||||
@@ -0,0 +1,3 @@
|
||||
+++
|
||||
title = "SyntheticService"
|
||||
+++
|
||||
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title="Services"
|
||||
#sort_by="weight"
|
||||
weight = 10
|
||||
+++
|
||||
|
||||
@@ -0,0 +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;
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Algo API"
|
||||
template="documentation.html"
|
||||
redirect_to = "server/getting-started/overview/"
|
||||
sort_by="weight"
|
||||
+++
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Getting Started"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
@@ -71,12 +71,12 @@ The first step is to start the spark_sdk Docker container with the examples dire
|
||||
|
||||
Windows Command Prompt:
|
||||
{% clipable() %}
|
||||
docker run --rm -it -v %CD%:/examples -u bts -w /examples --shm-size 1024m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
docker run --rm -it -v %CD%:/examples -u bts -w /examples --shm-size 2048m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
**Powershell** / Linux / Mac:
|
||||
{% clipable() %}
|
||||
docker run --rm -it -v ${PWD}:/examples -u bts -w /examples --shm-size 1024m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
docker run --rm -it -v ${PWD}:/examples -u bts -w /examples --shm-size 2048m --name spark_sdk -h spark_sdk --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 9000:9000 -p 9001:9001 spark_sdk bash
|
||||
{% end %}
|
||||
|
||||
### Attach
|
||||
|
||||
@@ -5,7 +5,7 @@ weight = 2
|
||||
|
||||
## Request Access 🔑
|
||||
|
||||
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:info@bluetradesys.com">info@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
|
||||
To start using the Server SDK you will need to request access though our sales department. Please email <a href="mailto:api@bluetradesys.com">api@bluetradesys.com</a> or call <a href="tel:919-913-0850">(919) 913-0850</a> and we will provide download credentials.
|
||||
|
||||
## Download
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
``` -->
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Reference"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
@@ -6,3 +6,9 @@ weight = 40
|
||||
## I need to make a call that might take a few seconds. What is the best way to do this?
|
||||
|
||||
Create a background thread in [do_before_launch](/server/ref/initialization#before-launch) or [do_launch](/server/ref/initialization#launch-launch-attached) to do the work. Use [on_timer](/server/ref/subscriptions#timer) to poll until the result is available.
|
||||
|
||||
## I don't see the header files in the .zip file. Where are they?
|
||||
|
||||
The SDK headers are baked into the docker image along with the build tools. Once you've loaded the image with `docker load < spark_sdk.img` you can run this command to copy them out to your local file system `docker run --rm -it -v $PWD:/out spark_sdk /bin/tar -cvf /out/headers.tar /sdk/include` This will copy the headers to the current directory in headers.tar. We still recommend doing the actual build inside the docker environment but having a copy of the headers can be useful for setting up your development environment.
|
||||
|
||||
|
||||
File renamed without changes.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,6 +1,5 @@
|
||||
+++
|
||||
title="Samples"
|
||||
template="documentation.html"
|
||||
sort_by="weight"
|
||||
weight = 3
|
||||
+++
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block extra_nav_class %}{% endblock extra_nav_class %}
|
||||
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
|
||||
|
||||
{% block content %}
|
||||
{% set section = get_section(path="client/_index.md") %}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
{% block doc_content %}
|
||||
{% endblock doc_content %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1,36 +0,0 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block extra_nav_class %}{% endblock extra_nav_class %}
|
||||
{% block extra_content_class %}content--reversed{% endblock extra_content_class %}
|
||||
|
||||
{% block content %}
|
||||
{%if page.extra.section %}
|
||||
{% set section = get_section(path="{{page.extra.section}}/_index.md") %}
|
||||
{% else %}
|
||||
{% set section = get_section(path="server/_index.md") %}
|
||||
{% endif %}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
{% block doc_content %}
|
||||
{% endblock doc_content %}
|
||||
{% block next_link %} {% endblock next_link %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-gb">
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
@@ -14,6 +14,7 @@
|
||||
<link rel="stylesheet" href="{{ get_url(path="site.css") }}"/>
|
||||
<link rel="icon" href="{{ get_url(path="favicon.ico") }}">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script> dataLayer = [{ 'visitorType': 'geek' }]; </script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -36,13 +37,13 @@
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="content {% block extra_content_class %}{% endblock extra_content_class %}">
|
||||
<div class="content content--reversed">
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<h1>Spark SDK</h1>
|
||||
<p class="hero__tagline">
|
||||
Spark consists of a client UI
|
||||
(<a class="white" href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
|
||||
(<a href="https://www.bluetradingsystems.com/bts-spark#ladder-algo-screen">Spark Client</a>),
|
||||
a high-performance professional futures front end, connected via our proprietary protocol over TCP to our Spark Server, which in turn connects to execution venues via their native protocols.
|
||||
Use the Spark SDK to extend Spark Server with your custom Algos, then use the Client SDK to launch and control them.
|
||||
</p>
|
||||
@@ -64,7 +65,7 @@
|
||||
<a href="{{ get_url(path="@/server/_index.md") }}" class="button">Get started</a>
|
||||
</div>
|
||||
|
||||
<div class="selling-point disabled">
|
||||
<div class="selling-point">
|
||||
<h2>
|
||||
Client SDK
|
||||
<i class="fa fa-gamepad" aria-hidden="true"></i>
|
||||
@@ -72,19 +73,9 @@
|
||||
<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" disabled>Get started</a>
|
||||
<a href="{{ get_url(path="@/client/_index.md") }}" class="button">Get started</a>
|
||||
</div>
|
||||
|
||||
<!-- <div class="selling-point disabled">
|
||||
<h2>
|
||||
Microsoft Excel
|
||||
<i class="fa fa-table" aria-hidden="true"></i>
|
||||
</h2>
|
||||
<p>
|
||||
More of your business is built on excel than probably should be. Continue this worrying trend with the Spark Excel API.
|
||||
</p>
|
||||
<a href="" class="button" disabled>Get started</a>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +85,15 @@
|
||||
© Blue Trading Systems
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
if (location.hostname == "api.bluetradesys.com") {
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-56FHG8R');
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="{{ get_url(path="elasticlunr.min.js") }}"></script>
|
||||
<script type="text/javascript" src="{{ get_url(path="search_index.en.js") }}"></script>
|
||||
<script type="text/javascript" src="{{ get_url(path="search.js") }}"></script>
|
||||
|
||||
@@ -1,13 +1,39 @@
|
||||
{% extends "documentation.html" %}
|
||||
{% extends "index.html" %}
|
||||
{# this has access to the "page" variable but documentation.html does not #}
|
||||
|
||||
{% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
|
||||
{% block doc_content %}
|
||||
<h1>{{page.title}}</h1>
|
||||
{{page.content | safe}}
|
||||
{% endblock doc_content %}
|
||||
|
||||
{% block next_link %}
|
||||
<p>
|
||||
{% endblock next_link %}
|
||||
|
||||
{% block content %}
|
||||
{% set section = get_section(path=page.ancestors[1]) %}
|
||||
{#
|
||||
{#<div>{{ section.title }} </div>#}
|
||||
<div class="documentation">
|
||||
<aside class="documentation__sidebar">
|
||||
<ul>
|
||||
{% for p in section.subsections %}
|
||||
{% set subsection = get_section(path=p) %}
|
||||
<li>
|
||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span>
|
||||
<ul>
|
||||
{% for page in subsection.pages %}
|
||||
<li class="{% if current_path == page.path %}active{% endif %}">
|
||||
<a href="{{page.permalink}}">{{page.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="documentation__content">
|
||||
<h1>{{page.title}}</h1>
|
||||
{{page.content | safe}}
|
||||
|
||||
{# next/pre links #}
|
||||
<p>
|
||||
{% if page.lighter %}
|
||||
<a href="{{ page.lighter.permalink }}">
|
||||
<i class="fa fa-chevron-left fa-fw" aria-hidden="true"></i>
|
||||
@@ -21,5 +47,7 @@
|
||||
<i class="fa fa-chevron-right fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endblock next_link %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_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/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)
|
||||
@@ -1,9 +1,60 @@
|
||||
+++
|
||||
+++
|
||||
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
|
||||
## 2.15.2
|
||||
### User Visible Changes
|
||||
- Excel RTD plugin can now receive ICE market data
|
||||
## 2.15.1
|
||||
### Bug Fixes
|
||||
- Fixed a FIX log parsing issue that resulted in exchange gateway logon problems
|
||||
## 2.15.0
|
||||
### User Visible Changes
|
||||
- Added ability to hide Ladder status messages via Ladder Settings -> Left Panel tab -> Show/Hide groupbox -> Status Message
|
||||
- Synthetic cloning now allows for any parameter to be changed, including tick size and leg contracts
|
||||
- Added ability to import synthetics from a CSV file; see our manual for further details on this process
|
||||
- Reduced Iceberg reloading latency
|
||||
### Bug Fixes
|
||||
- Fixed a bug where highlighted fills could lose their highlight status
|
||||
- Fixed a bug where the (Mini) OrderBook PIQ could be displaying an old value
|
||||
|
||||
## 2.14.3
|
||||
### Bug Fixes
|
||||
- Fixed a bug regarding ICE logon messaging
|
||||
|
||||
## 2.14.2
|
||||
### Bug Fixes
|
||||
- Fixed a bug regarding ICE messaging
|
||||
|
||||
## 2.14.1
|
||||
### Bug Fixes
|
||||
- Fixed a bug regarding ICE logon authentication
|
||||
|
||||
## 2.14.0
|
||||
### User Visible Changes
|
||||
- All ladders can now be recentered by (shift or alt) + double clicking over the Price column in a ladder
|
||||
@@ -677,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: b09d4c035b...7258a028d7.
+1
-1
Submodule externals updated: 6b8c745fb2...2ea5e355a6.
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "2.1.508",
|
||||
"rollForward": "latestMajor"
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\sp
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_fix", "src\client\spark_fix\spark_fix.csproj", "{D8031C13-7BEA-430A-BB60-40927D15CFC3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_historical", "src\client\spark_historical\spark_historical.csproj", "{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -226,50 +219,6 @@ Global
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x64.Build.0 = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.Release|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{D8031C13-7BEA-430A-BB60-40927D15CFC3}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.MinSizeRel|x86.Build.0 = Debug|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{5F64A360-43F4-4AD8-8B04-3C3F4424BCBC}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
drop table if exists msgs ;
|
||||
create table msgs (
|
||||
MsgID Int4,
|
||||
TradeDate varchar,
|
||||
SeqNum Int4,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
MatchEventIndicator varchar,
|
||||
NoMDEntries Int2,
|
||||
NoOrderIDEntries Int2,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
drop table if exists books ;
|
||||
create table books (
|
||||
MsgID Int4,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
SeqNum Int4,
|
||||
bid_p_0 Real,
|
||||
bid_sz_0 Int4,
|
||||
bid_p_1 Real,
|
||||
bid_sz_1 Int4,
|
||||
bid_p_2 Real,
|
||||
bid_sz_2 Int4,
|
||||
bid_p_3 Real,
|
||||
bid_sz_3 Int4,
|
||||
bid_p_4 Real,
|
||||
bid_sz_4 Int4,
|
||||
bid_p_5 Real,
|
||||
bid_sz_5 Int4,
|
||||
bid_p_6 Real,
|
||||
bid_sz_6 Int4,
|
||||
bid_p_7 Real,
|
||||
bid_sz_7 Int4,
|
||||
bid_p_8 Real,
|
||||
bid_sz_8 Int4,
|
||||
bid_p_9 Real,
|
||||
bid_sz_9 Int4,
|
||||
Symbol Varchar,
|
||||
ask_p_0 Real,
|
||||
ask_sz_0 Int4,
|
||||
ask_p_1 Real,
|
||||
ask_sz_1 Int4,
|
||||
ask_p_2 Real,
|
||||
ask_sz_2 Int4,
|
||||
ask_p_3 Real,
|
||||
ask_sz_3 Int4,
|
||||
ask_p_4 Real,
|
||||
ask_sz_4 Int4,
|
||||
ask_p_5 Real,
|
||||
ask_sz_5 Int4,
|
||||
ask_p_6 Real,
|
||||
ask_sz_6 Int4,
|
||||
ask_p_7 Real,
|
||||
ask_sz_7 Int4,
|
||||
ask_p_8 Real,
|
||||
ask_sz_8 Int4,
|
||||
ask_p_9 Real,
|
||||
ask_sz_9 Int4,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
drop table if exists trades;
|
||||
create table trades (
|
||||
MsgID Int4,
|
||||
Symbol varchar,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
TradeDate varchar,
|
||||
MDEntryPx Real,
|
||||
MDnetrySize Int4,
|
||||
NumberOfOrders Int4,
|
||||
AgressorSide Int4,
|
||||
MDTradeEntryID Int4,
|
||||
AgressorOID varchar,
|
||||
RestingOID varchar,
|
||||
PartialQty Int4,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
drop table if exists orders;
|
||||
create table orders (
|
||||
MsgID Int4,
|
||||
Symbol varchar,
|
||||
SendingTime varchar,
|
||||
TransactTime varchar,
|
||||
MDEntryPx Real,
|
||||
OrderID varchar,
|
||||
MDDisplayQty Int4,
|
||||
MDOrderPriority Int8,
|
||||
OrderUpdateAction Int4,
|
||||
Side Int4,
|
||||
SendingTime_UnixTS Int8,
|
||||
TransactTime_UnixTS Int8
|
||||
);
|
||||
|
||||
-- create unique index msg_msg_id on msgs msgid;
|
||||
|
||||
-- create view ranges as (
|
||||
-- select
|
||||
-- sendingtime,
|
||||
-- transacttime,
|
||||
-- bid_p_0 price,
|
||||
-- case when lag(bid_p_0) over (order by SendingTime) = bid_p_0 then null else 1 end seq
|
||||
-- from
|
||||
-- u0v0_books);
|
||||
|
||||
-- create view groups as (
|
||||
-- select
|
||||
-- sendingtime,
|
||||
-- transacttime,
|
||||
-- price,
|
||||
-- sum(seq) over (order by SendingTime) grp
|
||||
-- from
|
||||
-- ranges);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
DB=$1
|
||||
TABLE=$2
|
||||
FILE=$3
|
||||
|
||||
zcat $FILE | psql -U postgres -d $DB -c "copy $TABLE from STDIN with delimiter as ',' CSV HEADER"
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
docker exec -it postgres psql -U postgres "$@"
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#docker run --net host -e PGADMIN_DEFAULT_EMAIL=bts -e PGADMIN_DEFAULT_PASSWORD=BT5u53r dpage/pgadmin4
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-d \
|
||||
-p 8000:80 \
|
||||
-e PGADMIN_DEFAULT_EMAIL=bts \
|
||||
-e PGADMIN_DEFAULT_PASSWORD=BT5u53r \
|
||||
--name pgadmin \
|
||||
dpage/pgadmin4
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SRC_ROOT=$DIR/../../..
|
||||
DATA_DIR=`readlink -f $SRC_ROOT/analysis_data`
|
||||
DB_DIR=$DATA_DIR/db
|
||||
|
||||
mkdir -p $DB_DIR
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-d \
|
||||
--name postgres \
|
||||
--net host \
|
||||
-v $DB_DIR:/var/lib/postgresql/data \
|
||||
-v /home/$USER:/home/$USER \
|
||||
-v $DATA_DIR:$DATA_DIR \
|
||||
-v /etc/group:/etc/group \
|
||||
-v /run/user/:/run/user/ \
|
||||
-u $UID \
|
||||
-t postgres \
|
||||
postgres
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SRC_ROOT=$DIR/../../
|
||||
DATE=$1
|
||||
DB=md_$DATE
|
||||
|
||||
$DIR/db/psql.sh -c "drop database if exists $DB"
|
||||
$DIR/db/psql.sh -c "create database $DB"
|
||||
$DIR/db/psql.sh -d md_$DATE -f $DIR/db/create_db.sql
|
||||
|
||||
PROCESSED_DATA=$SRC_ROOT/analysis_data/processed/$DATE
|
||||
|
||||
ALL_COMMANDS=""
|
||||
for TABLE in trades orders msgs books
|
||||
do
|
||||
for ITEM in $PROCESSED_DATA/*.$TABLE.gz
|
||||
do
|
||||
ALL_COMMANDS+="docker exec -t postgres /bin/bash -c \""
|
||||
ALL_COMMANDS+="$DIR/db/import_csv.sh $DB $TABLE $ITEM"
|
||||
ALL_COMMANDS+="\"\n"
|
||||
done
|
||||
done
|
||||
|
||||
echo -e $ALL_COMMANDS | parallel --will-cite
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DATE=$1
|
||||
|
||||
SRC_ROOT=$DIR/../../
|
||||
SPARK_BIN_ROOT=`readlink -f $SRC_ROOT/build`/ninja_release/server
|
||||
RAW_DATA=$SRC_ROOT/analysis_data/raw/$DATE
|
||||
PROCESSED_DATA=$SRC_ROOT/analysis_data/processed/$DATE
|
||||
|
||||
mkdir -p $PROCESSED_DATA
|
||||
rm $PROCESSED_DATA/* >/dev/null
|
||||
|
||||
ALL_COMMANDS=""
|
||||
for IN_PATH in $RAW_DATA/*.gz
|
||||
do
|
||||
BASE_NAME=`basename $IN_PATH`
|
||||
BASE_NAME_STEM=${BASE_NAME%.*}
|
||||
for CMD in trades orders msgs books
|
||||
do
|
||||
OUT_PATH=$PROCESSED_DATA/$BASE_NAME_STEM.$CMD.gz
|
||||
ALL_COMMANDS+="(cd $SPARK_BIN_ROOT && zcat $IN_PATH | ./sparkd parsehistoricalfix $CMD | gzip >$OUT_PATH)\n"
|
||||
done
|
||||
done
|
||||
echo -e $ALL_COMMANDS | parallel --will-cite
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
d=$1
|
||||
end=${2-`date +%Y%m%d`}
|
||||
until [[ $d > $end ]]; do
|
||||
echo "$d"
|
||||
d=$(date -d "$d + 1 day" +%Y%m%d)
|
||||
done
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DATE=${1-`date -d "yesterday" +%Y%m%d`}
|
||||
|
||||
$DIR/sync.sh $DATE &&
|
||||
$DIR/parse.sh $DATE &&
|
||||
$DIR/load.sh $DATE
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DATE=$1
|
||||
SRC_ROOT=$DIR/../../
|
||||
RAW_DATA_ROOT=$SRC_ROOT/analysis_data/raw/$DATE
|
||||
mkdir -p $RAW_DATA_ROOT
|
||||
AWS_ACCESS_KEY_ID=AKIAIFIGOOWGBLEMO3PQ AWS_SECRET_ACCESS_KEY=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 AWS_REGION=us-east-1 aws s3 sync s3://cme-bts-datamine/mbo/mbo/$DATE/ $RAW_DATA_ROOT
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(spark_client)
|
||||
include(${CMAKE_SOURCE_DIR}/tools/safe_dep_glob.cmake)
|
||||
|
||||
file(GLOB_RECURSE spark_client_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cs)
|
||||
file(GLOB_RECURSE spark_client_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.csproj)
|
||||
file(GLOB_RECURSE spark_ui_sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.cs)
|
||||
file(GLOB_RECURSE spark_ui_csproj RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} spark/*.csproj)
|
||||
list(REMOVE_ITEM spark_ui_sources ${spark_client_sources} )
|
||||
list(REMOVE_ITEM spark_ui_csproj ${spark_client_csproj} )
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/../
|
||||
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh
|
||||
ARGS ${CMAKE_BUILD_TYPE} ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Building sparkcmd in netcore3"
|
||||
DEPENDS libspark ${spark_client_sources} ${spark_client_csproj} ${CMAKE_CURRENT_SOURCE_DIR}/build_spark_dotnetcore.sh)
|
||||
|
||||
add_custom_target(spark_client_dir ALL DEPENDS ${CMAKE_BINARY_DIR}/../)
|
||||
@@ -5,7 +5,6 @@
|
||||
<SparkRoot>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)\..\..))</SparkRoot>
|
||||
<SourceRoot>$([System.IO.Path]::GetFullPath($(SparkRoot)\src))</SourceRoot>
|
||||
<Externals>$(SparkRoot)\externals\windows</Externals>
|
||||
<Externals Condition=" '$(OS)' != 'Windows_NT' ">$(SparkRoot)/externals/linux</Externals>
|
||||
<DepotName>$([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath($(SparkRoot)))))</DepotName>
|
||||
<ErrorReport>None</ErrorReport>
|
||||
<Prefer32Bit>False</Prefer32Bit>
|
||||
@@ -23,20 +22,21 @@
|
||||
<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>
|
||||
<AvailableItemName Include="Proto2">
|
||||
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto2</Targets>
|
||||
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto2Linux</Targets>
|
||||
<Targets>CompileProto3</Targets>
|
||||
</AvailableItemName>
|
||||
<AvailableItemName Include="Proto3">
|
||||
<Targets Condition=" '$(OS)' == 'Windows_NT' " >CompileProto3</Targets>
|
||||
<Targets Condition=" '$(OS)' != 'Windows_NT' " >CompileProto3Linux</Targets>
|
||||
<Targets>CompileProto3</Targets>
|
||||
</AvailableItemName>
|
||||
<AvailableItemName Include="Proto3Excel">
|
||||
<Targets Condition=" '$(OS)' == 'Windows_NT' ">CompileProto3Excel</Targets>
|
||||
<Targets>CompileProto3Excel</Targets>
|
||||
</AvailableItemName>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -47,14 +47,6 @@
|
||||
<protogen>$(Externals)\bin\protogen.exe</protogen>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="ProtoBuf" Condition=" '$(OS)' != 'Windows_NT' ">
|
||||
<ProtocPluginBase>$(SparkRoot)/src/lib/api/service_compiler</ProtocPluginBase>
|
||||
<ProtoInclude>$(Externals)/include</ProtoInclude>
|
||||
<protoc>$(Externals)/bin/protoc</protoc>
|
||||
<protoc_rename>$(Externals)/bin/lowercase_files.sh</protoc_rename>
|
||||
<protogen>$(Externals)/bin/protogen</protogen>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<Proto2>
|
||||
</Proto2>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
<Target Name="CompileProto2"
|
||||
Condition="'@(Proto2)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||
Condition="'@(Proto2)'!=''"
|
||||
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
||||
Outputs="$(GenDir)\proto2\%(Proto2.filename).cs"
|
||||
AfterTargets="PreBuildEvent">
|
||||
@@ -28,7 +28,7 @@
|
||||
passes wipes out the additional compile includes that we add.
|
||||
-->
|
||||
<Target Name="CompileProto3" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||
Condition="'@(Proto3)'!=''"
|
||||
Inputs="$(protoc);
|
||||
%(Proto3.Identity);$(ProtocPluginBase)\plugin.py;
|
||||
$(ProtocPluginBase)\templates\c#\client.pb.cs;
|
||||
@@ -58,7 +58,7 @@
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3Excel)'!='' AND '$(OS)' == 'Windows_NT' "
|
||||
Condition="'@(Proto3Excel)'!=''"
|
||||
Inputs="$(protoc);
|
||||
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
|
||||
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
|
||||
@@ -82,74 +82,12 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="CompileProto2Linux"
|
||||
Condition="'@(Proto2)'!='' AND '$(OS)' != 'Windows_NT' "
|
||||
Inputs="$(MSBuildProjectFile);$(protogen);%(Proto2.FullPath)"
|
||||
Outputs="$(GenDir)/proto2/%(Proto2.filename).cs"
|
||||
AfterTargets="PreBuildEvent">
|
||||
<PropertyGroup>
|
||||
<SourceRelativeFile>$([System.String]::Copy('%(proto2.fullpath)').Replace($(SourceRoot), ''))</SourceRelativeFile>
|
||||
<SourceRelativeDir>$([System.IO.Path]::GetDirectoryName($(SourceRelativeFile)))</SourceRelativeDir>
|
||||
<WorkingDirectory>$(GenDir)/proto2</WorkingDirectory>
|
||||
</PropertyGroup>
|
||||
<MakeDir Directories="$(WorkingDirectory)" />
|
||||
<Copy SourceFiles="%(Proto2.FullPath)" DestinationFolder="$(WorkingDirectory)" SkipUnchangedFiles="True" UseHardLinksIfPossible="True" />
|
||||
<Error Text="protogen.exe does not exist. If you forgot to clone with externals try: git submodule update --init --recursive" Condition="!Exists($(protogen))" />
|
||||
<Message Importance="High" Text="Doing Proto2 %(proto2.filename)" />
|
||||
<Exec Command="PYTHONPATH=$(Externals)/python $(Protogen) %(Proto2.filename)%(proto2.extension) --csharp_out=. -I. -I$(SourceRoot) -I$(Externals)/include"
|
||||
WorkingDirectory="$(WorkingDirectory)" />
|
||||
<ItemGroup>
|
||||
<Compile Include="$(WorkingDirectory)/%(Proto2.filename).cs" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileProto3Linux" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' "
|
||||
Inputs="$(protoc);
|
||||
%(Proto3.Identity);$(ProtocPluginBase)/plugin.py;
|
||||
$(ProtocPluginBase)/templates/c#/client.pb.cs;
|
||||
$(ProtocPluginBase)/templates/c#/server.pb.cs;
|
||||
$(ProtocPluginBase)/templates/c#/immutable.pb.cs;"
|
||||
Outputs="%(Proto3.OutCS);%(Proto3.OutCSServer);%(Proto3.OutCSClient);%(Proto3.OutCSImmutable)">
|
||||
<MakeDir Directories="%(Proto3.OutBase)" />
|
||||
<MakeDir Directories="$(IntermediateOutputPath)/gen" />
|
||||
|
||||
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
|
||||
<Exec Command="
|
||||
PYTHONPATH=$(Externals)/python $(protoc) \
|
||||
%(Proto3.FullPath) \
|
||||
--error_format=gcc \
|
||||
--proto_path=$(protoinc):$(SourceRoot) \
|
||||
--plugin='protoc-gen-service=$(ProtocPluginBase)/plugin.py' \
|
||||
--service_out=lang=c#,strip_out_path=1:%(Proto3.OutBase) \
|
||||
--csharp_out=%(CompileOptions)%(Proto3.OutBase)"
|
||||
WorkingDirectory="%(relativedir)" />
|
||||
|
||||
<!-- <Exec Command="$(protoc_rename) %(Proto3.OutBase)" /> -->
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="%(Proto3.OutCS)" />
|
||||
<Compile Include="%(Proto3.OutCSClient)" />
|
||||
<Compile Include="%(Proto3.OutCSServer)" />
|
||||
<Compile Include="%(Proto3.OutCSImmutable)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!-- <Target Name="RenameProto3Linux" AfterTargets="CompileProto3Linux"
|
||||
Condition="'@(Proto3)'!='' AND '$(OS)' != 'Windows_NT' ">
|
||||
<Move SourceFiles="$(OutBase)\Apicore.cs" DestinationFiles="$(OutBase)\apicore.cs" />
|
||||
<Move SourceFiles="$(OutBase)\Apioptions.cs" DestinationFiles="$(OutBase)\apioptions.cs" />
|
||||
<Move SourceFiles="$(OutBase)\Co2.cs" DestinationFiles="$(OutBase)\co2.cs" />
|
||||
</Target> -->
|
||||
|
||||
|
||||
<Target Name="AfterClean" AfterTargets="Clean">
|
||||
<Delete Files="%(Proto3.OutCS)" />
|
||||
<Delete Files="%(Proto3.OutCSClient)" />
|
||||
<Delete Files="%(Proto3.OutCSServer)" />
|
||||
<Delete Files="%(Proto3.OutCSImmutable)" />
|
||||
<Delete Files="$(GenDir)/proto2/*" />
|
||||
<Delete Files="$(GenDir)\proto2\*" />
|
||||
<Delete Files="%(Proto3Excel.OutCSExcel)" />
|
||||
</Target>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#$1 = Debug / Release (CMAKE_BUILD_TYPE), $2 = CMAKE_BINARY_DIR
|
||||
cd "$(dirname "$0")"
|
||||
echo $PWD dotnet build -c $1 ./sparkcmd/
|
||||
dotnet build -c $1 ./sparkcmd/
|
||||
|
||||
if [ "$1" == "Release" ]
|
||||
then
|
||||
dotnet publish -c $1 ./sparkcmd/
|
||||
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64/publish
|
||||
fi
|
||||
|
||||
ln -fs $2/src/lib/libspark.so $2/../$1/bin/netcoreapp3.0/linux-x64
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -4,43 +4,112 @@ using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts {
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class DeleteSyntheticDaemon : IShellDaemon, IDisposable {
|
||||
// request_id is noted in the series of log messages that occur during a particular delete op.
|
||||
static int _request_id = 1;
|
||||
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
IReadOnlyDictionary<uint, ExecutionStrategyImmutable> _strategy_map =
|
||||
DictionaryHelper.empty<uint, ExecutionStrategyImmutable>();
|
||||
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model, ErrorReporter error_reporter) {
|
||||
_orders_model = orders_model;
|
||||
_error_reporter = error_reporter;
|
||||
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(_delete_synthetic);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SyntheticDeleteTag).Subscribe(_delete_synthetic),
|
||||
orders_model.spreader.strategy.Subscribe(map => _strategy_map = map));
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async void _delete_synthetic(uint id) {
|
||||
var (orders, orders_success) = await _orders_model.on_orders.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
async void _delete_synthetic(IImmutableSet<uint> keys) {
|
||||
var log = new LoggerContext(Log.Ui, $"Synthetic Delete Req {unchecked(Interlocked.Increment(ref _request_id))} - ", null);
|
||||
if (keys.is_null_or_empty()) {
|
||||
log.warn("received empty delete request (no securities specified).");
|
||||
return;
|
||||
}
|
||||
log.info($"received request to delete the following {keys.Count} securities: {string.Join(", ", keys)}");
|
||||
var (orders, orders_success) = await _orders_model.security_order_groups.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
if (!orders_success) {
|
||||
_error_reporter.report_error($"Unable to delete synthetic security ({id}): unable to determine whether or not security ({id}) has working orders (deletion is not permitted while there are working orders).");
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because the orders lookup timed out (must confirm no working orders).");
|
||||
} else if (orders.state.Values.Any(o => o.order.Def.SecurityKey == id && o.order.is_non_terminal())) {
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because non-terminal-orders were detected.");
|
||||
var msg = "Unable to delete synthetic securities: " +
|
||||
$"timed out waiting for orders table (must verify that a synthetic security does not have any working orders before it can be deleted).";
|
||||
_error_reporter.report_error(msg);
|
||||
log.warn(msg);
|
||||
} else {
|
||||
ExecutionStrategyImmutable? strategy = await _orders_model.spreader.lookup_strategy_async(id);
|
||||
if (strategy.HasValue) {
|
||||
ExecutionStrategyImmutable deleted_strategy = strategy.Value.with(deleted: true);
|
||||
await _orders_model.spreader.save_strategy(id, deleted_strategy);
|
||||
var working_orders = new List<uint>();
|
||||
var missing_strategies = new List<uint>();
|
||||
var items = new List<(uint key, ExecutionStrategyImmutable strategy)>();
|
||||
var temp_strats = _strategy_map;
|
||||
foreach (var k in keys) {
|
||||
if (orders.state.TryGetValue(k, out var sec_group) && sec_group.table.state.Values.Any(tup => tup.order.is_active())) {
|
||||
working_orders.Add(k);
|
||||
} else if (!temp_strats.TryGetValue(k, out var strat)) {
|
||||
missing_strategies.Add(k);
|
||||
} else {
|
||||
items.Add((k, strat.with(deleted: true)));
|
||||
}
|
||||
}
|
||||
var lines = new List<string>();
|
||||
if (working_orders.Count > 0) {
|
||||
lines.Add($"skipping {working_orders.Count} securities because they have working orders.");
|
||||
}
|
||||
if (missing_strategies.Count > 0) {
|
||||
lines.Add($"skipping {missing_strategies.Count} securities because their execution strategies were not discovered.");
|
||||
}
|
||||
if (lines.Count > 0)
|
||||
log.warn(string.Join("; ", lines));
|
||||
var errors = await _orders_model.spreader.save_strategies(items);
|
||||
int error_count = errors.Count(e => !string.IsNullOrEmpty(e));
|
||||
log.info($"{items.Count - error_count}/{items.Count} attempted deletes were successful (skipped {working_orders.Count + missing_strategies.Count}).");
|
||||
// display results to user
|
||||
const string dialog_title = "Synthetic Deletion";
|
||||
string display_msg;
|
||||
MessageBoxImage icon = MessageBoxImage.None;
|
||||
if (keys.Count > 1) {
|
||||
if (error_count == 0 && working_orders.Count == 0 && missing_strategies.Count == 0) {
|
||||
display_msg = $"Synthetic securities have been deleted.";
|
||||
icon = MessageBoxImage.Information;
|
||||
} else {
|
||||
display_msg = $"{keys.Count - error_count - working_orders.Count - missing_strategies.Count}/{keys.Count} securities were successfully deleted.";
|
||||
icon = MessageBoxImage.Error;
|
||||
}
|
||||
} else {
|
||||
if (error_count == 0 && working_orders.Count == 0 && missing_strategies.Count == 0) {
|
||||
display_msg = "Synthetic security has been deleted.";
|
||||
icon = MessageBoxImage.Information;
|
||||
} else {
|
||||
string reason;
|
||||
if (error_count > 0) {
|
||||
reason = errors[0];
|
||||
} else if (missing_strategies.Count > 0) {
|
||||
reason = "did not find execution strategy.";
|
||||
} else if (working_orders.Count > 0) {
|
||||
reason = "working orders detected.";
|
||||
} else {
|
||||
// unexpected code path
|
||||
reason = "unknown error.";
|
||||
}
|
||||
icon = MessageBoxImage.Error;
|
||||
display_msg = $"Unable to delete synthetic security: " + reason;
|
||||
}
|
||||
}
|
||||
BTSMessageBox.Show(display_msg, dialog_title, BTSMessageBoxButtons.OK, icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.support;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
@@ -164,5 +167,45 @@ namespace bts.spark {
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
|
||||
}
|
||||
|
||||
public static async Task<(TResult result, string error_msg)[]> safe_execute_batch<T, TResult>(
|
||||
this ErrorReporter error_reporter,
|
||||
IEnumerable<Task<T>> execute,
|
||||
Func<T, (TResult result, string error_msg)> result_selector,
|
||||
string desc,
|
||||
string title) {
|
||||
//
|
||||
try {
|
||||
var results = await Task.WhenAll(execute as Task<T>[] ?? ArrayHelper.clone_optimized(execute));
|
||||
var result_values = new (TResult, string)[results.Length];
|
||||
StringBuilder error_bd = null;
|
||||
for (int i = 0; i < results.Length; i++) {
|
||||
var r = results[i];
|
||||
var (result, error) = result_selector(r);
|
||||
if (!string.IsNullOrEmpty(error)) {
|
||||
if (error_bd == null) {
|
||||
error_bd = new StringBuilder();
|
||||
} else {
|
||||
error_bd.AppendLine();
|
||||
}
|
||||
error_bd.Append(error);
|
||||
}
|
||||
result_values[i] = (result, error);
|
||||
}
|
||||
if (error_bd != null) {
|
||||
var error_msg = error_bd.ToString();
|
||||
if (!string.IsNullOrEmpty(error_msg))
|
||||
_report_error(error_msg);
|
||||
}
|
||||
return result_values;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
_report_error(ex.ToString());
|
||||
}
|
||||
return null;
|
||||
//
|
||||
void _report_error(string msg) =>
|
||||
error_reporter.report_error($"Error {desc}: {msg}", title, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace bts {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
.Select(upd => upd.all.ToDictionary(i => i.SecurityKey, i => i.Strategy))
|
||||
.Select(upd => upd.all.Where(i => i.HasStrategy).ToDictionary(i => i.SecurityKey, i => i.Strategy))
|
||||
.Subscribe(t => {
|
||||
var deleted_synthetic_keys = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
// add back 'undeleted' synthetics
|
||||
@@ -64,4 +64,4 @@ namespace bts {
|
||||
Task.Run(run);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts;
|
||||
using mbts.ui.dpi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -25,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),
|
||||
@@ -44,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),
|
||||
@@ -55,10 +59,14 @@ namespace mbts.ui {
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
UndoCircularGeometryKey = nameof(UndoCircularGeometryKey);
|
||||
UndoCircularGeometryKey = nameof(UndoCircularGeometryKey),
|
||||
WarningExclamationGeometryKey = nameof(WarningExclamationGeometryKey),
|
||||
WarningGeometryKey = nameof(WarningGeometryKey);
|
||||
|
||||
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));
|
||||
|
||||
@@ -153,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);
|
||||
@@ -176,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);
|
||||
@@ -190,6 +201,8 @@ namespace mbts.ui {
|
||||
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
|
||||
public static ImageSource Tools => create_icon(ToolsGeometryKey);
|
||||
public static ImageSource Trash => create_icon(TrashGeometryKey);
|
||||
public static ImageSource Warning => create_icon(WarningGeometryKey, Brushes.IndianRed);
|
||||
public static ImageSource WarningExclamation => create_icon(WarningExclamationGeometryKey, Brushes.IndianRed);
|
||||
public static ImageSource UndoCircular => create_icon(UndoCircularGeometryKey);
|
||||
public static ImageSource ViewSettings => load_resource_and_freeze("ViewSettings");
|
||||
public static ImageSource Wrench { get { return load_resource_and_freeze("Wrench"); } }
|
||||
|
||||
@@ -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>
|
||||
@@ -363,6 +384,18 @@
|
||||
c-84.15,0-153-68.85-153-153h-51c0,112.2,91.8,204,204,204s204-91.8,204-204S360.825,89.25,248.625,89.25z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.WarningExclamationGeometryKey}">
|
||||
M15.998999,17.988002C16.551,17.988002 16.999,18.437009 16.999,18.990018 16.999,19.543027 16.551,19.992034 15.998999,19.992034 15.446999,19.992034 14.998999,19.543027 14.998999,18.990018 14.998999,18.437009 15.446999,17.988002 15.998999,17.988002z
|
||||
M15.998999,8.0630169C16.552,8.0630169,16.999,8.5110159,16.999,9.0650149L16.999,16.167001C16.999,16.721 16.552,17.168998 15.998999,17.168998 15.446,17.168998 14.998999,16.721 14.998999,16.167001L14.998999,9.0650149C14.998999,8.5110159,15.446,8.0630169,15.998999,8.0630169z
|
||||
</sys:String>
|
||||
|
||||
<sys:String x:Key="{x:Static mbts:Icons.WarningGeometryKey}">
|
||||
M15.998999,17.988002C16.551,17.988002 16.999,18.437009 16.999,18.990018 16.999,19.543027 16.551,19.992034 15.998999,19.992034 15.446999,19.992034 14.998999,19.543027 14.998999,18.990018 14.998999,18.437009 15.446999,17.988002 15.998999,17.988002z
|
||||
M15.998999,8.0630169C16.552,8.0630169,16.999,8.5110159,16.999,9.0650149L16.999,16.167001C16.999,16.721 16.552,17.168998 15.998999,17.168998 15.446,17.168998 14.998999,16.721 14.998999,16.167001L14.998999,9.0650149C14.998999,8.5110159,15.446,8.0630169,15.998999,8.0630169z
|
||||
M15.999277,2.3337629L2.3830507,22.996989 29.615442,22.996989z
|
||||
M15.999513,0C16.50052,-3.8678991E-07,17.001274,0.21826346,17.288297,0.65479128L31.74151,22.585975C32.057495,23.064982 32.084472,23.677966 31.812494,24.184989 31.541494,24.687009 31.02049,24.999999 30.450475,24.999999L1.5480773,24.999999C0.97903845,24.999999 0.45705819,24.687009 0.18703459,24.184989 -0.085979476,23.677966 -0.057963934,23.064982 0.25704309,22.585975L14.709219,0.65479128C14.997247,0.21826346,15.498506,-3.8678991E-07,15.999513,0z
|
||||
</sys:String>
|
||||
|
||||
<!--
|
||||
'y' for equilateral triangle of side length x:
|
||||
(x/2)^2 + Y^2 = x^2
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
@@ -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,62 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.utils;
|
||||
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 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,87 +1,365 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.collections;
|
||||
using bts.utils.csv;
|
||||
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;
|
||||
@@ -91,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(),
|
||||
@@ -108,86 +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 static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(TimeAndSalesVM.Row),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(time),
|
||||
nameof(price),
|
||||
nameof(size),
|
||||
nameof(aggressor_side),
|
||||
nameof(security_key),
|
||||
});
|
||||
|
||||
|
||||
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,127 +1,225 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.api;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils.csv;
|
||||
using bts.view;
|
||||
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[] {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.value.row_vms.ToCsv(TimeAndSalesVM.Row.csv_ordered_properties), "TaS")),
|
||||
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)),
|
||||
// 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 ? new SecurityCriteria(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 ? new SecurityCriteria(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:ConditionalQuoting.Types.PriceType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ConditionalPriceTypeTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ConditionalPriceTypeTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding enabled}">
|
||||
@@ -333,13 +333,61 @@
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ContractPickerButton" TargetType="Button">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="ToolTip" Value="Change Security"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Bd"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="Bd" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#24FFFFFF"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="Bd" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#40FFFFFF"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Border.Background>
|
||||
<SolidColorBrush/>
|
||||
</Border.Background>
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<DataGridTextColumn x:Key="{x:Static usp:ExecutionStrategyEditor.AddColumnPlaceholderKey}" x:Shared="False" IsReadOnly="True">
|
||||
<DataGridTextColumn.Header>
|
||||
<c:PopupControl
|
||||
ShowExpanderArrow="False" PopupPlacement="Right" Header="Add Leg" Padding="6,0"
|
||||
ShowExpanderArrow="False"
|
||||
PopupPlacement="Right"
|
||||
Header="Add Leg"
|
||||
Padding="6,0"
|
||||
Background="{StaticResource Button.Static.BackgroundBrush}"
|
||||
IsOpen="{Binding IsNewLegPopupOpen, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}, Mode=TwoWay}"
|
||||
IsEnabled="{Binding CanUserAddNewLeg, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}}"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<msp:ContractPickerView
|
||||
CloseOnOK="False" CloseOnCancel="False"
|
||||
@@ -348,7 +396,7 @@
|
||||
</c:PopupControl>
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}">
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}" DataType="{x:Type usp:LegSpreaderParamsTuple}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -356,13 +404,40 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Command="{Binding RemoveLegCommand, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}}"
|
||||
CommandParameter="{Binding}" Style="{StaticResource RoundCloseButton}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource RoundCloseButton}"
|
||||
ToolTip="Remove Leg"
|
||||
/>
|
||||
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
<c:ContractPickerPopup
|
||||
Grid.Column="1"
|
||||
BorderThickness="0"
|
||||
ButtonStyle="{StaticResource ContractPickerButton}"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding ContractPickerModel.all_securities, RelativeSource={RelativeSource AncestorType=usp:ExecutionStrategyEditor}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security}"
|
||||
ShowDialogSkirt="True"
|
||||
>
|
||||
<c:ContractPickerPopup.Background>
|
||||
<SolidColorBrush/>
|
||||
</c:ContractPickerPopup.Background>
|
||||
<c:ContractPickerPopup.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Foreground="{x:Static u:BTSColors.header_text_brush}" Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</c:ContractPickerPopup.HeaderTemplate>
|
||||
</c:ContractPickerPopup>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.NonRemovableColumnHeaderTemplateKey}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
<DataTemplate x:Key="ReadOnlyColumnHeaderTemplate" DataType="{x:Type usp:LegSpreaderParamsTuple}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding security}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
<ControlTemplate x:Key="ExecutionStrategyEditorTemplate" TargetType="usp:ExecutionStrategyEditor">
|
||||
<Border
|
||||
@@ -474,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"
|
||||
@@ -560,6 +659,14 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.ColumnHeaderStyle>
|
||||
<Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Padding" Value="5,0"/>
|
||||
<Setter Property="MinHeight" Value="26"/>
|
||||
</Style>
|
||||
</DataGrid.ColumnHeaderStyle>
|
||||
<DataGrid.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource GroupItemStyle}"/>
|
||||
</DataGrid.GroupStyle>
|
||||
|
||||
@@ -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(
|
||||
@@ -227,8 +247,8 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
|
||||
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
|
||||
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
|
||||
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
|
||||
var col = new DataGridColumnInternal(l, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter);
|
||||
col.HeaderTemplate = TryFindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey) as DataTemplate;
|
||||
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
|
||||
return col;
|
||||
}
|
||||
@@ -237,10 +257,12 @@ namespace mbts.ui.spark {
|
||||
|
||||
private class DataGridColumnInternal : DataGridDynamicColumn {
|
||||
readonly IValueConverter _cell_template_converter, _cell_editing_template_converter;
|
||||
readonly LegSpreaderParamsTuple _leg_tup;
|
||||
|
||||
public DataGridColumnInternal(Security security, IValueConverter cell_template_converter, IValueConverter cell_editing_template_converter) {
|
||||
Header = security;
|
||||
SetValue(DataGridEx.CellKeyProperty, security);
|
||||
public DataGridColumnInternal(LegSpreaderParamsTuple leg_tup, IValueConverter cell_template_converter, IValueConverter cell_editing_template_converter) {
|
||||
_leg_tup = leg_tup;
|
||||
Header = leg_tup;
|
||||
SetValue(DataGridEx.CellKeyProperty, leg_tup);
|
||||
_cell_template_converter = cell_template_converter;
|
||||
_cell_editing_template_converter = cell_editing_template_converter;
|
||||
}
|
||||
@@ -248,8 +270,8 @@ namespace mbts.ui.spark {
|
||||
// I decided this was an easy and simple enough solution to justify breaking the mvvm paradigm
|
||||
// The alternative - within strict bounds of the paradigm - is to change the DataGridParameterCellTemplateConverter into an IMultiValueConverter, bound to data_type AND read_only
|
||||
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
|
||||
if (dataItem is SyntheticEditorVM.RowVM row_vm) {
|
||||
if (row_vm.read_only || row_vm.get_cell(Header) is SyntheticEditorVM.ICell cell_vm && !cell_vm.enabled)
|
||||
if (dataItem is RowVM row_vm) {
|
||||
if (row_vm.read_only || row_vm.get_cell(_leg_tup) is ICell cell_vm && !cell_vm.enabled)
|
||||
return base.GenerateElement(cell, dataItem);
|
||||
}
|
||||
return base.GenerateEditingElement(cell, dataItem);
|
||||
@@ -352,4 +374,4 @@ namespace mbts.ui.spark {
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.spreader.ContractLegTypeView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
>
|
||||
<ItemsControl ItemsSource="{Binding Controller.flags, RelativeSource={RelativeSource AncestorType=spr:ContractLegTypeView}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox
|
||||
Margin="2"
|
||||
IsChecked="{Binding is_selected}"
|
||||
Content="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeShortDescConverter}}"
|
||||
ToolTip="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeLongDescConverter}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,104 @@
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
static class EnumFlagItemsVM {
|
||||
public static EnumFlagItemsVM<T> create<T>(IEnumerable<T> flags) where T : struct =>
|
||||
new EnumFlagItemsVM<T>(flags);
|
||||
}
|
||||
|
||||
class EnumFlagItemsVM<T> : ViewModelBase where T : struct {
|
||||
readonly ObservableCollection<SelectableItemVM<T>> _flags = new ObservableCollection<SelectableItemVM<T>>();
|
||||
bool _is_busy;
|
||||
T _result;
|
||||
|
||||
public EnumFlagItemsVM()
|
||||
: this(EnumHelper.get_values<T>()) { }
|
||||
|
||||
public EnumFlagItemsVM(IEnumerable<T> flags) {
|
||||
this.flags = ObservableAutoWrapper.create_read_only_wrapper(_flags);
|
||||
_flags.AddRange(flags.Select(f => new SelectableItemVM<T>(f)));
|
||||
foreach (var vm in _flags) {
|
||||
vm.is_selected_changed += on_is_selected_changed;
|
||||
void on_is_selected_changed(bool value) {
|
||||
if (!_is_busy) {
|
||||
_is_busy = true;
|
||||
result = value ?
|
||||
EnumHelper.add_flags(result, vm.value) :
|
||||
EnumHelper.remove_flags(result, vm.value);
|
||||
_is_busy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
public IReadOnlyObservableCollection<SelectableItemVM<T>> flags { get; }
|
||||
|
||||
public T result {
|
||||
get => _result;
|
||||
set {
|
||||
if (!EqualityComparer<T>.Default.Equals(_result, value)) {
|
||||
_result = value;
|
||||
if (!_is_busy) {
|
||||
_is_busy = true;
|
||||
for (int i = 0; i < flags.Count; i++)
|
||||
flags[i].is_selected = EnumHelper.has_flags(value, flags[i].value);
|
||||
_is_busy = false;
|
||||
}
|
||||
result_changed?.Invoke();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Interaction logic for ContractLegTypeView.xaml
|
||||
/// </summary>
|
||||
partial class ContractLegTypeView : UserControl {
|
||||
public static IValueConverter LegTypeShortDescConverter =>
|
||||
ValueConverter.create<ContractLegType, string>(SyntheticSecurityParser.display_name);
|
||||
|
||||
public static IValueConverter LegTypeLongDescConverter =>
|
||||
ValueConverter.create<ContractLegType, string>(lt =>
|
||||
string.Join(" & ", EnumHelper.split_flags(lt).Select(f => EnumHelper.get_display_name(f))));
|
||||
|
||||
public ContractLegTypeView() {
|
||||
InitializeComponent();
|
||||
Controller.result = Value;
|
||||
Controller.result_changed += _on_controller_result_changed;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
WPFHelper.create_dp<ContractLegType, ContractLegTypeView>(
|
||||
nameof(Value),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => {
|
||||
o.Controller.result_changed -= o._on_controller_result_changed;
|
||||
o.Controller.result = v;
|
||||
o.Controller.result_changed += o._on_controller_result_changed;
|
||||
});
|
||||
|
||||
public ContractLegType Value {
|
||||
get => (ContractLegType)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public EnumFlagItemsVM<ContractLegType> Controller { get; } = EnumFlagItemsVM.create(EnumHelper.get_values_except(ContractLegType.None));
|
||||
|
||||
void _on_controller_result_changed() =>
|
||||
SetCurrentValue(ValueProperty, Controller.result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<Window
|
||||
x:Class="bts.shell.spreader.SyntheticImportConflictsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Synthetics Conflicts"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
>
|
||||
<Window.MaxHeight>
|
||||
<Binding Path="SizeToContent" RelativeSource="{RelativeSource Self}">
|
||||
<Binding.Converter>
|
||||
<cv:WindowMaxHeightConverter SizeToContentThreshold="{u:DPIDouble 700}"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Window.MaxHeight>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<spr:SyntheticImportConflictsView Margin="12,10,12,8"/>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Command="{Binding ok_cmd}">Continue</Button>
|
||||
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportConflictsDialog.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportConflictsDialog : Window {
|
||||
public SyntheticImportConflictsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.user.settings;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
readonly struct SyntheticComponent {
|
||||
public readonly SyntheticContractComponent type;
|
||||
public readonly int leg_index;
|
||||
|
||||
public SyntheticComponent(SyntheticContractComponent type, int leg_index) {
|
||||
this.type = type;
|
||||
this.leg_index = leg_index;
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportConflictsVM : ImportConflictsVM<(SyntheticImportRowVM synth, ContractLegVM leg), SyntheticImportRowVM, SyntheticContractComponent>, IRequestViewClose {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SyntheticImportConflictsVM(int num_synthetics)
|
||||
: base(num_synthetics, tup => tup.synth) { }
|
||||
|
||||
// misc items sources
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
if (!ReferenceEquals(_security_map, value)) {
|
||||
_security_map = value;
|
||||
_resolve_securities();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _resolve_securities() {
|
||||
if (_security_map == null)
|
||||
return;
|
||||
var reverse_lookup = SyntheticSecurityParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
|
||||
foreach (var r in conflicts) {
|
||||
if (r.component == SyntheticContractComponent.Security && r.has_error) {
|
||||
var id = reverse_lookup(r.text);
|
||||
if (id != 0 && _security_map.TryGetValue(id, out var smd))
|
||||
r.value = smd.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportConflictRowVM : ImportConflictRowVM<(SyntheticImportRowVM synth, ContractLegVM leg), SyntheticContractComponent>, ITemplateSelectorKey {
|
||||
public SyntheticImportConflictRowVM(
|
||||
SyntheticContractComponent component,
|
||||
string text,
|
||||
IReadOnlyCollection<(SyntheticImportRowVM synth, ContractLegVM leg)> affected_parts,
|
||||
object error_value,
|
||||
object value)
|
||||
: base(component, text, affected_parts, error_value, value, affected_parts.DistinctOn(tup => tup.synth).Count()) { }
|
||||
|
||||
public override void on_conflicts_resolved() {
|
||||
if (has_error)
|
||||
return;
|
||||
foreach (var (synth, leg) in affected_parts) {
|
||||
switch (component) {
|
||||
case SyntheticContractComponent.Name:
|
||||
if (value is string str)
|
||||
synth.name = str;
|
||||
break;
|
||||
case SyntheticContractComponent.TickSize:
|
||||
if (value is double ts && ts.IsNonZero()) {
|
||||
synth.tick_factors = TickUtils.get_regional_ticks(new ContractSpecificationImmutable(new ContractSpecification {
|
||||
LowTick = ts,
|
||||
HighTick = ts
|
||||
})).low_tick;
|
||||
} else if (value is TickFactors tf && tf.is_valid())
|
||||
synth.tick_factors = tf;
|
||||
break;
|
||||
case SyntheticContractComponent.Security:
|
||||
if (value is SecurityImmutable sec && sec.is_exchange_security())
|
||||
leg.security = sec;
|
||||
break;
|
||||
case SyntheticContractComponent.PriceMultiplier:
|
||||
if (value is double pm && pm.IsNonZero())
|
||||
leg.price_multiplier = pm;
|
||||
break;
|
||||
case SyntheticContractComponent.Quantity:
|
||||
if (value is double q && q.IsNonZero())
|
||||
leg.quantity = q;
|
||||
break;
|
||||
case SyntheticContractComponent.LegType:
|
||||
if (value is ContractLegType lt)
|
||||
leg.leg_type = lt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.spreader.SyntheticImportConflictsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
MinWidth="400"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<!--Value Cell Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="PriceMultiplier">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Quantity">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Security">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="LegType">
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeShortDescConverter}}">
|
||||
<TextBlock.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding value, Converter={x:Static spr:ContractLegTypeView.LegTypeLongDescConverter}}"/>
|
||||
<LineBreak/>
|
||||
<Run Text="Q = Quote"/>
|
||||
<LineBreak/>
|
||||
<Run Text="H = Hedge"/>
|
||||
<LineBreak/>
|
||||
<Run Text="N = None"/>
|
||||
</TextBlock>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
<!--Value Cell Editing Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="Security">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding value, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Name">
|
||||
<DataTemplate>
|
||||
<TextBox u:Behavior.FocusOnLoaded="True" Text="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="TickSize">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="PriceMultiplier">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble Value="{Binding value}" DisplayConverter="{StaticResource DoubleTextConverter}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="Quantity">
|
||||
<DataTemplate>
|
||||
<c:TickUpDownDouble Value="{Binding value}" DisplayConverter="{StaticResource DoubleTextConverter}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="LegType">
|
||||
<DataTemplate>
|
||||
<spr:ContractLegTypeView Value="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<!--Content-->
|
||||
<Grid x:Name="content_root">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image VerticalAlignment="Center" Margin="0,0,8,0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
|
||||
<Run Text="{Binding num_successfully_parsed_rows, Mode=OneWay}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding num_rows, Mode=OneWay}"/>
|
||||
<Run Text=" synthetics were successfully converted."/>
|
||||
<Run Text="Resolve conflicts below by selecting a value to assign each affected synthetic,"/>
|
||||
<Run Text="or modify individual synthetics in the main window."/>
|
||||
<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Run Text="Click 'Continue' to proceed to the main window."/>
|
||||
</TextBlock>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding conflicts_view}"
|
||||
Margin="0,8,0,0"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
SelectionMode="Single"
|
||||
>
|
||||
<DataGrid.RowValidationErrorTemplate>
|
||||
<ControlTemplate>
|
||||
<Grid Background="Green">
|
||||
<Border x:Name="exclamation" HorizontalAlignment="Center">
|
||||
<Image Height="12" VerticalAlignment="Center" Source="{x:Static u:Icons.WarningExclamation}"/>
|
||||
</Border>
|
||||
<ContentControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ToolTip="{Binding (Validation.Errors), RelativeSource={RelativeSource AncestorType=DataGridRow}}">
|
||||
<ContentControl.Template>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Content="{Binding ToolTip, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ToolTip.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}" DisplayMemberPath="ErrorContent"/>
|
||||
</DataTemplate>
|
||||
</ToolTip.ContentTemplate>
|
||||
</ToolTip>
|
||||
</Grid.ToolTip>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ContentControl.Template>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</DataGrid.RowValidationErrorTemplate>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="ValidationErrorTemplate" Value="{Binding RowValidationErrorTemplate, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Component" Binding="{Binding component, Converter={StaticResource EnumDescriptionConverter}}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Affected Synthetics" Binding="{Binding num_affected_rows}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
enum SyntheticContractComponent {
|
||||
Name,
|
||||
TickSize,
|
||||
Security,
|
||||
PriceMultiplier,
|
||||
Quantity,
|
||||
LegType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportConflictsView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportConflictsView : UserControl {
|
||||
public SyntheticImportConflictsView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<Window
|
||||
x:Class="bts.shell.spreader.SyntheticImportDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Synthetics"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<Window.MaxHeight>
|
||||
<Binding Path="SizeToContent" RelativeSource="{RelativeSource Self}">
|
||||
<Binding.Converter>
|
||||
<cv:WindowMaxHeightConverter SizeToContentThreshold="{u:DPIDouble 700}"/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Window.MaxHeight>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type spr:SyntheticImportVM}">
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<spr:SyntheticImportView/>
|
||||
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding ok_cmd}" CancelCommand="{Binding cancel_cmd}"/>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</DataTemplate>
|
||||
</Window.ContentTemplate>
|
||||
</Window>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportDialog.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportDialog : Window {
|
||||
public SyntheticImportDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
class SyntheticImportRowCollectionView : ListCollectionView {
|
||||
ReadOnlyObservableCollection<object> _base_groups, _groups_wrapper;
|
||||
ObservableAutoWrapper<object, object> _groups;
|
||||
|
||||
public SyntheticImportRowCollectionView(IList list)
|
||||
: base(list) { }
|
||||
|
||||
public override bool CanGroup => true;
|
||||
|
||||
// Must return null if there are no GroupDescriptions (i.e., instead of an empty collection).
|
||||
// If an empty collection is returned, the view assumes grouping is active and there are no items.
|
||||
public override ReadOnlyObservableCollection<object> Groups {
|
||||
get {
|
||||
if ((GroupDescriptions?.Count ?? 0) == 0)
|
||||
return null;
|
||||
if (_groups_wrapper == null) {
|
||||
_base_groups = base.Groups;
|
||||
if (_base_groups == null)
|
||||
return null;
|
||||
_groups = new ObservableAutoWrapper<object, object>(_base_groups, obj => new SyntheticImportRowCollectionViewGroup((CollectionViewGroup)obj, this));
|
||||
_groups_wrapper = new ReadOnlyObservableCollection<object>(_groups);
|
||||
}
|
||||
return _groups_wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
public void set_all_expanded(bool is_expanded) {
|
||||
foreach (var g in Groups.OfTypeSafe<SyntheticImportRowCollectionViewGroup>())
|
||||
g.set_all_expanded(is_expanded);
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportRowCollectionViewGroup : CollectionViewGroup, INotifyDataErrorInfo, IRaiseNotifyPropertyChanged, IEquatable<SyntheticImportRowCollectionViewGroup> {
|
||||
static readonly string _desc_delim = ", ";
|
||||
readonly SyntheticImportRowCollectionView _root;
|
||||
readonly CollectionViewGroup _from;
|
||||
readonly Dictionary<object, InnerGroupSubscription> _inner_groups = new Dictionary<object, InnerGroupSubscription>();
|
||||
readonly Dictionary<int, IDisposable> _rows = new Dictionary<int, IDisposable>();
|
||||
string
|
||||
_leg_type_desc,
|
||||
_security_desc,
|
||||
_quantity_desc,
|
||||
_price_multiplier_desc;
|
||||
|
||||
IDisposable _dispose;
|
||||
bool _is_expanded;
|
||||
|
||||
public SyntheticImportRowCollectionViewGroup(CollectionViewGroup from, SyntheticImportRowCollectionView root) : base(from.Name) {
|
||||
_root = root;
|
||||
_from = from;
|
||||
((INotifyCollectionChanged)_from.Items).CollectionChanged += _from_items_CollectionChanged;
|
||||
_from_items_CollectionChanged(_from.Items, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _from.Items, 0));
|
||||
}
|
||||
|
||||
public event Action ProtectedItemCountChanged;
|
||||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
|
||||
|
||||
public override bool IsBottomLevel => _from.IsBottomLevel;
|
||||
public bool is_expanded { get => _is_expanded; set => _set(ref _is_expanded, value); }
|
||||
public int level => parent == null ? 1 : parent.level + 1;
|
||||
public SyntheticImportRowCollectionViewGroup parent { get; private set; }
|
||||
// leg type desc
|
||||
public string leg_type_desc =>
|
||||
_leg_type_desc ?? (_leg_type_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.leg_type.display_name())));
|
||||
// price multiplier desc
|
||||
public string price_multiplier_desc =>
|
||||
_price_multiplier_desc ?? (_price_multiplier_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.price_multiplier)));
|
||||
// quantity desc
|
||||
public string quantity_desc =>
|
||||
_quantity_desc ?? (_quantity_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.quantity)));
|
||||
// security desc
|
||||
public string security_desc =>
|
||||
_security_desc ?? (_security_desc = string.Join(_desc_delim, ProtectedItems.OfType<ContractLegVM>().Select(l => l.security?.ToString() ?? "?").ExceptNull()).if_null_or_empty("(No Legs)"));
|
||||
|
||||
new int ProtectedItemCount {
|
||||
get => base.ProtectedItemCount;
|
||||
set {
|
||||
if (base.ProtectedItemCount != value) {
|
||||
base.ProtectedItemCount = value;
|
||||
ProtectedItemCountChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasErrors => Items.OfType<ContractLegVM>().Any(l => l.HasErrors);
|
||||
public IEnumerable GetErrors(string propertyName) =>
|
||||
propertyName == nameof(Items) ?
|
||||
Items.OfType<ContractLegVM>().SelectMany(l => l.AllErrors) :
|
||||
Enumerable.Empty<string>();
|
||||
|
||||
public void set_all_expanded(bool is_expanded) {
|
||||
this.is_expanded = is_expanded;
|
||||
foreach (var g in Items.OfTypeSafe<SyntheticImportRowCollectionViewGroup>())
|
||||
g.set_all_expanded(is_expanded);
|
||||
}
|
||||
|
||||
public bool Equals(SyntheticImportRowCollectionViewGroup other) =>
|
||||
other != null &&
|
||||
level == other.level &&
|
||||
Equals(Name, other.Name);
|
||||
|
||||
void IRaiseNotifyPropertyChanged.raise_notify_property_changed(string prop) =>
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
|
||||
void _add_item(int index, object item) {
|
||||
Log.assert(index >= 0, "Index is less than 0.");
|
||||
if (item is CollectionViewGroup group) {
|
||||
var inner_group = new SyntheticImportRowCollectionViewGroup(group, _root);
|
||||
_inner_groups.Add(group.Name, new InnerGroupSubscription(this, inner_group));
|
||||
ProtectedItems.Insert(index, inner_group);
|
||||
} else {
|
||||
var row = (ContractLegVM)item;
|
||||
if (_rows.TryPopValue(row.id, out var sub))
|
||||
sub.Dispose();
|
||||
_rows[row.id] = _connect_row(row);
|
||||
ProtectedItems.Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
void _remove_item(int index, object item) {
|
||||
if (item is SyntheticImportRowCollectionViewGroup inner_group) {
|
||||
if (!_inner_groups.TryPopValue(inner_group.Name, out var ig_sub))
|
||||
throw new InvalidOperationException($"Inner groups cache does not have an entry for the specified name '{inner_group.Name}' (entire tree structure may be compromised).");
|
||||
ig_sub.Dispose();
|
||||
Disposable.dispose(ref inner_group._dispose);
|
||||
}
|
||||
ProtectedItems.RemoveAt(index);
|
||||
}
|
||||
|
||||
void _from_items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
for (int i = 0; i < e.OldItems.Count; i++)
|
||||
_remove_item(e.OldStartingIndex, ProtectedItems[e.OldStartingIndex]);
|
||||
for (int i = 0; i < e.NewItems.Count; i++)
|
||||
_add_item(e.NewStartingIndex + i, e.NewItems[i]);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
while (ProtectedItems.Count > 0)
|
||||
_remove_item(0, ProtectedItems[0]);
|
||||
for (int i = 0; i < _from.Items.Count; i++)
|
||||
_add_item(i, _from.Items[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _invalidate_props(params string[] props) {
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(props[i]));
|
||||
}
|
||||
|
||||
void _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = "") =>
|
||||
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
|
||||
|
||||
void _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = "") {
|
||||
if (!comparer.Equals(backing, new_value)) {
|
||||
backing = new_value;
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(prop));
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable _connect_row(ContractLegVM row) {
|
||||
row.result_changed += on_result_changed;
|
||||
row.ErrorsChanged += on_errors_changed;
|
||||
return Disposable.Create(() => {
|
||||
row.result_changed -= on_result_changed;
|
||||
row.ErrorsChanged -= on_errors_changed;
|
||||
});
|
||||
void on_result_changed() {
|
||||
_security_desc = null;
|
||||
_price_multiplier_desc = null;
|
||||
_quantity_desc = null;
|
||||
_leg_type_desc = null;
|
||||
_invalidate_props(nameof(security_desc), nameof(price_multiplier_desc), nameof(quantity_desc), nameof(leg_type_desc));
|
||||
}
|
||||
void on_errors_changed(object sender, DataErrorsChangedEventArgs e) =>
|
||||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Items)));
|
||||
}
|
||||
|
||||
class InnerGroupSubscription : IDisposable {
|
||||
public readonly SyntheticImportRowCollectionViewGroup _inner_group;
|
||||
readonly SyntheticImportRowCollectionViewGroup _parent;
|
||||
int _last_count;
|
||||
|
||||
public InnerGroupSubscription(SyntheticImportRowCollectionViewGroup parent, SyntheticImportRowCollectionViewGroup inner_group) {
|
||||
_parent = parent;
|
||||
_inner_group = inner_group;
|
||||
_inner_group.parent = parent;
|
||||
// init item count
|
||||
_on_item_count_changed();
|
||||
// listen to item count changes
|
||||
_inner_group.ProtectedItemCountChanged += _on_item_count_changed;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_inner_group.ProtectedItemCountChanged -= _on_item_count_changed;
|
||||
_inner_group.parent = null;
|
||||
// make sure the state is valid (if a bad state goes by undetected here, nefarious/difficult-to-track bugs could arise)
|
||||
if (_last_count != _inner_group.ProtectedItemCount) {
|
||||
Log.Ui.warn("Cached inner count is not equal to the inner group's ItemCount (entire tree structure may be compromised).");
|
||||
}
|
||||
_parent.ProtectedItemCount -= _last_count;
|
||||
}
|
||||
|
||||
void _on_item_count_changed() {
|
||||
// add the delta
|
||||
_parent.ProtectedItemCount += (_inner_group.ProtectedItemCount - _last_count);
|
||||
// reset the local cache
|
||||
_last_count = _inner_group.ProtectedItemCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.user.settings;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Disposable = mbts.Disposable;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
class SyntheticImportVM : DisposableViewModelBase, IRequestViewClose {
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public SyntheticImportVM() {
|
||||
var rows_view = new SyntheticImportRowCollectionView(flat_legs);
|
||||
rows_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ContractLegVM.owner)));
|
||||
this.rows_view = rows_view;
|
||||
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
|
||||
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(
|
||||
rows,
|
||||
r => {
|
||||
flat_legs.AddRange(r.legs);
|
||||
return Disposable.Create(() => flat_legs.removeRange(r.legs));
|
||||
});
|
||||
}
|
||||
|
||||
public event Action import_request;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<SyntheticImportRowVM> rows { get; } = new ObservableCollection<SyntheticImportRowVM>();
|
||||
public ObservableCollection<ContractLegVM> flat_legs { get; } = new ObservableCollection<ContractLegVM>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map { get => _security_map; set => Set(ref _security_map, value); }
|
||||
public ICollectionView rows_view { get; }
|
||||
// commands
|
||||
public ICommand ok_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
}
|
||||
|
||||
class SyntheticImportRowVM : DisposableViewModelBase, IEquatable<SyntheticImportRowVM> {
|
||||
readonly ReactiveThresholdMonitor<ContractLegVM> _leg_errors_mon = new ReactiveThresholdMonitor<ContractLegVM>(l => l.subscribe_has_errors(true));
|
||||
readonly Func<string, bool> _get_name_exists;
|
||||
SecurityImmutable? _security;
|
||||
ExecutionStrategyImmutable? _strategy;
|
||||
TickFactors _tick_factors;
|
||||
string _name;
|
||||
bool _is_selected;
|
||||
|
||||
public SyntheticImportRowVM(uint user_id, Func<string, bool> get_name_exists) {
|
||||
this.user_id = user_id;
|
||||
_get_name_exists = get_name_exists;
|
||||
_validate();
|
||||
add_leg_cmd = new DelegateCommand<SyntheticImportRowVM>(r => {
|
||||
if (r != null) {
|
||||
r.legs.Add(new ContractLegVM {
|
||||
price_multiplier = 1,
|
||||
quantity = 1,
|
||||
owner = r
|
||||
});
|
||||
}
|
||||
});
|
||||
remove_leg_cmd = new DelegateCommand<ContractLegVM>(l => l?.owner?.legs?.Remove(l));
|
||||
PropertyChanged += on_property_changed;
|
||||
var disp = new DisposableCombo(
|
||||
_leg_errors_mon.OnDispatcher().Subscribe(v => _validate()),
|
||||
ObservableAutoWrapper.connect_item_hooks(legs,
|
||||
l => {
|
||||
l.result_changed += invalidate;
|
||||
return new DisposableCombo(_leg_errors_mon.join(l), l) +
|
||||
new Action(() => l.result_changed -= invalidate);
|
||||
})) +
|
||||
new Action(() => PropertyChanged -= on_property_changed);
|
||||
|
||||
void invalidate() {
|
||||
_security = null;
|
||||
_strategy = null;
|
||||
_validate();
|
||||
result_changed?.Invoke();
|
||||
}
|
||||
|
||||
void on_property_changed(object sender, PropertyChangedEventArgs e) =>
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
public SecurityImmutable result =>
|
||||
_security ?? (_security = new SecurityImmutable(to_security())).Value;
|
||||
|
||||
public ExecutionStrategyImmutable strategy =>
|
||||
_strategy ?? (_strategy = new ExecutionStrategyImmutable(to_strategy())).Value;
|
||||
|
||||
public (SecurityImmutable security, ExecutionStrategyImmutable strategy) result_tuple =>
|
||||
(result, strategy);
|
||||
|
||||
public ParsedSyntheticContract parsed_synth { get; set; }
|
||||
|
||||
public string name { get => _name; set => Set(ref _name, value); }
|
||||
public uint user_id { get; }
|
||||
public TickFactors tick_factors { get => _tick_factors; set => Set(ref _tick_factors, value); }
|
||||
public ObservableCollection<ContractLegVM> legs { get; } = new ObservableCollection<ContractLegVM>();
|
||||
public bool is_selected { get => _is_selected; set => Set(ref _is_selected, value); }
|
||||
public ICommand add_leg_cmd { get; }
|
||||
public ICommand remove_leg_cmd { get; }
|
||||
|
||||
public Security to_security() {
|
||||
var sc = new SyntheticContract {
|
||||
Name = name,
|
||||
UserId = user_id
|
||||
};
|
||||
sc.Legs.AddRange(legs.Select(l => l.to_contract_leg()));
|
||||
return new Security {
|
||||
Synthetic = sc,
|
||||
Symbol = sc.Legs.FirstOrDefault()?.Security?.Symbol ?? string.Empty,
|
||||
Exchange = Venue.Bts,
|
||||
ContractSpec = tick_factors.to_spec()
|
||||
};
|
||||
}
|
||||
|
||||
public ExecutionStrategy to_strategy() {
|
||||
var strat = new ExecutionStrategy();
|
||||
foreach (var l in legs) {
|
||||
if (l.security.HasValue)
|
||||
strat.LegParamsMap[l.security.Value.Id] = l.to_leg_params();
|
||||
}
|
||||
return strat;
|
||||
}
|
||||
|
||||
void _validate() {
|
||||
using (validation()) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
validation_assert(false, "Name is not specified.", nameof(name));
|
||||
} else if (_get_name_exists(name)) {
|
||||
validation_assert(false, "Name already exists.", nameof(name));
|
||||
}
|
||||
validation_assert(tick_factors != null && tick_factors.is_valid(), "Invalid tick", nameof(tick_factors));
|
||||
validation_assert(legs.Count > 0 && !_leg_errors_mon.is_above_threshold, "Invalid legs.", nameof(legs));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(SyntheticImportRowVM other) =>
|
||||
other != null &&
|
||||
parsed_synth.unpersisted_id == other.parsed_synth.unpersisted_id;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as SyntheticImportRowVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
parsed_synth.unpersisted_id;
|
||||
}
|
||||
|
||||
class ContractLegVM : DisposableViewModelBase, IEquatable<ContractLegVM> {
|
||||
ContractLegImmutable? _leg;
|
||||
LegSpreaderParamsImmutable? _leg_params;
|
||||
SecurityImmutable? _security;
|
||||
double _price_multiplier, _quantity;
|
||||
ContractLegType _leg_type;
|
||||
bool _is_selected;
|
||||
|
||||
public ContractLegVM() {
|
||||
PropertyChanged += on_property_changed;
|
||||
_dispose = Disposable.Create(() => PropertyChanged -= on_property_changed);
|
||||
_validate();
|
||||
void on_property_changed(object sender, PropertyChangedEventArgs e) {
|
||||
switch (e.PropertyName) {
|
||||
case nameof(security):
|
||||
case nameof(price_multiplier):
|
||||
case nameof(quantity):
|
||||
_leg = null;
|
||||
break;
|
||||
case nameof(leg_type):
|
||||
_leg_params = null;
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
_validate();
|
||||
result_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
|
||||
public ParsedContractLeg parsed_leg { get; set; }
|
||||
public SyntheticImportRowVM owner { get; set; }
|
||||
public int owner_id => owner.parsed_synth.unpersisted_id;
|
||||
public int id => parsed_leg.id;
|
||||
|
||||
public ContractLegImmutable leg =>
|
||||
_leg ?? (_leg = new ContractLegImmutable(to_contract_leg())).Value;
|
||||
|
||||
public LegSpreaderParamsImmutable leg_params =>
|
||||
_leg_params ?? (_leg_params = new LegSpreaderParamsImmutable(to_leg_params())).Value;
|
||||
|
||||
public (ContractLegImmutable leg, LegSpreaderParamsImmutable leg_params) result_tuple =>
|
||||
(leg, leg_params);
|
||||
|
||||
public SecurityImmutable? security { get => _security; set => Set(ref _security, value); }
|
||||
public double price_multiplier { get => _price_multiplier; set => Set(ref _price_multiplier, value); }
|
||||
public double quantity { get => _quantity; set => Set(ref _quantity, value); }
|
||||
public ContractLegType leg_type { get => _leg_type; set => Set(ref _leg_type, value); }
|
||||
public bool is_selected { get => _is_selected; set => Set(ref _is_selected, value); }
|
||||
|
||||
public void load(ContractLegImmutable leg) {
|
||||
security = leg.Security;
|
||||
price_multiplier = leg.PriceMultiplier;
|
||||
quantity = leg.Quantity;
|
||||
}
|
||||
|
||||
public ContractLeg to_contract_leg() =>
|
||||
new ContractLeg {
|
||||
Security = security?.CloneToMutable(),
|
||||
PriceMultiplier = price_multiplier,
|
||||
Side = quantity < 0 ? Side.Sell : Side.Buy,
|
||||
Quantity = Math.Abs(quantity)
|
||||
};
|
||||
|
||||
public LegSpreaderParams to_leg_params() {
|
||||
var lps = SyntheticDataHelper.Defaults.LegSpreaderParams;
|
||||
lps.EnableHedging = (leg_type & ContractLegType.Hedge) != 0;
|
||||
lps.IsActiveQuoting = (leg_type & ContractLegType.Quote) != 0;
|
||||
return lps;
|
||||
}
|
||||
|
||||
void _validate() {
|
||||
using (validation()) {
|
||||
validation_assert(security.is_exchange_security(), "Invalid security", nameof(security));
|
||||
validation_assert(price_multiplier.IsNonZero(), "Invalid price multiplier", nameof(price_multiplier));
|
||||
validation_assert(quantity.IsNonZero(), "Invalid quantity", nameof(quantity));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(ContractLegVM other) =>
|
||||
other != null && id == other.id;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as ContractLegVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.spreader.SyntheticImportView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:spr="clr-namespace:bts.shell.spreader"
|
||||
xmlns:pnl="clr-namespace:bts.shell.pnl"
|
||||
MinWidth="400"
|
||||
Content="{Binding}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
||||
<Style x:Key="GroupDesc" TargetType="TextBlock">
|
||||
<Setter Property="Margin" Value="{x:Static spr:SyntheticImportView.CellPadding}"/>
|
||||
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="#80000000"/>
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
</Style>
|
||||
<DataTemplate DataType="{x:Type spr:ContractLegVM}">
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedCriteria, RelativeSource={RelativeSource Self}, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}, Mode=OneWay}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type spr:SyntheticImportVM}">
|
||||
<c:DIYGrid
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Rows="{Binding rows_view}"
|
||||
RowHeight="{x:Static spr:SyntheticImportView.RowHeight}"
|
||||
RowsHorizontalScrollBarVisibility="Collapsed"
|
||||
SelectionHighlight="#30000000"
|
||||
SelectionMode="Single"
|
||||
SupportGroupItems="True"
|
||||
>
|
||||
<c:DIYGrid.Columns>
|
||||
<c:DIYGridColumnCollection>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Name}" Width="106"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Ticks}" Width="128"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Security}" Header="Securities" Width="120"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.Quantity}" Header="Quantities" Width="70"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.PriceMultiplier}" Header="Price Multipliers" Width="80"/>
|
||||
<spr:SyntheticImportColumn key="{x:Static spr:SyntheticImportColumns.LegType}" Header="Leg Types" Width="83"/>
|
||||
</c:DIYGridColumnCollection>
|
||||
</c:DIYGrid.Columns>
|
||||
<c:DIYGrid.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="GroupItem">
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<EventSetter Event="MouseDown" Handler="_on_group_item_mouse_down"/>
|
||||
<!--Note: Tag is bound to 'Items' so errors on the 'Items' prop will be included in Validation.Errors.-->
|
||||
<Setter Property="Tag">
|
||||
<Setter.Value>
|
||||
<MultiBinding>
|
||||
<MultiBinding.Converter>
|
||||
<cv:MultiValueConverterPipe/>
|
||||
</MultiBinding.Converter>
|
||||
<Binding Path="Name.legs"/>
|
||||
<Binding Path="Name.name"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<Grid
|
||||
c:DIYGrid.SyncWithColumnGeometry="True"
|
||||
u:StateManager.VisualState="{Binding IsChecked, ElementName=expander, Converter={x:Static pnl:PNLView.ExpandedVisualStateConverter}}"
|
||||
Background="{TemplateBinding Background}"
|
||||
>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ExpansionStates">
|
||||
<VisualState Name="Expanded"/>
|
||||
<VisualState Name="Collapsed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="presenter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Converter={StaticResource GridLengthPixelConverter}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="99" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Fill="Gray" UseLayoutRounding="True"/>
|
||||
<!--Expander Toggle-->
|
||||
<c:ExpanderToggleButton
|
||||
x:Name="expander"
|
||||
Grid.ColumnSpan="99"
|
||||
Validation.ValidationAdornerSiteFor="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ArrowPadding="14,2,0,0"
|
||||
Background="{u:Brush Transparent}"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
IsChecked="{Binding is_expanded}"
|
||||
>
|
||||
<u:StateManager.VisualState>
|
||||
<Binding Path="TemplatedParent.(Validation.Errors).Count" RelativeSource="{RelativeSource Self}">
|
||||
<Binding.Converter>
|
||||
<cv:ValueConverterPipe>
|
||||
<cv:IntGreaterThanConverter Value="0"/>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="NoErrors" ValueForTrue="Errors"/>
|
||||
</cv:ValueConverterPipe>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</u:StateManager.VisualState>
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:MouseStateDefinition MouseSource="{Binding Group.Target, RelativeSource={RelativeSource Self}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="MouseStates">
|
||||
<VisualState Name="Normal"/>
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="expander" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#10000000"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="expander" Storyboard.TargetProperty="Background.Color" BeginTime="0" Duration="0" To="#10000000"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Validation.ErrorTemplate>
|
||||
<ControlTemplate TargetType="Control">
|
||||
<Grid>
|
||||
<AdornedElementPlaceholder x:Name="adorned_element_pl"/>
|
||||
<Border x:Name="exclamation" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="6,0,0,0" VerticalAlignment="Center" Source="{x:Static u:Icons.WarningExclamation}"/>
|
||||
</Border>
|
||||
<ContentControl u:Behavior.MirrorLayoutFor="{Binding ElementName=exclamation}" ToolTip="{Binding AdornedElement.(Validation.ValidationAdornerSiteFor).(Validation.Errors), ElementName=adorned_element_pl}">
|
||||
<ContentControl.Template>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Content="{Binding ToolTip, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ToolTip.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}" DisplayMemberPath="ErrorContent"/>
|
||||
</DataTemplate>
|
||||
</ToolTip.ContentTemplate>
|
||||
</ToolTip>
|
||||
</Grid.ToolTip>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ContentControl.Template>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Validation.ErrorTemplate>
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True" Margin="-22,0,0,0">
|
||||
<c:EditableTextBlock
|
||||
x:Name="PART_Name_EditableTextBlock"
|
||||
Grid.Column="0"
|
||||
Margin="22,0,0,0"
|
||||
EndEditOnLostKeyboardFocus="True"
|
||||
Foreground="{u:Brush Black}"
|
||||
Text="{Binding Name.name}"
|
||||
ToolTip="Click to Edit Name"
|
||||
UseLayoutRounding="True"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<c:EditableTextBlock.CommandBindings>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.BeginEditCommand}" Executed="{x:Static c:EditableTextBlock.BeginEditExecuted}"/>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.EndEditCommand}" Executed="{x:Static c:EditableTextBlock.EndEditExecuted}"/>
|
||||
<CommandBinding Command="{x:Static c:EditableTextBlock.CancelEditCommand}" Executed="{x:Static c:EditableTextBlock.CancelEditExecuted}"/>
|
||||
</c:EditableTextBlock.CommandBindings>
|
||||
<c:EditableTextBlock.InputBindings>
|
||||
<MouseBinding MouseAction="LeftClick" Command="{x:Static c:EditableTextBlock.BeginEditCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:EditableTextBlock.EndEditCommand}"/>
|
||||
<KeyBinding Key="Esc" Command="{x:Static c:EditableTextBlock.CancelEditCommand}"/>
|
||||
</c:EditableTextBlock.InputBindings>
|
||||
</c:EditableTextBlock>
|
||||
<c:TickFactorsPicker
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Ticks}"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
SelectedTickFactors="{Binding Name.tick_factors}"
|
||||
UseLayoutRounding="True"
|
||||
/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Security}" Text="{Binding security_desc}" Style="{StaticResource GroupDesc}"/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Quantity}" Text="{Binding quantity_desc}" Style="{StaticResource GroupDesc}"/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.PriceMultiplier}" Text="{Binding price_multiplier_desc}" Style="{StaticResource GroupDesc}"/>
|
||||
<TextBlock c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.LegType}" Text="{Binding leg_type_desc}" Style="{StaticResource GroupDesc}">
|
||||
<TextBlock.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding leg_type_desc, Mode=OneWay}"/>
|
||||
<LineBreak/>
|
||||
<Run Text="Q = Quote"/>
|
||||
<LineBreak/>
|
||||
<Run Text="H = Hedge"/>
|
||||
<LineBreak/>
|
||||
<Run Text="N = None"/>
|
||||
</TextBlock>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</c:ExpanderToggleButton>
|
||||
<ItemsPresenter x:Name="presenter" Grid.Row="1" Grid.ColumnSpan="99"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
</GroupStyle>
|
||||
</c:DIYGrid.GroupStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate DataType="{x:Type spr:ContractLegVM}">
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True">
|
||||
<c:ContractPickerPopup
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Security}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security, Converter={x:Static cv:ValueConverters.SecuritySelectorText}}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=c:DIYGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding security, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding SelectedSecurity, RelativeSource={RelativeSource Self}}"
|
||||
>
|
||||
</c:ContractPickerPopup>
|
||||
<c:TickUpDownDouble
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.Quantity}"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
Value="{Binding quantity}"
|
||||
/>
|
||||
<c:TickUpDownDouble
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.PriceMultiplier}"
|
||||
Margin="{x:Static spr:SyntheticImportView.CellPadding}"
|
||||
Value="{Binding price_multiplier}"
|
||||
/>
|
||||
<spr:ContractLegTypeView
|
||||
c:DIYGrid.ColumnKey="{x:Static spr:SyntheticImportColumns.LegType}"
|
||||
Value="{Binding leg_type}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,50 @@
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.utility;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
enum SyntheticImportColumns {
|
||||
Name = 1,
|
||||
Security,
|
||||
Ticks,
|
||||
PriceMultiplier,
|
||||
Quantity,
|
||||
LegType
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for SyntheticImportView.xaml
|
||||
/// </summary>
|
||||
partial class SyntheticImportView : UserControl {
|
||||
public static double RowHeight =>
|
||||
27.0.RoundLayoutValue();
|
||||
|
||||
public static Thickness CellPadding =>
|
||||
new Thickness(6, 3, 6, 3).RoundToLayout();
|
||||
|
||||
public SyntheticImportView() =>
|
||||
InitializeComponent();
|
||||
|
||||
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (!e.Handled && e.ClickCount == 2 && sender is FrameworkElement el && el.DataContext is SyntheticImportRowCollectionViewGroup group) {
|
||||
group.is_expanded = !group.is_expanded;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SyntheticImportColumn : DIYGrid.ColumnBase<SyntheticImportColumns> {
|
||||
public SyntheticImportColumn() =>
|
||||
is_visible = true;
|
||||
|
||||
protected override void on_key_initialized(SyntheticImportColumns key) {
|
||||
if (Header == null)
|
||||
Header = EnumHelper.get_display_name(key);
|
||||
if (id == null)
|
||||
id = key.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.shell.spreader;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class SyntheticImporter {
|
||||
const string _import_title = "Import Synthetics";
|
||||
static readonly TimeSpan _import_op_sec_map_timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
public static IDisposable import_from_file(string path, IBus bus, OrdersModel orders_model) {
|
||||
bool is_disposed = false;
|
||||
IDisposable dispose_op = null;
|
||||
dispose_op = Disposable.Create(() => is_disposed = true);
|
||||
impl();
|
||||
return Disposable.Create(() => Disposable.dispose(ref dispose_op));
|
||||
async void impl() {
|
||||
IReadOnlyDictionary<uint, SecurityMD> sec_md_map = null;
|
||||
while (true) {
|
||||
await Task.WhenAny(init_sec_md_map(), Task.Delay(_import_op_sec_map_timeout));
|
||||
if (is_disposed)
|
||||
return;
|
||||
if (sec_md_map != null)
|
||||
break;
|
||||
if (!retry_on_error($"Synthetic import op timed out waiting for security map after {_import_op_sec_map_timeout.TotalSeconds:0.00} seconds."))
|
||||
return;
|
||||
}
|
||||
// The following conditions should break the while loop:
|
||||
// 1) the import succeeds
|
||||
// 2) the import fails, and the user does not click 'Yes' in the 'try again...' confirmation dialog
|
||||
while (true) {
|
||||
string error_msg = null;
|
||||
try {
|
||||
var raw_lines = File.ReadAllLines(path);
|
||||
var used_names = new HashSet<string>(sec_md_map.Values.Where(smd => smd.security.ContractCase == Security.ContractOneofCase.Synthetic)
|
||||
.Select(smd => smd.security.Synthetic.Name));
|
||||
var parsed_synths = SyntheticSecurityParser.parse_rows(raw_lines, MappedDictionary.create(sec_md_map, smd => smd.security), used_names).ToList();
|
||||
var main_vm = new SyntheticImportVM() {
|
||||
security_map = sec_md_map
|
||||
};
|
||||
// add rows/conflicts
|
||||
main_vm.rows.AddRange(parsed_synths.Select(ps => {
|
||||
var sec = ps.result;
|
||||
var r = new SyntheticImportRowVM(orders_model.admin_model.user.id, used_names.Contains) {
|
||||
tick_factors = TickUtils.get_regional_ticks(sec.ContractSpec).low_tick,
|
||||
name = sec.Synthetic.Name,
|
||||
parsed_synth = ps
|
||||
};
|
||||
r.legs.AddRange(ps.legs.Zip(sec.Synthetic.Legs).Select(tup => {
|
||||
var (pl, l) = tup;
|
||||
var leg_vm = new ContractLegVM {
|
||||
price_multiplier = l.PriceMultiplier,
|
||||
quantity = l.Quantity,
|
||||
parsed_leg = pl,
|
||||
owner = r,
|
||||
leg_type =
|
||||
(pl.result_tuple.leg_params.EnableHedging ? ContractLegType.Hedge : ContractLegType.None) |
|
||||
(pl.result_tuple.leg_params.IsActiveQuoting ? ContractLegType.Quote : ContractLegType.None)
|
||||
};
|
||||
if (l.Security != null)
|
||||
leg_vm.security = new SecurityImmutable(l.Security);
|
||||
return leg_vm;
|
||||
}));
|
||||
return r;
|
||||
}));
|
||||
var main_win = new SyntheticImportDialog { DataContext = main_vm };
|
||||
ui.WindowStyle.SetSizeToContent(main_win, SizeToContent.WidthAndHeight);
|
||||
main_vm.import_request += on_main_import_request;
|
||||
main_win.move_to_mouse(-200, -75);
|
||||
dispose_op = WindowUtilities.connect_vm(main_win, main_vm, main_vm);
|
||||
main_win.Show();
|
||||
// conflicts
|
||||
var conflicts = SyntheticSecurityParser.get_conflicts(main_vm.rows);
|
||||
if (conflicts.Values.Any(c => c.Count > 0)) {
|
||||
var conflicts_vm = new SyntheticImportConflictsVM(parsed_synths.Count) {
|
||||
security_map = sec_md_map
|
||||
};
|
||||
foreach (var kv in conflicts) {
|
||||
var assign_del = SyntheticSecurityParser.assign_component(kv.Key);
|
||||
foreach (var text_groups in kv.Value) {
|
||||
var eval = get_error_value(kv.Key);
|
||||
var row = new SyntheticImportConflictRowVM(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
|
||||
conflicts_vm.conflicts.Add(row);
|
||||
conflicts_vm.committed += () => {
|
||||
foreach (var (synth, leg_vm) in text_groups.Value) {
|
||||
var leg_tup = leg_vm?.parsed_leg?.result_tuple;
|
||||
assign_del(synth.parsed_synth.result, leg_tup?.leg, leg_tup?.leg_params, row.value);
|
||||
}
|
||||
row.on_conflicts_resolved();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var conflicts_win = new SyntheticImportConflictsDialog {
|
||||
DataContext = conflicts_vm,
|
||||
Owner = main_win,
|
||||
Title = $"{_import_title} Conflicts",
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
||||
};
|
||||
dispose_op = new DisposableCombo(WindowUtilities.connect_vm(conflicts_win, conflicts_vm, conflicts_vm)) + dispose_op;
|
||||
|
||||
conflicts_win.ShowDialog();
|
||||
}
|
||||
|
||||
object get_error_value(SyntheticContractComponent component) {
|
||||
switch (component) {
|
||||
case SyntheticContractComponent.PriceMultiplier:
|
||||
case SyntheticContractComponent.Quantity: return double.NaN;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
void on_main_import_request() {
|
||||
if (!is_disposed)
|
||||
import_synths(parsed_synths, main_vm.rows);
|
||||
Disposable.dispose(ref dispose_op);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
error_msg = $"Error parsing synthetics from file '{path}': {ex}.";
|
||||
}
|
||||
if (error_msg == null) {
|
||||
// Exit loop: the import succeeded.
|
||||
break;
|
||||
}
|
||||
if (!retry_on_error(error_msg)) {
|
||||
// Exit loop: the import failed, and the user did not click 'Yes' in the 'try again...' confirmation dialog.
|
||||
break;
|
||||
}
|
||||
// Continue loop: the import failed, and the user wants to try again.
|
||||
error_msg = null;
|
||||
}
|
||||
async void import_synths(IReadOnlyList<ParsedSyntheticContract> parsed_synths, IEnumerable<SyntheticImportRowVM> rows) {
|
||||
(uint? key, string error_msg)[] new_ids = null;
|
||||
string error_msg = null;
|
||||
try {
|
||||
new_ids = await orders_model.spreader.create_synthetics(
|
||||
rows.Where(r => !r.HasErrors)
|
||||
.Select(r => r.result_tuple));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.Ui.error($"Failed to save all synthetics during import op (one or more may have been successfully saved): {ex}.");
|
||||
error_msg = $"Import was unsuccessful: {ex}.";
|
||||
}
|
||||
if (new_ids == null)
|
||||
error_msg = "An unknown error occurred (check the synthetic management window to see if any synthetics were successfully added).";
|
||||
if (error_msg != null) {
|
||||
BTSMessageBox.Show(error_msg, "Import Synthetics", BTSMessageBoxButtons.OK);
|
||||
} else {
|
||||
Log.assert(new_ids != null);
|
||||
var failed = new_ids
|
||||
.Where(r => !r.error_msg.is_null_or_empty())
|
||||
.ToList();
|
||||
int num_succeeded = new_ids.Length - failed.Count;
|
||||
if (num_succeeded < parsed_synths.Count) {
|
||||
if (!BTSMessageBox.Confirm(
|
||||
$"{num_succeeded}/{parsed_synths.Count} synthetics were successfully imported.\n" +
|
||||
"Would you like to keep the imports that were successful?",
|
||||
_import_title)) {
|
||||
bus.post(SparkTags.SyntheticDeleteTag,
|
||||
ImmutableHashSet.CreateRange(new_ids.Where(r => r.key.HasValue).Select(r => r.key.Value)));
|
||||
}
|
||||
} else {
|
||||
BTSMessageBox.Show(
|
||||
$"All {num_succeeded} synthetics were successfully imported.",
|
||||
_import_title,
|
||||
BTSMessageBoxButtons.OK,
|
||||
MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task init_sec_md_map() =>
|
||||
sec_md_map = await bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().FirstOrDefaultAsync();
|
||||
|
||||
bool retry_on_error(string error_msg) {
|
||||
Log.Ui.warn(error_msg);
|
||||
return BTSMessageBox.Confirm(error_msg + "\nTry again?", _import_title, MessageBoxImage.Exclamation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.shell.spreader {
|
||||
[Flags]
|
||||
enum ContractLegType {
|
||||
None = 0,
|
||||
Quote = 1,
|
||||
Hedge = 2
|
||||
}
|
||||
|
||||
class ParsedSyntheticContract : IDisposable {
|
||||
[ThreadStatic]
|
||||
static int _id_counter;
|
||||
|
||||
public readonly int unpersisted_id = unchecked(_id_counter++);
|
||||
public readonly Security result = new Security {
|
||||
Synthetic = new SyntheticContract(),
|
||||
ContractSpec = new ContractSpecification(),
|
||||
Exchange = Venue.Bts
|
||||
};
|
||||
|
||||
readonly ReactiveThresholdMonitor<ParsedContractLeg> _legs_valid_mon =
|
||||
new ReactiveThresholdMonitor<ParsedContractLeg>(l => l.subscribe_is_valid());
|
||||
IDisposable _dispose;
|
||||
|
||||
public ParsedSyntheticContract() {
|
||||
_dispose = Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(legs, _legs_valid_mon.join),
|
||||
_legs_valid_mon.Subscribe(_ => _is_dirty = true));
|
||||
}
|
||||
|
||||
bool
|
||||
_is_dirty,
|
||||
_is_valid;
|
||||
|
||||
(string text, bool success)
|
||||
_name,
|
||||
_tick_size;
|
||||
|
||||
public (string text, bool success) name { get => _name; set => _set(ref _name, value); }
|
||||
public (string text, bool success) tick_size { get => _tick_size; set => _set(ref _tick_size, value); }
|
||||
public ObservableCollection<ParsedContractLeg> legs { get; } = new ObservableCollection<ParsedContractLeg>();
|
||||
|
||||
public bool is_valid {
|
||||
get {
|
||||
if (_is_dirty) {
|
||||
_is_valid = tick_size.success && name.success && !_legs_valid_mon.is_above_threshold;
|
||||
_is_dirty = false;
|
||||
}
|
||||
return _is_valid;
|
||||
}
|
||||
}
|
||||
|
||||
public ParsedContractLeg current_leg =>
|
||||
legs.LastOrDefault() ?? add_new_leg();
|
||||
|
||||
public ParsedContractLeg add_new_leg() {
|
||||
var leg = new ParsedContractLeg();
|
||||
legs.Add(leg);
|
||||
result.Synthetic.Legs.Add(leg.result);
|
||||
return leg;
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
public bool Equals(ParsedSyntheticContract other) => other != null && unpersisted_id == other.unpersisted_id;
|
||||
public override bool Equals(object obj) => Equals(obj as ParsedSyntheticContract);
|
||||
public override int GetHashCode() => unpersisted_id.GetHashCode();
|
||||
|
||||
void _set<T>(ref T backing, T value) {
|
||||
backing = value;
|
||||
_is_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
class ParsedContractLeg {
|
||||
[ThreadStatic]
|
||||
static int _id_counter;
|
||||
|
||||
public readonly ContractLeg result = new ContractLeg { };
|
||||
public readonly LegSpreaderParams leg_params = new LegSpreaderParams();
|
||||
bool
|
||||
_is_dirty,
|
||||
_is_valid;
|
||||
|
||||
(string text, bool success)
|
||||
_security,
|
||||
_quantity,
|
||||
_price_multiplier,
|
||||
_leg_type;
|
||||
|
||||
public event Action invalidated;
|
||||
|
||||
public int id { get; } = unchecked(_id_counter++);
|
||||
public (string text, bool success) security { get => _security; set => _set(ref _security, value); }
|
||||
public (string text, bool success) quantity { get => _quantity; set => _set(ref _quantity, value); }
|
||||
public (string text, bool success) price_multiplier { get => _price_multiplier; set => _set(ref _price_multiplier, value); }
|
||||
public (string text, bool success) leg_type { get => _leg_type; set => _set(ref _leg_type, value); }
|
||||
|
||||
public (ContractLeg leg, LegSpreaderParams leg_params) result_tuple => (result, leg_params);
|
||||
|
||||
public bool is_valid {
|
||||
get {
|
||||
if (_is_dirty) {
|
||||
if (_is_valid != (security.success && quantity.success && price_multiplier.success && leg_type.success)) {
|
||||
_is_valid = !_is_valid;
|
||||
invalidated?.Invoke();
|
||||
}
|
||||
_is_dirty = false;
|
||||
}
|
||||
return _is_valid;
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<bool> subscribe_is_valid() =>
|
||||
Observable.Create<bool>(o => {
|
||||
o.OnNext(is_valid);
|
||||
invalidated += on_invalidated;
|
||||
return Disposable.Create(() => invalidated -= on_invalidated);
|
||||
void on_invalidated() =>
|
||||
o.OnNext(is_valid);
|
||||
});
|
||||
|
||||
void _set<T>(ref T backing, T value) {
|
||||
backing = value;
|
||||
_is_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
static class SyntheticSecurityParser {
|
||||
public const string file_extension = "csv";
|
||||
|
||||
public static string display_name(this ContractLegType leg_type) {
|
||||
if (leg_type == ContractLegType.None)
|
||||
return "N";
|
||||
var flags = EnumHelper.split_flags(leg_type);
|
||||
return string.Join("", flags.Select(f => f.ToString())
|
||||
.Where(str => str.Length > 0)
|
||||
.Select(str => str.Substring(0, 1)));
|
||||
}
|
||||
|
||||
public static Action<Security, ContractLeg, LegSpreaderParams, object> assign_component(SyntheticContractComponent component) {
|
||||
switch (component) {
|
||||
case SyntheticContractComponent.Name: return (sec, leg, leg_params, val) => sec.Synthetic.Name = val?.ToString() ?? string.Empty;
|
||||
case SyntheticContractComponent.TickSize: return (sec, leg, leg_params, val) => sec.ContractSpec.LowTick = sec.ContractSpec.HighTick = ConvertUtils.try_convert<double?>(val) ?? 0.0;
|
||||
case SyntheticContractComponent.PriceMultiplier: return (sec, leg, leg_params, val) => leg.PriceMultiplier = ConvertUtils.try_convert<double?>(val) ?? double.NaN;
|
||||
case SyntheticContractComponent.Quantity: return (sec, leg, leg_params, val) => leg.Quantity = ConvertUtils.try_convert<double?>(val) ?? double.NaN;
|
||||
case SyntheticContractComponent.Security: return (sec, leg, leg_params, val) => leg.Security = val as Security ?? (val is SecurityImmutable si ? si.CloneToMutable() : null);
|
||||
case SyntheticContractComponent.LegType: return (sec, leg, leg_params, val) => assign_leg_type(leg_params, ConvertUtils.try_convert(val, out ContractLegType lt) ? lt : ContractLegType.None);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// Note: the returned dictionary will have entries for each <see cref="SyntheticContractComponent"/>, regardless of whether or not that component has any conflicts.
|
||||
public static Dictionary<SyntheticContractComponent, Dictionary<string, List<(ParsedSyntheticContract synth, ParsedContractLeg leg)>>> get_conflicts(IEnumerable<ParsedSyntheticContract> parsed_fills) {
|
||||
var missing_components = EnumHelper.get_values<SyntheticContractComponent>().ToDictionary(c => c, c => new Dictionary<string, List<(ParsedSyntheticContract synth, ParsedContractLeg leg)>>());
|
||||
var mk_list = new Func<string, List<(ParsedSyntheticContract synth, ParsedContractLeg leg)>>(_ => new List<(ParsedSyntheticContract, ParsedContractLeg)>());
|
||||
foreach (var psc in parsed_fills) {
|
||||
if (!psc.name.success) {
|
||||
add_missing(SyntheticContractComponent.Name, psc.name.text, psc);
|
||||
}
|
||||
if (!psc.tick_size.success) {
|
||||
add_missing(SyntheticContractComponent.TickSize, psc.tick_size.text, psc);
|
||||
}
|
||||
foreach (var leg in psc.legs) {
|
||||
if (!leg.price_multiplier.success)
|
||||
add_missing(SyntheticContractComponent.PriceMultiplier, leg.price_multiplier.text, psc, leg);
|
||||
if (!leg.quantity.success)
|
||||
add_missing(SyntheticContractComponent.Quantity, leg.quantity.text, psc, leg);
|
||||
if (!leg.security.success)
|
||||
add_missing(SyntheticContractComponent.Security, leg.security.text, psc, leg);
|
||||
if (!leg.leg_type.success)
|
||||
add_missing(SyntheticContractComponent.LegType, leg.leg_type.text, psc, leg);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(SyntheticContractComponent component, string text, ParsedSyntheticContract result, ParsedContractLeg leg = null) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add((result, leg));
|
||||
}
|
||||
|
||||
public static Dictionary<SyntheticContractComponent, Dictionary<string, List<(SyntheticImportRowVM row, ContractLegVM leg)>>> get_conflicts(IEnumerable<SyntheticImportRowVM> import_rows) {
|
||||
var missing_components = EnumHelper.get_values<SyntheticContractComponent>().ToDictionary(c => c, c => new Dictionary<string, List<(SyntheticImportRowVM row, ContractLegVM leg)>>());
|
||||
var mk_list = new Func<string, List<(SyntheticImportRowVM row, ContractLegVM leg)>>(_ => new List<(SyntheticImportRowVM, ContractLegVM)>());
|
||||
foreach (var ir in import_rows) {
|
||||
var psc = ir.parsed_synth;
|
||||
if (!psc.name.success) {
|
||||
add_missing(SyntheticContractComponent.Name, psc.name.text, ir);
|
||||
}
|
||||
if (!psc.tick_size.success) {
|
||||
add_missing(SyntheticContractComponent.TickSize, psc.tick_size.text, ir);
|
||||
}
|
||||
foreach (var leg in ir.legs) {
|
||||
if (!leg.parsed_leg.price_multiplier.success)
|
||||
add_missing(SyntheticContractComponent.PriceMultiplier, leg.parsed_leg.price_multiplier.text, ir, leg);
|
||||
if (!leg.parsed_leg.quantity.success)
|
||||
add_missing(SyntheticContractComponent.Quantity, leg.parsed_leg.quantity.text, ir, leg);
|
||||
if (!leg.parsed_leg.security.success)
|
||||
add_missing(SyntheticContractComponent.Security, leg.parsed_leg.security.text, ir, leg);
|
||||
if (!leg.parsed_leg.leg_type.success)
|
||||
add_missing(SyntheticContractComponent.LegType, leg.parsed_leg.leg_type.text, ir, leg);
|
||||
}
|
||||
}
|
||||
return missing_components;
|
||||
void add_missing(SyntheticContractComponent component, string text, SyntheticImportRowVM row, ContractLegVM leg = null) =>
|
||||
missing_components[component].GetOrAdd(text ?? string.Empty, mk_list).Add((row, leg));
|
||||
}
|
||||
// This implementation prioritizes simplicity over speed.
|
||||
// The security desc lookup could be better optimized, but the speed at which fills are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedSyntheticContract> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, ISet<string> used_names) {
|
||||
var sec_lookup = security_map.ToLookup(kv => kv.Value.ToString());
|
||||
var name_col = Cells.name(used_names.Contains);
|
||||
var sec_col = Cells.contract_security(lookup_security);
|
||||
return RowParser.parse_rows(rows, columns(), Cells.delimiter);
|
||||
|
||||
Security lookup_security(string sec_text) =>
|
||||
sec_lookup.Contains(sec_text) ? sec_lookup[sec_text].First().Value.CloneToMutable() : null;
|
||||
|
||||
IEnumerable<RowParser.AssignColumnValue<ParsedSyntheticContract>> columns() {
|
||||
yield return name_col;
|
||||
yield return Cells.tick_size;
|
||||
// if an imported synth has > 255 legs, assume it is an error.
|
||||
const int max_legs = 255;
|
||||
int leg_count = 0;
|
||||
do {
|
||||
yield return sec_col;
|
||||
yield return Cells.contract_quantity;
|
||||
yield return Cells.contract_price_multiplier;
|
||||
yield return Cells.leg_type;
|
||||
} while (leg_count++ < max_legs);
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
|
||||
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
|
||||
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
|
||||
}
|
||||
|
||||
public static void assign_leg_type(LegSpreaderParams leg_params, ContractLegType leg_type) {
|
||||
leg_params.IsActiveQuoting = (leg_type & ContractLegType.Quote) != 0;
|
||||
leg_params.EnableHedging = (leg_type & ContractLegType.Hedge) != 0;
|
||||
}
|
||||
|
||||
static class Cells {
|
||||
public const char delimiter = ',';
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedSyntheticContract> name(Func<string, bool> name_exists) =>
|
||||
(ref ParsedSyntheticContract psc, string cell_text) => {
|
||||
psc.name = (cell_text, !name_exists(cell_text));
|
||||
psc.result.Synthetic.Name = cell_text;
|
||||
};
|
||||
|
||||
public static void tick_size(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.tick_size = (cell_text, double.TryParse(cell_text.Trim(), out var val));
|
||||
if (psc.tick_size.success) {
|
||||
psc.tick_size = (cell_text, val.IsNonZero());
|
||||
psc.result.ContractSpec.LowTick = psc.result.ContractSpec.HighTick = val;
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedSyntheticContract> contract_security(Func<string, Security> reverse_security_lookup) =>
|
||||
(ref ParsedSyntheticContract psc, string cell_text) => {
|
||||
var leg = psc.add_new_leg();
|
||||
leg.result.Security = reverse_security_lookup(cell_text.Trim());
|
||||
leg.security = (cell_text, leg.result.Security != null);
|
||||
};
|
||||
|
||||
public static void contract_quantity(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.current_leg.quantity = (cell_text, double.TryParse(cell_text.Trim(), out var val) && val.IsNonZero());
|
||||
psc.current_leg.result.Quantity = val;
|
||||
}
|
||||
|
||||
public static void contract_price_multiplier(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.current_leg.price_multiplier = (cell_text, double.TryParse(cell_text.Trim(), out var val) && val.IsNonZero());
|
||||
psc.current_leg.result.PriceMultiplier = val;
|
||||
}
|
||||
|
||||
public static void leg_type(ref ParsedSyntheticContract psc, string cell_text) {
|
||||
psc.current_leg.leg_type = (cell_text, _parse_leg_type(cell_text.Trim(), out var leg_type));
|
||||
assign_leg_type(psc.current_leg.leg_params, leg_type);
|
||||
}
|
||||
|
||||
static bool _parse_leg_type(string cell_text, out ContractLegType result) {
|
||||
result = ContractLegType.None;
|
||||
if (cell_text.IsNullOrEmpty())
|
||||
return false;
|
||||
switch (char.ToUpper(cell_text[0])) {
|
||||
case 'Q': result = ContractLegType.Quote; break;
|
||||
case 'H': result = ContractLegType.Hedge; break;
|
||||
case 'N': break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,52 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
class LegSpreaderParamsTuple : ViewModelBase, IEquatable<LegSpreaderParamsTuple> {
|
||||
[ThreadStatic]
|
||||
static int _id_counter;
|
||||
|
||||
SecurityImmutable? _security;
|
||||
|
||||
namespace mbts.ui.spark
|
||||
{
|
||||
internal class LegSpreaderParamsTuple
|
||||
{
|
||||
public LegSpreaderParamsTuple(ContractLeg leg, LegSpreaderParams leg_params) {
|
||||
id = unchecked(_id_counter++);
|
||||
this.leg = leg;
|
||||
this.leg_params = leg_params;
|
||||
if (leg.Security != null)
|
||||
_security = new SecurityImmutable(leg.Security);
|
||||
}
|
||||
|
||||
public event Action security_changed;
|
||||
|
||||
public int id { get; }
|
||||
/// <summary>
|
||||
/// Important: do NOT modify security via <see cref="ContractLeg.Security"/>,
|
||||
/// use <see cref="security"/> instead ([only]the latter raises the PropertyChanged event).
|
||||
/// </summary>
|
||||
public ContractLeg leg { get; }
|
||||
public LegSpreaderParams leg_params { get; }
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
set {
|
||||
if (!_security.Equals(value)) {
|
||||
_security = value;
|
||||
leg.Security = value?.CloneToMutable();
|
||||
security_changed?.Invoke();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public uint SecurityKey => leg.Security.Id;
|
||||
|
||||
public bool Equals(LegSpreaderParamsTuple other) =>
|
||||
ReferenceEquals(this, other);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LegSpreaderParamsTuple);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -4,6 +4,8 @@ using bts.api.core;
|
||||
using bts.api.synthetic;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
@@ -82,6 +84,61 @@ namespace mbts.spark {
|
||||
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
|
||||
"lookup strategy");
|
||||
|
||||
public Task<(uint? key, string error_msg)[]> create_synthetics(IEnumerable<(SecurityImmutable security, ExecutionStrategyImmutable strategy)> synthetics) {
|
||||
var tasks = synthetics.Select(tup => {
|
||||
var (sec, strat) = tup;
|
||||
if (sec.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return Task.FromException<(Result<CreateResult>, Result<UpdateResult>?)>(ExceptionBuilder.Argument.unexpected_case(sec.ContractCase, nameof(sec)));
|
||||
if (sec.Synthetic.UserId != _user_id) {
|
||||
var msec = sec.CloneToMutable();
|
||||
msec.Synthetic.UserId = _user_id;
|
||||
sec = new SecurityImmutable(msec);
|
||||
}
|
||||
return impl();
|
||||
async Task<(Result<CreateResult> sec, Result<UpdateResult>? strat)> impl() {
|
||||
var result = await _service.create_synthetic_async(sec);
|
||||
Result<UpdateResult>? strategy;
|
||||
if (result.success && result.value.Error == CreateResult.Types.Error.Ok) {
|
||||
strategy = await _service.update_security_metadata_async(new Metadata {
|
||||
SecurityKey = result.value.Key,
|
||||
Strategy = tup.strategy.CloneToMutable()
|
||||
});
|
||||
} else {
|
||||
strategy = null;
|
||||
}
|
||||
return (result, strategy);
|
||||
}
|
||||
});
|
||||
return _error.safe_execute_batch(tasks,
|
||||
tup => {
|
||||
var (sec, strat) = tup;
|
||||
// security
|
||||
string sec_error_msg = null;
|
||||
uint? key = null;
|
||||
if (!sec.success) {
|
||||
sec_error_msg = sec.error_message;
|
||||
} else if (sec.value.Error != CreateResult.Types.Error.Ok) {
|
||||
sec_error_msg = sec.value.Error.ToString();
|
||||
} else {
|
||||
key = sec.value.Key;
|
||||
}
|
||||
// strategy
|
||||
string strat_error_msg = null;
|
||||
if (!strat.HasValue) {
|
||||
strat_error_msg = "Strategy was not successfully saved";
|
||||
} else if (!strat.Value.success) {
|
||||
strat_error_msg = strat.Value.error_message;
|
||||
} else if (strat.Value.value.Error != UpdateResult.Types.Error.Ok) {
|
||||
strat_error_msg = strat.Value.value.Error.ToString();
|
||||
}
|
||||
// merge error msgs (result should be null if both errors are empty)
|
||||
var error_msg = string.Join(Environment.NewLine, new[] { sec_error_msg, strat_error_msg }.ExceptNull());
|
||||
return (key, error_msg.NullIfEmpty());
|
||||
},
|
||||
"Synthetic",
|
||||
"creating synthetics");
|
||||
}
|
||||
|
||||
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
|
||||
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
|
||||
return null;
|
||||
@@ -106,10 +163,32 @@ namespace mbts.spark {
|
||||
(r, success) => success,
|
||||
"saving strategy");
|
||||
|
||||
public async Task<string[]> save_strategies(IEnumerable<(uint key, ExecutionStrategyImmutable strategy)> items) {
|
||||
// Copy everything to a new list, to guarentee that it won't be altered during the op.
|
||||
// The performance impact should be insignificant, as this should be a rare call.
|
||||
var item_list = new List<(uint key, ExecutionStrategyImmutable strategy, int index)>(items.Select((item, index) => (item.key, item.strategy, index)));
|
||||
string[] errors = new string[item_list.Count];
|
||||
await Task.WhenAll(item_list.Select(impl));
|
||||
return errors;
|
||||
async Task impl((uint key, ExecutionStrategyImmutable strategy, int index) tup) {
|
||||
var (key, strategy, index) = tup;
|
||||
try {
|
||||
var result = await _service.update_security_metadata_async(new Metadata {
|
||||
SecurityKey = key,
|
||||
Strategy = strategy.CloneToMutable()
|
||||
});
|
||||
if (!result.success)
|
||||
errors[index] = result.error_message;
|
||||
} catch (Exception ex) {
|
||||
errors[index] = ex.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> verify_name_is_unique(string name) {
|
||||
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
name = name.Trim().ToLower();
|
||||
return success && !result.Values.Any(st => st.md.security.Synthetic.Name.Trim().ToLower() == name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> { }
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using bts;
|
||||
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;
|
||||
@@ -18,16 +20,19 @@ using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
const string
|
||||
_window_type = "SyntheticManagement",
|
||||
_import_title = "Import Synthetics";
|
||||
static readonly TimeSpan _import_op_sec_map_timeout = TimeSpan.FromSeconds(30);
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
OrdersModel _orders_model;
|
||||
SyntheticManagementView _view;
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
IDisposable _subscriptions;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
|
||||
|
||||
public SyntheticManagementWindow(SyntheticManagementWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Synthetic Management", bus, factory, SparkCommands.ShowSyntheticManagement.icon) {
|
||||
_orders_model = orders_model;
|
||||
@@ -40,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
|
||||
@@ -50,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);
|
||||
@@ -90,7 +97,12 @@ namespace mbts.spark {
|
||||
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}))
|
||||
})),
|
||||
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(
|
||||
@@ -105,13 +117,16 @@ namespace mbts.spark {
|
||||
r == null ? Observable.Return(new RowStrategy?()) :
|
||||
_orders_model.spreader.security_strategy(r.security.Id).Select(strategy => RowStrategy.create(r.security, strategy));
|
||||
|
||||
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
|
||||
IImmutableSet<uint> select_security_id(RowModificationParams? rmp) => ImmutableHashSet.Create(rmp.HasValue ? rmp.Value.security.Id : 0);
|
||||
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
|
||||
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
|
||||
|
||||
_vm.value = new SyntheticManagementDialogVM(_bus, _orders_model, _settings_manager, _state.sorting_criteria, _state.filters ?? new FilterStates());
|
||||
if (_view == null) _view = new SyntheticManagementView { DataContext = _vm.value };
|
||||
else _view.DataContext = _vm.value;
|
||||
|
||||
void import(string path) =>
|
||||
SyntheticImporter.import_from_file(path, _bus, _orders_model);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
@@ -119,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;
|
||||
@@ -145,4 +163,4 @@ namespace mbts.spark {
|
||||
new RowStrategy(security, strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel : RowModelBase<uint> {
|
||||
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
|
||||
IDictionary<int, uint> _value_store = new Dictionary<int, uint>();
|
||||
|
||||
public override string display_name { get; } = "Cross Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_omv_cross(leg_tup) is PostHedgeOppositeMarketVolumeBasedForcedCross omv_cross)
|
||||
omv_cross.CrossTicks = value;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.OppositeMarketVolumeBasedForcedCross;
|
||||
|
||||
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.CrossTicks);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.CrossTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class PostHedgeOppositeMarketVolumeBasedCrossThresholdModel : RowModelBase<QuantityMeasure> {
|
||||
IDictionary<uint, QuantityMeasure> _value_store = new Dictionary<uint, QuantityMeasure>();
|
||||
IDictionary<int, QuantityMeasure> _value_store = new Dictionary<int, QuantityMeasure>();
|
||||
|
||||
public override string display_name { get; } = "Threshold";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark {
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, QuantityMeasure value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
_value_store[leg_tup.id] = value;
|
||||
if (_get_omv_cross(leg_tup) is PostHedgeOppositeMarketVolumeBasedForcedCross omv_cross)
|
||||
omv_cross.QuantityThreshold = value;
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace mbts.spark {
|
||||
leg_tup.leg_params.PostHedgeParams.OppositeMarketVolumeBasedForcedCross;
|
||||
|
||||
QuantityMeasure _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.QuantityThreshold);
|
||||
_value_store.GetValueOrDefault(leg_tup.id, _ => SyntheticDataHelper.Defaults.PostHedgeOppositeMarketVolumeBasedForcedCross.QuantityThreshold);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 959 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user