Compare commits
479
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f912f99bd | ||
|
|
ae7e1761a7 | ||
|
|
4891eea5ba | ||
|
|
bc36db0480 | ||
|
|
cdaba74c06 | ||
|
|
b045307f30 | ||
|
|
60a7b504db | ||
|
|
0f62f14a1d | ||
|
|
87030dbb88 | ||
|
|
e209ec59e4 | ||
|
|
92dbedf28f | ||
|
|
7993f75fb1 | ||
|
|
1cddd2bcf3 | ||
|
|
9f7e5846b8 | ||
|
|
f7b3aac11a | ||
|
|
ec32101cef | ||
|
|
4e0cf68fa3 | ||
|
|
4e9a7d576e | ||
|
|
5e056dfff4 | ||
|
|
4029ccf8e6 | ||
|
|
22911d582b | ||
|
|
6ef7a2ccb5 | ||
|
|
737a07cecb | ||
|
|
1cbea3d1ee | ||
|
|
4dbedfa1f7 | ||
|
|
074f87dd37 | ||
|
|
6ee0ca9589 | ||
|
|
5745c13355 | ||
|
|
5f99edde0e | ||
|
|
d39bc5c73c | ||
|
|
9cb6d5ab88 | ||
|
|
278432a35b | ||
|
|
13c414f301 | ||
|
|
34f5265a42 | ||
|
|
83103e6dc4 | ||
|
|
add631b04d | ||
|
|
e08dc5dfba | ||
|
|
6a8e995c9c | ||
|
|
d7c460ac98 | ||
|
|
7871dc5e4f | ||
|
|
1aab1db4ca | ||
|
|
373187c091 | ||
|
|
423d127ee9 | ||
|
|
14f306d50d | ||
|
|
71fe863f1b | ||
|
|
ac47f0246f | ||
|
|
8e43f96331 | ||
|
|
3187e4134f | ||
|
|
0331501ce4 | ||
|
|
bd0f1a6f84 | ||
|
|
8776b80b07 | ||
|
|
f5a72841b4 | ||
|
|
f9ada7fca2 | ||
|
|
1cb4f24610 | ||
|
|
021c88f26b | ||
|
|
014fa8e291 | ||
|
|
71d54224bb | ||
|
|
bebbf2a0f1 | ||
|
|
7413814e4a | ||
|
|
36474ef3ea | ||
|
|
974be037fe | ||
|
|
a9ee246436 | ||
|
|
b692742e30 | ||
|
|
c61cbba316 | ||
|
|
4d99ffc86c | ||
|
|
52001a55c6 | ||
|
|
570dd387d2 | ||
|
|
28a122aa7a | ||
|
|
b2facaecd9 | ||
|
|
e346858516 | ||
|
|
be8ca67be5 | ||
|
|
197e68e357 | ||
|
|
dfa3e4a38d | ||
|
|
0f7625ff4a | ||
|
|
88ab4fbb79 | ||
|
|
5cc9b8c875 | ||
|
|
187793d47c | ||
|
|
656c7104f6 | ||
|
|
359e01e8bd | ||
|
|
2a452d3201 | ||
|
|
806ed427ab | ||
|
|
78cefa187a | ||
|
|
8531b330dc | ||
|
|
54d9bde6b7 | ||
|
|
fe6dabb5fe | ||
|
|
f0b089d2e5 | ||
|
|
8979c595a5 | ||
|
|
ba4771aeee | ||
|
|
719c1e474b | ||
|
|
72123a61a9 | ||
|
|
3b1c2dc52b | ||
|
|
0546f0d9fb | ||
|
|
05f1be77ef | ||
|
|
79e876e1dc | ||
|
|
b6cc413733 | ||
|
|
a5e9f40f01 | ||
|
|
e40dc48fa8 | ||
|
|
5aab4312fc | ||
|
|
9ede4c7736 | ||
|
|
b63b0b6303 | ||
|
|
e004580d99 | ||
|
|
7403057c58 | ||
|
|
47730bd3d4 | ||
|
|
9964d2579a | ||
|
|
6960ee1a12 | ||
|
|
9edde6b8e3 | ||
|
|
8ef02ea4cb | ||
|
|
dc99ea6ea4 | ||
|
|
9c6993d70c | ||
|
|
c6be4dad17 | ||
|
|
bd2e2674bf | ||
|
|
71ddcd135b | ||
|
|
bffb7686a8 | ||
|
|
a73bf0ce7c | ||
|
|
94c5a1bcd1 | ||
|
|
ca2a346054 | ||
|
|
b990923cf5 | ||
|
|
8490151844 | ||
|
|
78c8234eef | ||
|
|
1f1f92d337 | ||
|
|
a6426e7a98 | ||
|
|
dc1a7b3f5d | ||
|
|
2a549e551f | ||
|
|
c158188557 | ||
|
|
17614b663e | ||
|
|
5e36a22aba | ||
|
|
442fbd2888 | ||
|
|
d1975cdd00 | ||
|
|
a2c75f08d8 | ||
|
|
02a867f8a4 | ||
|
|
adc48d7871 | ||
|
|
33bd089e74 | ||
|
|
edcb5a5a24 | ||
|
|
6f39705d4f | ||
|
|
07377b8cc9 | ||
|
|
77ad9ca011 | ||
|
|
10f24aa2e9 | ||
|
|
37fc5880ea | ||
|
|
910d991d92 | ||
|
|
ced8bd9fea | ||
|
|
c5004f46ee | ||
|
|
1deb5d25b0 | ||
|
|
b5950ab8c0 | ||
|
|
a233451333 | ||
|
|
deef8136df | ||
|
|
9dda440a48 | ||
|
|
f22442e665 | ||
|
|
1ba7ceaf6a | ||
|
|
54676d5aa9 | ||
|
|
edf5c78405 | ||
|
|
f621d08f72 | ||
|
|
724243c762 | ||
|
|
f70e8de497 | ||
|
|
c97e4be79e | ||
|
|
95be0015d6 | ||
|
|
cbe8917f28 | ||
|
|
653b1c7814 | ||
|
|
9a1098669e | ||
|
|
dcf6c69ef9 | ||
|
|
fdcd245ef5 | ||
|
|
cf9133da8d | ||
|
|
802501a337 | ||
|
|
b9c6f8d309 | ||
|
|
ee94f43628 | ||
|
|
ed06924df1 | ||
|
|
b78179ccee | ||
|
|
e0add4cc47 | ||
|
|
cd670c4bd1 | ||
|
|
acd637000c | ||
|
|
c09f03a922 | ||
|
|
f9fbf9c45a | ||
|
|
5a00c5eb2a | ||
|
|
74fa41f151 | ||
|
|
32d5fc2c37 | ||
|
|
1049046d0b | ||
|
|
1f8e0a1e2b | ||
|
|
846f6d80d8 | ||
|
|
9819a01e38 | ||
|
|
d0ad69e259 | ||
|
|
f0abd13c48 | ||
|
|
ea403b7138 | ||
|
|
4a94beaa83 | ||
|
|
3f03ceb2b5 | ||
|
|
0dd988fd1e | ||
|
|
93d0a21c9d | ||
|
|
92b3cd1da0 | ||
|
|
8fc42f6193 | ||
|
|
73d5b44a7f | ||
|
|
e47c4ead2a | ||
|
|
21dfe5ce43 | ||
|
|
e032dc822f | ||
|
|
87148b41f0 | ||
|
|
68cb21a0a9 | ||
|
|
093ce9a0c3 | ||
|
|
5003b23f18 | ||
|
|
0de0ddbf16 | ||
|
|
5d79c0c79a | ||
|
|
8e4d4782ce | ||
|
|
b8bd3944ce | ||
|
|
5d3d5251c7 | ||
|
|
f7cee30511 | ||
|
|
98690ee747 | ||
|
|
3e14d83dde | ||
|
|
1fc38234e5 | ||
|
|
746622161c | ||
|
|
c064cdd215 | ||
|
|
625d47d98e | ||
|
|
acce1e3f7d | ||
|
|
9bdf084838 | ||
|
|
5132f81bc1 | ||
|
|
ab797a311e | ||
|
|
d032b23841 | ||
|
|
8c68a66ef1 | ||
|
|
d617d1597c | ||
|
|
8c8f2d3179 | ||
|
|
669ccd5ffc | ||
|
|
12db326f1d | ||
|
|
625f76e1f4 | ||
|
|
55fc880b2e | ||
|
|
30ca30442a | ||
|
|
802160abf1 | ||
|
|
ccff3ee160 | ||
|
|
3da1bfaeb1 | ||
|
|
03eff1f388 | ||
|
|
b53825baa9 | ||
|
|
1b1c8a7c1d | ||
|
|
5206a378ff | ||
|
|
7d2047d7ce | ||
|
|
8a43a4d157 | ||
|
|
230e9e525f | ||
|
|
034318db5e | ||
|
|
0758dd6f46 | ||
|
|
83f9f3375c | ||
|
|
98a6616804 | ||
|
|
b46f811cb5 | ||
|
|
0065dba0f0 | ||
|
|
078b9d41b1 | ||
|
|
7cf37bb080 | ||
|
|
564cd3e273 | ||
|
|
6d918c0962 | ||
|
|
709229ac14 | ||
|
|
375786b283 | ||
|
|
9e5970c93e | ||
|
|
ab5fca73f6 | ||
|
|
840115db03 | ||
|
|
db7f74f85d | ||
|
|
3f381f248b | ||
|
|
b5a93e4466 | ||
|
|
7cd052979e | ||
|
|
3b3d44d6f6 | ||
|
|
1b56ff3327 | ||
|
|
1f823003d7 | ||
|
|
a64749cd07 | ||
|
|
3ac6e882d4 | ||
|
|
4c239f31f8 | ||
|
|
f9759b9533 | ||
|
|
2b2eaac979 | ||
|
|
5b5274ab5f | ||
|
|
56fc1019de | ||
|
|
abe7c25bf4 | ||
|
|
484ad656f9 | ||
|
|
0e92ee15f4 | ||
|
|
4dcaf224ef | ||
|
|
2d5211ac9b | ||
|
|
e62d116a44 | ||
|
|
8c492fdad3 | ||
|
|
0deb4fbcf4 | ||
|
|
b8b375eca5 | ||
|
|
4a42238cfb | ||
|
|
d4081420ae | ||
|
|
b811fb6a32 | ||
|
|
27d38cf443 | ||
|
|
4dad98a8b2 | ||
|
|
0f78cbc80e | ||
|
|
eb3067448d | ||
|
|
f4a3256cb8 | ||
|
|
a2b8d53ebb | ||
|
|
b7a3d5a23c | ||
|
|
4f4bfea70f | ||
|
|
76d6e37f59 | ||
|
|
8e535b0303 | ||
|
|
44887d80e6 | ||
|
|
bd63c0798e | ||
|
|
6e06b3566a | ||
|
|
ebc7432995 | ||
|
|
33a1762cb7 | ||
|
|
16e9d100d2 | ||
|
|
5009c4c29a | ||
|
|
f4f1bb7914 | ||
|
|
82d3e59024 | ||
|
|
6fe59386df | ||
|
|
767cb36856 | ||
|
|
908958ea3c | ||
|
|
8cef60b5ab | ||
|
|
bbf961ed78 | ||
|
|
f3b8ce8088 | ||
|
|
026bda165e | ||
|
|
9727f17edf | ||
|
|
61b2913a31 | ||
|
|
0a8bee2c31 | ||
|
|
2dd8e64272 | ||
|
|
aa704b8c96 | ||
|
|
8673b857a7 | ||
|
|
f83f2c7964 | ||
|
|
86d8d7dafb | ||
|
|
5363fdd0db | ||
|
|
d3e7870b9a | ||
|
|
f05692e441 | ||
|
|
053c460ff6 | ||
|
|
af7e9dd3a8 | ||
|
|
3e3b6acfd7 | ||
|
|
b75496e665 | ||
|
|
03864caa09 | ||
|
|
1558b79c16 | ||
|
|
57bfe5a85e | ||
|
|
3eaeaf0f35 | ||
|
|
ab3121cd5d | ||
|
|
ec5bd4a164 | ||
|
|
3877aec09a | ||
|
|
fb17596304 | ||
|
|
b36bcc66fb | ||
|
|
73189fbbd8 | ||
|
|
484a9dba12 | ||
|
|
83f2866604 | ||
|
|
37f40046da | ||
|
|
2401b9db77 | ||
|
|
a45546690c | ||
|
|
bc394ee3b5 | ||
|
|
b917fba19a | ||
|
|
63106eb866 | ||
|
|
fa79bddb6b | ||
|
|
83851c1494 | ||
|
|
373f7ee3a1 | ||
|
|
2ed8b9caf0 | ||
|
|
470eee431e | ||
|
|
e5e6723b3a | ||
|
|
71be28911b | ||
|
|
8ab3182c21 | ||
|
|
508e2d4fcf | ||
|
|
027e434ac9 | ||
|
|
e9be6d9859 | ||
|
|
82e63f691c | ||
|
|
cb83ad08a6 | ||
|
|
e2e78d91c3 | ||
|
|
427d03d340 | ||
|
|
32e5111c54 | ||
|
|
ef12f81b73 | ||
|
|
c0f255f5a7 | ||
|
|
f6861820e4 | ||
|
|
6a3960b33f | ||
|
|
e3858fd203 | ||
|
|
3f1d9307e9 | ||
|
|
5f7c2330f3 | ||
|
|
315b2a8b26 | ||
|
|
29ebfebcbc | ||
|
|
4fbaca6d05 | ||
|
|
4d4f706e7c | ||
|
|
241396e292 | ||
|
|
4e68c98cfb | ||
|
|
30c1dfd9a7 | ||
|
|
a64ca702c9 | ||
|
|
0a2b6ce17e | ||
|
|
660f203132 | ||
|
|
27f27dbcf1 | ||
|
|
1ac6c8cd40 | ||
|
|
a13cececa1 | ||
|
|
01d2e88097 | ||
|
|
8f96cd07e3 | ||
|
|
b515e45b23 | ||
|
|
a37e2ead87 | ||
|
|
92ae592d31 | ||
|
|
db1cbf589f | ||
|
|
a561ff3aae | ||
|
|
3eeab708d2 | ||
|
|
082056ea3f | ||
|
|
e9f3e92f82 | ||
|
|
152c79a4a9 | ||
|
|
db877c859b | ||
|
|
1ec2423ad2 | ||
|
|
1a88e52a08 | ||
|
|
a1b52d083d | ||
|
|
f364317cfa | ||
|
|
f474af1925 | ||
|
|
03ac5035fc | ||
|
|
e78d8208fa | ||
|
|
7f8c591f75 | ||
|
|
7d9685f995 | ||
|
|
699736ff52 | ||
|
|
8d0e7bc467 | ||
|
|
df784e9961 | ||
|
|
af39aab153 | ||
|
|
885c1ad9f8 | ||
|
|
91fdd6ec58 | ||
|
|
3adfd7056a | ||
|
|
5f845f1355 | ||
|
|
5d4bb1d421 | ||
|
|
9836e46dd9 | ||
|
|
bf9d5c2c6d | ||
|
|
ef1c3a7959 | ||
|
|
b07f6aba59 | ||
|
|
a7b3b0fa6d | ||
|
|
f7a3b3ebc1 | ||
|
|
d291e4a022 | ||
|
|
292e5575d0 | ||
|
|
1ad57dbf45 | ||
|
|
b25f5556af | ||
|
|
c10a4be411 | ||
|
|
de312c3023 | ||
|
|
93ff56c5cd | ||
|
|
5254b7367a | ||
|
|
6d2efb7dc9 | ||
|
|
50214741e7 | ||
|
|
56950b682d | ||
|
|
83b1bbf087 | ||
|
|
203998e201 | ||
|
|
59d88d3cc1 | ||
|
|
13f9717195 | ||
|
|
855e0d7ef9 | ||
|
|
27a1cb277c | ||
|
|
3107d5f382 | ||
|
|
e1940faeac | ||
|
|
a2f22482f6 | ||
|
|
ccea4571a6 | ||
|
|
3a72a4a82e | ||
|
|
ed6affb6b5 | ||
|
|
2fc208ef74 | ||
|
|
64f86db86b | ||
|
|
5e960479c7 | ||
|
|
837aa51adf | ||
|
|
d7a7b480ad | ||
|
|
d94afe9867 | ||
|
|
410e17aec5 | ||
|
|
a8bd99af56 | ||
|
|
e11b5928ce | ||
|
|
6bcf352813 | ||
|
|
eeec7b0076 | ||
|
|
bacdb1c4cd | ||
|
|
1e4ad28088 | ||
|
|
e013f93169 | ||
|
|
002c051c06 | ||
|
|
42fd7b3f40 | ||
|
|
f73d163312 | ||
|
|
ee124401b9 | ||
|
|
ec7417a9c6 | ||
|
|
536f4703ab | ||
|
|
5f848a36e2 | ||
|
|
a7c714591a | ||
|
|
4655a68593 | ||
|
|
9677d8996e | ||
|
|
3f0a9e442e | ||
|
|
f3387b57af | ||
|
|
abed4bbff6 | ||
|
|
bb90429ada | ||
|
|
1efbc2fd45 | ||
|
|
04027ed980 | ||
|
|
5a850c314c | ||
|
|
b566668d83 | ||
|
|
6679695931 | ||
|
|
23a8112dd3 | ||
|
|
b9a07a8e21 | ||
|
|
86deaf2edb | ||
|
|
fc347250e3 | ||
|
|
aae867a555 | ||
|
|
37a454cf51 | ||
|
|
5162b9f0c0 | ||
|
|
ad757495bd | ||
|
|
e473c18a30 | ||
|
|
640ec0b1ed | ||
|
|
c3e2622a22 | ||
|
|
7d05f76fe4 | ||
|
|
7a8a38f89d | ||
|
|
1e54cb1daf | ||
|
|
7b714018d2 | ||
|
|
c3371d0971 | ||
|
|
322e3d81ba | ||
|
|
4ada5a89f4 | ||
|
|
15aa016aa0 | ||
|
|
e77f3169de | ||
|
|
660c5d3a06 |
No files matched your search
@@ -21,3 +21,5 @@ src/client/sparkcmd/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
Vendored
+1
-1
@@ -37,7 +37,7 @@
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
|
||||
"args": ["md","GLBX","Future","ES","ESM8"],
|
||||
"args": ["md","GLBX","Future","ZN", "ZNU8"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
|
||||
+26
-6
@@ -32,10 +32,30 @@ if (WIN32)
|
||||
|
||||
add_subdirectory(src/lib)
|
||||
else ()
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} \
|
||||
-Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers --std=c++14 -fdiagnostics-color")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG -O3")
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/web)
|
||||
# so that sparkd looks for libspark.so in its directory
|
||||
set (CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set (CMAKE_INSTALL_RPATH ".")
|
||||
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} \
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++14 -fdiagnostics-color")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -DNDEBUG -O3")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/web)
|
||||
add_subdirectory(src/sdk)
|
||||
add_subdirectory(src/data_files)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/server/sparkd/sparkd ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/server/sparkd/defaults ${CMAKE_BINARY_DIR}/server
|
||||
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
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
endif()
|
||||
+1
-1
Submodule docker/linux updated: 6037d094b7...aedfd79e02.
+1
-1
Submodule externals updated: 281bd00a2d...433d413283.
@@ -0,0 +1,6 @@
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
those re-entered explicitly
|
||||
- With this release we add per user location and smp id tags. At least
|
||||
the location will have to be set through the admin UI for every user
|
||||
@@ -0,0 +1,16 @@
|
||||
1.1.6
|
||||
- Fixed occasional crash when filtering fills
|
||||
- Fixed several cash per point multipliers
|
||||
- Colored time of sales row with aggressor side
|
||||
- Iceberg orders now cancel (rathern than error) when a child is canceled
|
||||
|
||||
1.1.0
|
||||
- Added per user self match protection, location and self match protection FIX tag settings
|
||||
- Implemented a new "Trigger Order" algo
|
||||
- Corrected tick formatting for a number of products
|
||||
- Added the hability to recreate canceled orders
|
||||
- Added leg fills to facilitate futured based PnL
|
||||
- PnL report now has PnL columns calculated based on your start of day, current day, and total position
|
||||
- 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
@@ -4,7 +4,7 @@ VisualStudioVersion = 15.0.27130.2024
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spark_lib", "build\src\lib\spark_lib.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
|
||||
EndProject
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ VisualStudioVersion = 15.0.26730.10
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sparkd", "src\server\sparkd\sparkd.vcxproj", "{BBBAFD38-312D-4A8D-9AF0-F2BDEE72CE6F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spark_lib", "build\src\lib\spark_lib.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{502FD050-679A-3B50-B438-098500700919}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
<Message Importance="High" Text="Generating service %(Proto3.FileName)" />
|
||||
<Exec Command="
|
||||
set PATH=$(spark_build_python_path);%path% &
|
||||
set PYTHONPATH=$(Externals)\python &
|
||||
$(protoc) ^
|
||||
%(Proto3.FullPath) ^
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace bts.ui {
|
||||
public AboutDaemon(IBus bus, [Import("username")]string username, IBrowserWindowManager browser) {
|
||||
_user = username;
|
||||
_browser = browser;
|
||||
_dispose = bus.subscribe(SparkTags.About).ObserveOnDispatcher().Subscribe(_ => show_about());
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts {
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model) {
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
|
||||
.ObserveOnDispatcher()
|
||||
.OnDispatcher()
|
||||
.Subscribe(_delete_synthetic);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.spark
|
||||
@@ -22,18 +22,21 @@ namespace bts.spark
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_dispose = bus.subscribe(SparkTags.Support).ObserveOnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
|
||||
public async Task report_error(string message, string title = null, MessageBoxImage? image = null) {
|
||||
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
|
||||
if (result == BTSMessageBoxButtons.Support) {
|
||||
show_support_window(message, title);
|
||||
}
|
||||
public void report_error(string message, string title = null, MessageBoxImage? image = null) {
|
||||
// Error reports are likely to arrive the api thread (which is not an STA thread)
|
||||
Application.Current.Dispatcher.BeginInvoke(async () => {
|
||||
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
|
||||
if (result == BTSMessageBoxButtons.Support) {
|
||||
show_support_window(message, title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void show_support_window(string message, string title) {
|
||||
@@ -70,11 +73,11 @@ namespace bts.spark
|
||||
return (get_successful_rv(result.value), string.Empty);
|
||||
else {
|
||||
string error_msg = template.fmt(error);
|
||||
error_reporter.report_error(error_msg).ignore();
|
||||
error_reporter.report_error(error_msg);
|
||||
return (get_unsuccessful_rv(), error_msg);
|
||||
}
|
||||
} else {
|
||||
error_reporter.report_error(result.error_message).ignore();
|
||||
error_reporter.report_error(result.error_message);
|
||||
return (get_unsuccessful_rv(), result.error_message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace bts {
|
||||
_deleted_synthetics = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
_bus.post(SparkTags.SyntheticstrategiesTag, t);
|
||||
}));
|
||||
new DataPersistor(_bus);
|
||||
Task.Run(run);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace bts.shell {
|
||||
_model = model;
|
||||
_error_reporter = error_reporter;
|
||||
_dispose = Disposable.Create(
|
||||
model.on_orders.ObserveOnDispatcher().Subscribe(o => _orders = o),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).ObserveOnDispatcher().Subscribe(m =>_md_map = m),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).ObserveOnDispatcher().Subscribe(_show_modify_order)
|
||||
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.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui {
|
||||
[Export(typeof(IShellDaemon))]
|
||||
class RecenterDaemon : IShellDaemon, IDisposable {
|
||||
bool _run = true;
|
||||
readonly IBus _bus;
|
||||
readonly TimeSpan _time_to_center;
|
||||
DateTime _last_recenter;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RecenterDaemon(IBus bus) {
|
||||
_bus = bus;
|
||||
_time_to_center = TimeSpan.FromSeconds(config.Configuration.get<bts.config.LadderKnobs>().seconds_to_recenter);
|
||||
_last_recenter = DateTime.UtcNow;
|
||||
|
||||
_dispose = _bus.subscribe(SparkTags.RequestRecenterLadder).Subscribe(_ => trigger_recenter(SparkTags.RecenterLadderNow));
|
||||
run();
|
||||
}
|
||||
|
||||
void trigger_recenter(ITag<DateTime> tag) {
|
||||
_last_recenter = DateTime.UtcNow;
|
||||
_bus.post(tag, _last_recenter);
|
||||
}
|
||||
|
||||
async void run() {
|
||||
_run = true;
|
||||
while (_run) {
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
if (_last_recenter + _time_to_center < DateTime.UtcNow) {
|
||||
trigger_recenter(SparkTags.RecenterLadderBegin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ namespace bts {
|
||||
public SecurityPickerDaemon(IBus bus) {
|
||||
_bus = bus;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowPickOne).ObserveOnDispatcher().Subscribe(show_pick_one),
|
||||
bus.subscribe(SparkTags.FilterPickOne).ObserveOnDispatcher().Subscribe(show_filtered_pick_one),
|
||||
bus.subscribe(SparkTags.ShowPickMultiple).ObserveOnDispatcher().Subscribe(show_pick_multiple),
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).ObserveOnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
bus.subscribe(SparkTags.ShowPickOne).OnDispatcher().Subscribe(show_pick_one),
|
||||
bus.subscribe(SparkTags.FilterPickOne).OnDispatcher().Subscribe(show_filtered_pick_one),
|
||||
bus.subscribe(SparkTags.ShowPickMultiple).OnDispatcher().Subscribe(show_pick_multiple),
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).OnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using bts.api.core;
|
||||
using bts.metrics;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.perf {
|
||||
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class ShellLatencyDaemon : IShellDaemon, IDisposable {
|
||||
readonly ShellLatencyService _service;
|
||||
readonly string _hostname;
|
||||
readonly string _version;
|
||||
IDisposable _subs;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service) {
|
||||
_service = service;
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_hostname = Environment.MachineName;
|
||||
_subs = _service.subscribei_clock().Subscribe(on_tick);
|
||||
}
|
||||
|
||||
void on_tick(Update<TickImmutable, int> update) {
|
||||
if (update.added.Count > 0) {
|
||||
var tick = update.added.Last();
|
||||
_service.report_async(new Report() {
|
||||
HostName = _hostname,
|
||||
Version = _version,
|
||||
Timestamp = tick.Timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using bts.api.orderman;
|
||||
using bts.api.synthetic;
|
||||
using bts.api.diagnostic;
|
||||
using bts.api.admin;
|
||||
using bts.metrics;
|
||||
|
||||
namespace Spark
|
||||
{
|
||||
@@ -41,5 +42,9 @@ namespace Spark
|
||||
[Export]
|
||||
public SyntheticSecurityService synthetic_security_service => context.locate<SyntheticSecurityService>();
|
||||
|
||||
}
|
||||
[Export]
|
||||
public ShellLatencyService shell_latency_service => context.locate<ShellLatencyService>();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace mbts.ui
|
||||
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
|
||||
public static ImageSource Date { get { return load_image("Date.png"); } }
|
||||
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
|
||||
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
|
||||
public static ImageSource Algo { get { return Cube3d; } }
|
||||
public static ImageSource Quote { get { return Math; } }
|
||||
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
|
||||
@@ -62,9 +62,7 @@ namespace mbts.ui
|
||||
#pragma warning restore CS0618
|
||||
|
||||
// Vector icons
|
||||
public static ImageSource Favorites { get { return Favorites_Gold; } }
|
||||
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
|
||||
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
|
||||
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
|
||||
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
|
||||
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
|
||||
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -3,33 +3,17 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mbts="clr-namespace:mbts.ui">
|
||||
<DrawingImage x:Key="FavoritesGold">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="FavoritesWhite">
|
||||
<DrawingImage x:Key="Bookmarks">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<DrawingGroup.Children>
|
||||
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Thickness="1" Brush="Black" />
|
||||
<Pen Thickness="3" Brush="Black" />
|
||||
</GeometryDrawing.Pen>
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#ffd932" />
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup.Children>
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace bts.ui {
|
||||
[Export(typeof(IShellModule))]
|
||||
public class BookmarkShellModule : ILazyMenuModule {
|
||||
readonly WindowManager _wm;
|
||||
readonly IBookmarks _bookmarks;
|
||||
readonly ISavedWindowFactory _window_factory;
|
||||
readonly WorkspacesModel _workspaces;
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarkShellModule(
|
||||
WorkspacesModel workspaces,
|
||||
WindowManager wm,
|
||||
IBookmarks bm,
|
||||
ISavedWindowFactory swf) {
|
||||
_workspaces = workspaces;
|
||||
_wm = wm;
|
||||
_bookmarks = bm;
|
||||
_window_factory = swf;
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Bookmarks;
|
||||
|
||||
public string name => "My Windows";
|
||||
|
||||
public int priority => 35;
|
||||
|
||||
public IEnumerable<SparkCommand> actions {
|
||||
get {
|
||||
// The windows that are open
|
||||
var open =
|
||||
_wm.list_documents()
|
||||
.Select(td => td.ViewModel)
|
||||
.Where(vm => vm.HasCustomTitle)
|
||||
.Select(vm =>
|
||||
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
var open_set = new HashSet<string>(open.Select(c => c.title));
|
||||
|
||||
// The windows that are not open
|
||||
var closed =
|
||||
_bookmarks.bookmarks
|
||||
.Where(state => !open_set.Contains(state.title))
|
||||
.Select(state =>
|
||||
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
|
||||
() => {
|
||||
_bookmarks.remove(state);
|
||||
return _window_factory.restore(state);
|
||||
}))
|
||||
.Cast<SparkCommand>();
|
||||
|
||||
// todo: The windows that are closed
|
||||
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
|
||||
yield return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
// This module shouldn't have any windows
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using mbts.ui.settings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui.shell {
|
||||
|
||||
public interface IBookmarks {
|
||||
void save_bookmark(IShellWindow window);
|
||||
void save_bookmark(WindowState window);
|
||||
void remove(WindowState window);
|
||||
void clear();
|
||||
IEnumerable<WindowState> bookmarks { get; }
|
||||
}
|
||||
|
||||
[Export(typeof(IBookmarks))]
|
||||
public class BookmarksModel : IBookmarks {
|
||||
readonly ISettingsCollector _settings_collector;
|
||||
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public BookmarksModel(ISettingsCollector settings) {
|
||||
_settings_collector = settings;
|
||||
}
|
||||
public void save_bookmark(IShellWindow window) {
|
||||
var state = _settings_collector.get_window_state(window);
|
||||
if (state != null) {
|
||||
_bookmarks[state.title] = state;
|
||||
}
|
||||
}
|
||||
public void save_bookmark(WindowState window) {
|
||||
_bookmarks[window.title] = window;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
_bookmarks.Clear();
|
||||
}
|
||||
|
||||
public void remove(WindowState window) {
|
||||
_bookmarks.Remove(window.title);
|
||||
}
|
||||
|
||||
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
|
||||
}
|
||||
}
|
||||
@@ -78,11 +78,11 @@ namespace mbts.spark {
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_orders(security.Id).ObserveOnDispatcher().Subscribe(_on_orders),
|
||||
_position.ObserveOnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_inside_bids.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.ObserveOnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
_orders_model.on_security_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace mbts.spark {
|
||||
}
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.ObserveOnDispatcher().Subscribe(s => {
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
rows_view.SetSortingCriteria(s.sorting_criteria);
|
||||
var filters = s.filters;
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace mbts.spark {
|
||||
})
|
||||
),
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count < 3, secs => new SecurityCriteria(secs))
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(c =>
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
|
||||
}
|
||||
|
||||
@@ -67,4 +67,4 @@ namespace mbts.spark {
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace bts.shell {
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).ObserveOnDispatcher().Subscribe(_show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -6,11 +6,14 @@ using mbts.ui.controls;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
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> {
|
||||
@@ -26,23 +29,32 @@ namespace mbts.spark {
|
||||
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);
|
||||
trade_key_filter.load_state(filters.trade_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
// Columns
|
||||
add_column(TimeAndSalesView.Columns.Security, "Security", DIYGrid.DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, "Time", DIYGrid.DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, "Price", DIYGrid.DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
|
||||
add_column(TimeAndSalesView.Columns.Size, "Size", DIYGrid.DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, "Trade Key", DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, "Security Key", DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template) =>
|
||||
columns.Add(new DIYGrid.RenderTextColumn<Row>(r => r[col_key], filter, filter_template) { Header = header, Width = width });
|
||||
//
|
||||
add_column(TimeAndSalesView.Columns.Security, DefaultColumnHeaders.Security, DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.Time, DefaultColumnHeaders.Time, DefaultColumnWidths.Time, time_filter, DataGridFilterKnownTemplates.TimeFilter);
|
||||
add_column(TimeAndSalesView.Columns.Price, DefaultColumnHeaders.Price, DefaultColumnWidths.Price, price_filter, DataGridFilterKnownTemplates.DoubleMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Size, DefaultColumnHeaders.Size, DefaultColumnWidths.Size, size_filter, DataGridFilterKnownTemplates.UIntMinMax, System.Windows.HorizontalAlignment.Right);
|
||||
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DIYGrid.DefaultColumnWidths.Aggressor, aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
|
||||
add_column(TimeAndSalesView.Columns.TradeKey, DefaultColumnHeaders.TradeKey, DIYGrid.DefaultColumnWidths.TradeKey, trade_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DIYGrid.DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
|
||||
void add_column(TimeAndSalesView.Columns col_key, string header, double width, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
|
||||
columns.Add(new TimeAndSalesColumn(
|
||||
r => r[col_key]
|
||||
, (r) => _get_aggressor_brush(r)
|
||||
, filter
|
||||
, filter_template
|
||||
)
|
||||
{ Header = header, Width = width, horizontal_alignment = h_alignment }
|
||||
);
|
||||
|
||||
_resources.AddRange(
|
||||
show_ladder,
|
||||
DataGridFilterHelper.hook_filters(
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, trade_key_filter, security_key_filter
|
||||
rows_view, time_filter, security_filter, price_filter, size_filter, aggressor_filter, trade_key_filter, security_key_filter
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -50,11 +62,22 @@ namespace mbts.spark {
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableCollection<DIYGrid.ColumnBase>();
|
||||
public DelegateCommandParamObserver<Row> show_ladder { get; }
|
||||
|
||||
private Brush _get_aggressor_brush(Row r) {
|
||||
BTSColor c;
|
||||
Brush b;
|
||||
if (r.aggressor_side == AggressorSide.Buy) c = BTSColors.MarketData.bid;
|
||||
else if (r.aggressor_side == AggressorSide.Sell) c = BTSColors.MarketData.ask;
|
||||
else c = Brushes.Transparent;
|
||||
(b = new SolidColorBrush(c) { Opacity = 0.33 }).Freeze();
|
||||
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));
|
||||
public IDataGridColumnPersistentFilter<Row, string> trade_key_filter { get; } = new RegexFilterVM<Row>(r => r.trade_key.ToString(), nameof(Row.trade_key));
|
||||
|
||||
@@ -65,7 +88,8 @@ namespace mbts.spark {
|
||||
price = price_filter.get_state(),
|
||||
size = size_filter.get_state(),
|
||||
trade_key = trade_key_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
security_key = security_key_filter.get_state(),
|
||||
aggressor_sides = aggressor_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -90,6 +114,7 @@ namespace mbts.spark {
|
||||
_time_text,
|
||||
_price_text,
|
||||
_size_text,
|
||||
_aggressor_text,
|
||||
_trade_key_text,
|
||||
_security_key_text;
|
||||
|
||||
@@ -98,11 +123,13 @@ namespace mbts.spark {
|
||||
time = trade.Time.lldatetime();
|
||||
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
|
||||
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();
|
||||
}
|
||||
@@ -113,6 +140,7 @@ namespace mbts.spark {
|
||||
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] {
|
||||
@@ -122,6 +150,7 @@ namespace mbts.spark {
|
||||
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.TradeKey: return _trade_key_text;
|
||||
case TimeAndSalesView.Columns.SecurityKey: return _security_key_text;
|
||||
default: return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for TimeAndSalesView.xaml
|
||||
/// </summary>
|
||||
partial class TimeAndSalesView : UserControl {
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, TradeKey, SecurityKey }
|
||||
public enum Columns { Time, Security, Price, Size, OrderType, TradeKey, SecurityKey, Aggressor }
|
||||
|
||||
public TimeAndSalesView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
@@ -36,7 +37,16 @@ namespace mbts.spark {
|
||||
public override string window_type => _window_type;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override UIElement Content => _view;
|
||||
public override double MinWidth => 444 + SPARK_MENU_COLLAPSED_WIDTH + LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
public override double MinWidth => DefaultColumnWidths.Security
|
||||
+ DefaultColumnWidths.Time
|
||||
+ DefaultColumnWidths.Price
|
||||
+ DefaultColumnWidths.Size
|
||||
+ DefaultColumnWidths.Aggressor
|
||||
+ DefaultColumnWidths.TradeKey
|
||||
+ DefaultColumnWidths.SecurityKey
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH;
|
||||
|
||||
public override StartupData SaveState => _state = new StartupData(_state.security_criteria, _vm.value.rows_view.GetSortingCriteria(), _vm.value.get_filter_states());
|
||||
|
||||
public override void Dispose() =>
|
||||
@@ -47,8 +57,8 @@ namespace mbts.spark {
|
||||
_vm = new SingleValuePump<TimeAndSalesVM>();
|
||||
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("TimeAndSales Callback");
|
||||
var update_securities_cmd = SparkCommand.create("Update Time and Sales", Icon, update_securities_tag);
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).ObserveOnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).ObserveOnDispatcher();
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
@@ -56,7 +66,7 @@ namespace mbts.spark {
|
||||
SparkCommands.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)
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(update_criteria);
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(update_criteria);
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).ObserveOnDispatcher().Subscribe(show)
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ namespace bts.shell {
|
||||
public string trade_key;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
[ProtoMember(7)]
|
||||
public co2.AggressorSide[] aggressor_sides;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace bts.ui {
|
||||
|
||||
public int priority => 40;
|
||||
|
||||
public IEnumerable<SparkCommand> actions => new []{
|
||||
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
|
||||
SparkCommands.ShowWorkspaces
|
||||
//SparkCommands.ShowSettings
|
||||
};
|
||||
|
||||
@@ -14,35 +14,35 @@
|
||||
<ResourceDictionary Source="/view/Resources/DataTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Popup/PopupControl.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Spreader/ParameterSectionView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/TickFactorsPicker/TickFactorsPicker.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="GroupLevelIndent">18</sys:Double>
|
||||
<sys:Double x:Key="RowHeaderWidth">285</sys:Double>
|
||||
<sys:Double x:Key="RowHeight">16</sys:Double>
|
||||
<sys:Double x:Key="NegativeRowHeight">-16</sys:Double>
|
||||
<sys:Double x:Key="ItemsHostCanBeDisabledCollapsedHeight">15</sys:Double>
|
||||
<Thickness
|
||||
x:Key="GroupLevelMargin"
|
||||
Left="{StaticResource GroupLevelIndent}"
|
||||
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
/>
|
||||
<Thickness
|
||||
x:Key="GroupContentAlignedWithHeaderMargin"
|
||||
Left="{StaticResource GroupLevelIndent}"
|
||||
Left="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Top="{StaticResource NegativeRowHeight}"
|
||||
/>
|
||||
<cv:SyntheticPriceCalcDisplayTextConverter x:Key="SyntheticPriceCalcDisplayConverter"/>
|
||||
<cv:TickFactorsTextConverter x:Key="TickFactorsTextConverter"/>
|
||||
<usp:RowHeaderGroupIndentConverter
|
||||
x:Key="RowHeaderGroupIndentConverter"
|
||||
Indent="{StaticResource GroupLevelIndent}"
|
||||
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
/>
|
||||
<usp:RowHeaderGroupIndentConverter
|
||||
x:Key="ReverseRowHeaderGroupIndentConverter"
|
||||
Indent="{StaticResource GroupLevelIndent}"
|
||||
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Reverse="True"
|
||||
/>
|
||||
<usp:RowGroupExpanderWidthConverter
|
||||
x:Key="RowGroupExpanderWidthConverter"
|
||||
Indent="{StaticResource GroupLevelIndent}"
|
||||
Indent="{x:Static usp:ExecutionStrategyEditor.GroupLevelIndent}"
|
||||
Offset="{StaticResource RowHeight}"
|
||||
RowHeaderWidth="{StaticResource RowHeaderWidth}"
|
||||
/>
|
||||
@@ -293,7 +293,7 @@
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataGridTextColumn x:Key="AddColumnPlaceholder" x:Shared="False" IsReadOnly="True">
|
||||
<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"
|
||||
@@ -308,7 +308,7 @@
|
||||
</c:PopupControl>
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataTemplate x:Key="RemovableColumnHeaderTemplate">
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.RemovableColumnHeaderTemplateKey}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -321,7 +321,9 @@
|
||||
<TextBlock Grid.Column="1" Margin="4,0,0,0" Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="{x:Static usp:ExecutionStrategyEditor.NonRemovableColumnHeaderTemplateKey}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource SecurityTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
<ControlTemplate x:Key="ExecutionStrategyEditorTemplate" TargetType="usp:ExecutionStrategyEditor">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
@@ -364,20 +366,11 @@
|
||||
Grid.Column="2" VerticalAlignment="Center" Margin="6,0,0,0" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_Tick_ComboBox" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
ItemsSource="{TemplateBinding AvailableTickFactors}" SelectedItem="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
<c:TickFactorsPicker
|
||||
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="6,0,0,0" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
|
||||
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
|
||||
>
|
||||
<ComboBox.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</ComboBox.GroupStyle>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource TickFactorsTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
/>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="6,0,0,0">Pricing</TextBlock>
|
||||
<ComboBox
|
||||
Grid.Column="5" Margin="6,0,0,0" IsEnabled="False"
|
||||
|
||||
@@ -25,7 +25,13 @@ namespace mbts.ui.spark {
|
||||
/// <summary>
|
||||
/// Interaction logic for ExecutionStrategyEditor.xaml
|
||||
/// </summary>
|
||||
partial class ExecutionStrategyEditor : Control {
|
||||
partial class ExecutionStrategyEditor : Control {
|
||||
public const string
|
||||
RemovableColumnHeaderTemplateKey = "RemovableColumnHeaderTemplate",
|
||||
NonRemovableColumnHeaderTemplateKey = "ReadOnlyColumnHeaderTemplate",
|
||||
AddColumnPlaceholderKey = "AddColumnPlaceholder";
|
||||
public const double GroupLevelIndent = 18;
|
||||
|
||||
static ExecutionStrategyEditor() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExecutionStrategyEditor), new FrameworkPropertyMetadata(typeof(ExecutionStrategyEditor)));
|
||||
|
||||
@@ -44,13 +50,6 @@ namespace mbts.ui.spark {
|
||||
public TickFactors TickFactors { get => GetValue(TickFactorsProperty) as TickFactors; set => SetValue(TickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AvailableTickFactors
|
||||
public static readonly DependencyProperty AvailableTickFactorsProperty = DependencyProperty.Register(
|
||||
nameof(AvailableTickFactors), typeof(IEnumerable), typeof(ExecutionStrategyEditor), new PropertyMetadata(new TickFactors[0]));
|
||||
|
||||
public IEnumerable AvailableTickFactors { get => GetValue(AvailableTickFactorsProperty) as IEnumerable; set => SetValue(AvailableTickFactorsProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region IsNew
|
||||
public static readonly DependencyProperty IsNewProperty =
|
||||
DependencyProperty.Register(
|
||||
@@ -196,7 +195,7 @@ namespace mbts.ui.spark {
|
||||
if (dg != null) {
|
||||
var legs = LegTuples;
|
||||
dg.Columns.SetItems(legs.Select(_column_factory));
|
||||
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource("AddColumnPlaceholder"));
|
||||
if (IsNew) dg.Columns.Add((DataGridColumn)FindResource(AddColumnPlaceholderKey));
|
||||
_dg_line_adorner = AdornerHelper.attach_continuously_on_loaded(() => new ExecutionStrategyGridLineAdorner(dg), dg);
|
||||
_legs_collection_changed_sub = (legs as INotifyCollectionChanged)?.Subscribe((s, e) => dg.Columns.ReflectChange(legs, _column_factory, e));
|
||||
}
|
||||
@@ -204,7 +203,7 @@ namespace mbts.ui.spark {
|
||||
|
||||
DataGridColumn _column_factory(LegSpreaderParamsTuple l) {
|
||||
var col = new DataGridColumnInternal(l.leg.Security, ParameterCellTemplateConverter, ParameterCellEditingTemplateConverter) { Width = 75, };
|
||||
if (IsNew) col.HeaderTemplate = (DataTemplate)FindResource("RemovableColumnHeaderTemplate");
|
||||
col.HeaderTemplate = (DataTemplate)FindResource(IsNew ? RemovableColumnHeaderTemplateKey : NonRemovableColumnHeaderTemplateKey);
|
||||
DataGridEx.SetRightBorder(col, new DataGridExVerticalLine(false, true, Colors.DarkGray, 1));
|
||||
return col;
|
||||
}
|
||||
@@ -240,18 +239,18 @@ namespace mbts.ui.spark {
|
||||
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
|
||||
var rv = new ContentControl();
|
||||
rv.SetBinding(ContentControl.ContentProperty, new Binding());
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding("data_type") { Converter = cell_template_converter });
|
||||
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
class ExecutionStrategyErrorAdorner : NotifyDataErrorAdorner<ExecutionStrategyEditor, SyntheticEditorVM>, IDisposable {
|
||||
readonly ChildProperty<TextBox> _name_text_box;
|
||||
readonly ChildProperty<ComboBox> _tick_combo_box;
|
||||
readonly ChildProperty<TickFactorsPicker> _tick_factors_picker;
|
||||
|
||||
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
|
||||
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
|
||||
_tick_combo_box = child_property<ComboBox>("PART_Tick_ComboBox");
|
||||
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
@@ -263,7 +262,7 @@ namespace mbts.ui.spark {
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.name)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _name_text_box.value, null, new Pen(Brushes.Red, 1));
|
||||
if (_vm != null && _vm.GetErrors(nameof(SyntheticEditorVM.tick_factors)).Cast<string>().Any())
|
||||
this.draw_border(drawing_context, _tick_combo_box.value, null, new Pen(Brushes.Red, 1));
|
||||
this.draw_border(drawing_context, _tick_factors_picker.value, null, new Pen(Brushes.Red, 1));
|
||||
string error_text = string.Join(", ", _vm.AllErrors);
|
||||
this.draw_relative_text(RelativePosition.Below, error_text, drawing_context, _name_text_box.value, BTSColors.header_error_text_brush, 0, y_offset);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ namespace mbts.ui.spark
|
||||
void _items_presenter_SizeChanged(object sender, SizeChangedEventArgs e) { InvalidateVisual(); }
|
||||
|
||||
protected override void draw_vertical_line(DrawingContext dc, double x, DataGridExVerticalLine line) {
|
||||
if (_items_presenter != null) dc.DrawLine(line.pen.Value, new Point(x - 10, 58), new Point(x - 10, _items_presenter.ActualHeight + 58));
|
||||
x -= GroupLevelIndent;
|
||||
var start = _grid.TranslatePoint(new Point(x, _grid.super_header_height + _col_header_presenter.ActualHeight), this);
|
||||
var stop = _grid.TranslatePoint(new Point(x, _grid.ActualHeight), this);
|
||||
dc.DrawLine(line.pen.Value, start, stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,9 @@ namespace bts.shell {
|
||||
void IInitializable.initialize() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
_dispose = Disposable.Create(
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).ObserveOnDispatcher().Subscribe(_show_synthetic_create),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).ObserveOnDispatcher().Subscribe(_show_synthetic_strategy),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).ObserveOnDispatcher().Subscribe(_show_synthetic_clone)
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).OnDispatcher().Subscribe(_show_synthetic_create),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).OnDispatcher().Subscribe(_show_synthetic_strategy),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).OnDispatcher().Subscribe(_show_synthetic_clone)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
HorizontalAlignment="Stretch" Margin="6"
|
||||
SyntheticName="{Binding name}"
|
||||
TickFactors="{Binding tick_factors}"
|
||||
AvailableTickFactors="{Binding available_tick_factors_view}"
|
||||
IsNew="{Binding is_new}"
|
||||
CanUserAddNewLeg="{Binding can_add_leg}"
|
||||
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
|
||||
|
||||
@@ -72,9 +72,9 @@
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.security_key_filter, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.security_key_filter.active, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.security_key_filter.open, Source={u:DirectReference grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource IntMinMaxTemplate}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource RegexTemplate}"
|
||||
/>
|
||||
</DataGrid.Columns>
|
||||
</c:DataGridEx>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -32,14 +32,14 @@ namespace mbts.spark {
|
||||
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
|
||||
() => new SecurityCriteria(), () => true
|
||||
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
can_row_change, r => r == null ? 0 : r.security_key
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_mod_params)),
|
||||
can_execute, select_security_id
|
||||
), SparkCommand.create("Change", Icons.ModifySynthetic, SparkTags.ShowSyntheticStrategyTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
can_row_change, r => r == null ? SecurityDataHelper.Defaults.SecurityImmutable : r.security
|
||||
r => r != null, r => r?.security ?? SecurityDataHelper.Defaults.SecurityImmutable
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
r => r != null && r.strategy.HasValue, r => r == null ? default : (r.security, r.strategy.Value)
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_strategy)),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder.complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
|
||||
@@ -49,10 +49,19 @@ namespace mbts.spark {
|
||||
})
|
||||
};
|
||||
|
||||
bool can_row_change(SyntheticManagementDialogVM.Row r) =>
|
||||
r != null && r.n_working_orders == 0;
|
||||
}
|
||||
IObservable<RowModificationParams?> observe_row_mod_params(SyntheticManagementDialogVM.Row r) =>
|
||||
r == null ? Observable.Return(new RowModificationParams?()) :
|
||||
_orders_model.security_has_working_orders(r.security_key).Select(hwo => RowModificationParams.create(r.security, hwo));
|
||||
|
||||
IObservable<RowStrategy?> observe_row_strategy(SyntheticManagementDialogVM.Row r) =>
|
||||
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;
|
||||
(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;
|
||||
}
|
||||
|
||||
public void initialize(StartupData state) {
|
||||
Logger.notnull(state);
|
||||
_state = state;
|
||||
@@ -71,5 +80,31 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
struct RowModificationParams {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly bool has_working_orders;
|
||||
|
||||
RowModificationParams(SecurityImmutable security, bool has_working_orders) {
|
||||
this.security = security;
|
||||
this.has_working_orders = has_working_orders;
|
||||
}
|
||||
|
||||
public static RowModificationParams? create(SecurityImmutable security, bool has_working_orders) =>
|
||||
new RowModificationParams(security, has_working_orders);
|
||||
}
|
||||
|
||||
struct RowStrategy {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly ExecutionStrategyImmutable strategy;
|
||||
|
||||
RowStrategy(SecurityImmutable security, ExecutionStrategyImmutable strategy) {
|
||||
this.security = security;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public static RowStrategy? create(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
|
||||
new RowStrategy(security, strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace bts.shell {
|
||||
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
@@ -53,6 +53,8 @@ namespace bts.shell {
|
||||
public IntMinMaxState n_legs;
|
||||
[ProtoMember(5)]
|
||||
public UIntMinMaxState n_working_orders;
|
||||
[ProtoMember(6)]
|
||||
public string security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _enabled;
|
||||
|
||||
protected CellBase(IRowModel model, LegSpreaderParamsTuple leg_tup) =>
|
||||
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).ObserveOnDispatcher());
|
||||
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).OnDispatcher());
|
||||
|
||||
public bool enabled => _enabled.Value;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
: base(model, leg_tup) {
|
||||
_model = model;
|
||||
_leg_tup = leg_tup;
|
||||
_override_value_sub = model.observe_override_value(leg_tup).ObserveOnDispatcher().Subscribe(v => {
|
||||
_override_value_sub = model.observe_override_value(leg_tup).OnDispatcher().Subscribe(v => {
|
||||
value = v;
|
||||
RaisePropertyChanged(nameof(value));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -10,11 +11,9 @@ 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.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -23,7 +22,6 @@ namespace mbts.spark {
|
||||
/// The view model designed to serve as the DataContext for the SyntheticEditorView
|
||||
/// </summary>
|
||||
partial class SyntheticEditorVM : DisposableViewModelBase, IRequestViewClose, IRequestBindingSourceUpdate, IDisposable {
|
||||
readonly ObservableCollection<TickFactors> _available_tick_factors = new ObservableCollection<TickFactors>(TickUtils.all_present_ticks());
|
||||
readonly QuantityModel _quantity_model;
|
||||
readonly PriceMultiplierModel _price_mult_model;
|
||||
readonly List<IDisposable> _resources = new List<IDisposable>();
|
||||
@@ -44,10 +42,10 @@ namespace mbts.spark {
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
title = $"Edit '{synthetic.Synthetic.Name}'";
|
||||
icon = Icons.ModifySynthetic;
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.ObserveOnDispatcher());
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
|
||||
var commit = new ObservantAsyncCommand(commit_async);
|
||||
_resources.Add(security_has_working_orders.Select(v => !v).ObserveOnDispatcher().Subscribe(commit));
|
||||
_resources.Add(_has_working_orders.ObserveOnDispatcher().Subscribe(vals => {
|
||||
_resources.Add(security_has_working_orders.Select(v => !v).OnDispatcher().Subscribe(commit));
|
||||
_resources.Add(_has_working_orders.OnDispatcher().Subscribe(vals => {
|
||||
if (vals.newval)
|
||||
set_error(nameof(has_working_orders), "Security has working orders");
|
||||
else clear_error(nameof(has_working_orders));
|
||||
@@ -121,7 +119,7 @@ namespace mbts.spark {
|
||||
flash_error_msg("Name already taken", SparkUtils.UI.MessageFlashDuration, nameof(name)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
if (_tick_factors == null || !_tick_factors.is_valid() || !_available_tick_factors.Contains(_tick_factors)) {
|
||||
if (_tick_factors == null || !_tick_factors.is_valid()) {
|
||||
flash_error_msg("Please select ticks", SparkUtils.UI.MessageFlashDuration, nameof(tick_factors)).ignore();
|
||||
valid = false;
|
||||
}
|
||||
@@ -133,6 +131,8 @@ namespace mbts.spark {
|
||||
_synthetic.ContractSpec.LowTick = tick_frac;
|
||||
_synthetic.ContractSpec.HighTick = tick_frac;
|
||||
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
|
||||
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
|
||||
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
|
||||
uint? id = await model.create_synthetic(_synthetic);
|
||||
if (id.HasValue && id != 0) {
|
||||
if (await model.save_strategy(id.Value, get_strategy())) {
|
||||
@@ -152,8 +152,6 @@ namespace mbts.spark {
|
||||
_set_ticks_automatically = is_new;
|
||||
_set_quantities_automatically = is_new;
|
||||
_set_price_mults_automatically = is_new;
|
||||
available_tick_factors_view = CollectionViewSource.GetDefaultView(_available_tick_factors);
|
||||
available_tick_factors_view.GroupDescriptions.Add(new PropertyGroupDescription(nameof(TickFactors.tick_base)));
|
||||
|
||||
_quantity_model = new QuantityModel(!is_new);
|
||||
_price_mult_model = new PriceMultiplierModel(!is_new);
|
||||
@@ -173,7 +171,8 @@ namespace mbts.spark {
|
||||
}
|
||||
_is_setting_price_mults = false;
|
||||
};
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
|
||||
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
@@ -244,9 +243,11 @@ namespace mbts.spark {
|
||||
public TickFactors tick_factors {
|
||||
get => _tick_factors;
|
||||
set {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
if (_tick_factors != value) {
|
||||
_tick_factors = value;
|
||||
_set_ticks_automatically = false;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool has_working_orders => _has_working_orders.Value;
|
||||
@@ -260,7 +261,6 @@ namespace mbts.spark {
|
||||
public IRaiseCanExecuteChangedCommand commit { get; }
|
||||
public ICommand remove_leg { get; }
|
||||
public ContractPickerModel contract_picker_model { get; }
|
||||
public ICollectionView available_tick_factors_view { get; }
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
@@ -327,8 +327,6 @@ namespace mbts.spark {
|
||||
|
||||
void _deduce_ticks() {
|
||||
_tick_factors = TickUtils.deduce_ticks(leg_tuples.Select(l => l.leg.Security.ContractSpec));
|
||||
if (_tick_factors != null && !_available_tick_factors.Contains(tick_factors))
|
||||
_available_tick_factors.Add(_tick_factors);
|
||||
RaisePropertyChanged(nameof(tick_factors));
|
||||
}
|
||||
|
||||
@@ -337,4 +335,4 @@ namespace mbts.spark {
|
||||
else clear_error(nameof(leg_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,9 @@ namespace mbts.spark {
|
||||
description_filter.load_state(filters.description);
|
||||
n_legs_filter.load_state(filters.n_legs);
|
||||
n_working_orders_filter.load_state(filters.n_working_orders);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
}
|
||||
_dispose = DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter);
|
||||
_dispose = DataGridFilterHelper.hook_filters(rows_view, create_time_filter, name_filter, description_filter, n_legs_filter, n_working_orders_filter, security_key_filter);
|
||||
}
|
||||
|
||||
protected override Row row_factory((ExecutionStrategyImmutable? strategy, SecurityMD md) row_model) =>
|
||||
@@ -41,6 +42,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, string> description_filter { get; } = new RegexFilterVM<Row>(r => r.description, nameof(Row.description));
|
||||
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> n_legs_filter { get; } = new IntMinMax<Row>(r => r.n_legs, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, UIntMinMaxState> n_working_orders_filter { get; } = new UIntMinMax<Row>(r => r.n_working_orders, nameof(Row.n_legs));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
|
||||
public FilterStates get_filter_states() =>
|
||||
new FilterStates {
|
||||
@@ -48,7 +50,8 @@ namespace mbts.spark {
|
||||
create_time = create_time_filter.get_state(),
|
||||
name = name_filter.get_state(),
|
||||
n_legs = n_legs_filter.get_state(),
|
||||
n_working_orders = n_working_orders_filter.get_state()
|
||||
n_working_orders = n_working_orders_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -66,7 +69,7 @@ namespace mbts.spark {
|
||||
.Select(t => (uint)(t.state
|
||||
.Where(kv => kv.Value.is_active())
|
||||
.Count())
|
||||
).ObserveOnDispatcher());
|
||||
).OnDispatcher());
|
||||
name = security.Synthetic.Name;
|
||||
n_legs = security.Synthetic.Legs.Count();
|
||||
description = SecurityFormatter.format_contract_legs(security.Synthetic.Legs);
|
||||
@@ -104,4 +107,4 @@ namespace mbts.spark {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace bts.shell
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
_dispose = _message_src.ObserveOnDispatcher().Subscribe(_messages.AddLog);
|
||||
_dispose = _message_src.OnDispatcher().Subscribe(_messages.AddLog);
|
||||
_content =
|
||||
new LogViewer {
|
||||
ItemsSource = _messages,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
public HelpShellModule(IBus bus, IBrowserWindowManager browser) {
|
||||
_awf = new ActivityWindowFactory(this, bus);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowUri).ObserveOnDispatcher().Subscribe(u => browser.Pages.Add(u))
|
||||
bus.subscribe(SparkTags.ShowUri).OnDispatcher().Subscribe(u => browser.Pages.Add(u))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using static bts.api.orderman.Fill.Types;
|
||||
using static bts.shell.FillsWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using static mbts.ui.spark.FillsView;
|
||||
@@ -24,13 +25,16 @@ namespace mbts.spark {
|
||||
public FillsVM(IBus bus, OrdersModel model, StartupData state)
|
||||
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<ITableUpdateTransform<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateTableUpdate(fills_source), sorting_criteria) {
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
|
||||
_model = model;
|
||||
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts(), r => r.account, nameof(Row.account));
|
||||
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users(), r => r.user, nameof(Row.user));
|
||||
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
|
||||
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added, t.removed)),
|
||||
fills_source.with_respect_to(
|
||||
ft => ft.security_md.HasValue ? ft.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
s => s.Id, ft => ft.key
|
||||
).Select(t => (t.added.Values, t.removed.Values)),
|
||||
r => r.security,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
@@ -49,6 +53,7 @@ namespace mbts.spark {
|
||||
side_filter.load_state(filters.side);
|
||||
price_filter.load_state(filters.price);
|
||||
size_filter.load_state(filters.size);
|
||||
fill_kind_filter.load_state(filters.fill_kind);
|
||||
fill_key_filter.load_state(filters.fill_key);
|
||||
order_key_filter.load_state(filters.order_key);
|
||||
security_key_filter.load_state(filters.security_key);
|
||||
@@ -71,6 +76,7 @@ namespace mbts.spark {
|
||||
columns.Add(new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { sort_member_path = nameof(Row.side) });
|
||||
add_col(Columns.Size, DefaultColumnHeaders.Size, r => r.size.ToString(), nameof(Row.size), size_filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.Size);
|
||||
add_col(Columns.Price, DefaultColumnHeaders.Price, r => r.format_price(r.price), nameof(Row.price), price_filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.Price);
|
||||
add_col(Columns.FillKind, "Fill Kind", r => r.fill_kind.ToString(), nameof(Row.fill_kind), fill_kind_filter, DataGridFilterKnownTemplates.DiscreteValues, 70);
|
||||
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), nameof(Row.fill_key), fill_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
|
||||
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), nameof(Row.order_key), order_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
|
||||
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
|
||||
@@ -84,7 +90,7 @@ namespace mbts.spark {
|
||||
//
|
||||
_resources.Add(DataGridFilterHelper.hook_filters(rows_view,
|
||||
fill_time_filter, time_in_force_filter, order_type_filter, account_filter, user_filter, exchange_filter,
|
||||
security_filter, side_filter, price_filter, size_filter, fill_key_filter, order_key_filter, security_key_filter
|
||||
security_filter, side_filter, price_filter, size_filter, fill_kind_filter, fill_key_filter, order_key_filter, security_key_filter
|
||||
));
|
||||
}
|
||||
|
||||
@@ -101,6 +107,7 @@ namespace mbts.spark {
|
||||
public IDataGridColumnPersistentFilter<Row, Side[]> side_filter { get; } = DiscreteValuesFilterVM.Create<Row, Side>(new[] { Side.Buy, Side.Sell }, r => r.side, nameof(Row.side));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> price_filter { get; } = new DoubleMinMax<Row>(r => r.price, nameof(Row.price));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> size_filter { get; } = new DoubleMinMax<Row>(r => r.size, nameof(Row.size));
|
||||
public IDataGridColumnPersistentFilter<Row, FillKind[]> fill_kind_filter { get; } = DiscreteValuesFilterVM.Create<Row, FillKind>(EnumHelper.get_values<FillKind>().ToArray(), r => r.fill_kind, nameof(Row.fill_kind));
|
||||
public IDataGridColumnPersistentFilter<Row, string> fill_key_filter { get; } = new RegexFilterVM<Row>(r => r.fill_key, nameof(Row.fill_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> order_key_filter { get; } = new RegexFilterVM<Row>(r => r.order_key.ToString(), nameof(Row.order_key));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_key_filter { get; } = new RegexFilterVM<Row>(r => r.security_key.ToString(), nameof(Row.security_key));
|
||||
@@ -117,6 +124,7 @@ namespace mbts.spark {
|
||||
side = side_filter.get_state(),
|
||||
price = price_filter.get_state(),
|
||||
size = size_filter.get_state(),
|
||||
fill_kind = fill_kind_filter.get_state(),
|
||||
fill_key = fill_key_filter.get_state(),
|
||||
order_key = order_key_filter.get_state(),
|
||||
security_key = security_key_filter.get_state()
|
||||
@@ -126,8 +134,8 @@ namespace mbts.spark {
|
||||
protected override Row row_factory(FillTuple row_model) => new Row(row_model, _model);
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.fill_time), false); }
|
||||
|
||||
static IObservable<ITableUpdateTransform<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
if (state.orders != null && state.orders.Length == 0) {
|
||||
static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
if (!state.orders.is_null_or_empty()) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
} else return model.on_fill_tuples;
|
||||
@@ -138,7 +146,7 @@ namespace mbts.spark {
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security ?? SecurityDataHelper.Defaults.Security) {
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
@@ -154,6 +162,7 @@ namespace mbts.spark {
|
||||
public Side side => _fill.Side;
|
||||
public double price => _fill.LastPrice;
|
||||
public double size => _fill.LastShares;
|
||||
public FillKind fill_kind => _fill.FillKind;
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace mbts.ui.spark {
|
||||
/// Interaction logic for FillsView.xaml
|
||||
/// </summary>
|
||||
partial class FillsView : UserControl {
|
||||
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKey, OrderKey, SecurityKey, ExchangeOrderID }
|
||||
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey, ExchangeOrderID }
|
||||
|
||||
public FillsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts.shell {
|
||||
: base(module, "Fills") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
@@ -74,6 +74,8 @@ namespace bts.shell {
|
||||
public string order_key;
|
||||
[ProtoMember(13)]
|
||||
public string security_key;
|
||||
[ProtoMember(14)]
|
||||
public Fill.Types.FillKind[] fill_kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,11 @@ namespace bts.shell {
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
var window_props = Configuration.get<bts.config.IShellWindowProps>();
|
||||
_max_ladders_open = (int)(window_props?.ladder?.max_total_open ?? 75);
|
||||
_max_ladders_open = (int)(Configuration.get<bts.config.LadderKnobs>().max_open_ladders);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).ObserveOnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).ObserveOnDispatcher().Subscribe(show_windows)
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,6 +76,7 @@ namespace bts.shell {
|
||||
s.ask_break_line = LadderSettings.Default.ask_break_line;
|
||||
if (s.mid_break_line == null)
|
||||
s.mid_break_line = LadderSettings.Default.mid_break_line;
|
||||
s.row_height = LadderRender.measure_row_height(s);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(OrderBookStartupData state) {
|
||||
@@ -85,6 +85,8 @@ namespace bts.shell {
|
||||
public TimeFilterTuple last_change;
|
||||
[ProtoMember(18)]
|
||||
public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using ProtoBuf;
|
||||
@@ -17,10 +19,9 @@ namespace bts.shell {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
|
||||
public override Task<IShellWindow> load(PAndLStartupData state) {
|
||||
var w = new PAndLWindow(_model, this, _bus, state, SparkCommands.ShowPAndL.icon) as IShellWindow;
|
||||
return Task.FromResult(w);
|
||||
@@ -33,6 +34,61 @@ namespace bts.shell {
|
||||
|
||||
[ProtoContract]
|
||||
public class PAndLStartupData {
|
||||
|
||||
/// <summary>
|
||||
/// Parameterless constructor for protobuf
|
||||
/// </summary>
|
||||
public PAndLStartupData() { }
|
||||
|
||||
public PAndLStartupData(List<SortingCriteria> sorting_criteria, FilterStates filters, pnl_calc_type calc_type, TimeSpan start_of_day_time) {
|
||||
this.sort_criteria = sorting_criteria;
|
||||
this.filters = filters;
|
||||
this.calc_type = calc_type;
|
||||
this.start_of_day_time = start_of_day_time;
|
||||
}
|
||||
|
||||
[ProtoMember(5)]
|
||||
public readonly List<SortingCriteria> sort_criteria;
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
[ProtoMember(15)]
|
||||
public pnl_calc_type calc_type;
|
||||
[ProtoMember(20)]
|
||||
public TimeSpan? start_of_day_time;
|
||||
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
[ProtoMember(1)]
|
||||
public string account_regex;
|
||||
[ProtoMember(2)]
|
||||
public string exchange_regex;
|
||||
[ProtoMember(3)]
|
||||
public string symbol_regex;
|
||||
[ProtoMember(4)]
|
||||
public string security_regex;
|
||||
[ProtoMember(5)]
|
||||
public IntMinMaxState start_net_pos;
|
||||
[ProtoMember(6)]
|
||||
public DoubleMinMaxState start_pnl;
|
||||
[ProtoMember(7)]
|
||||
public UIntMinMaxState day_long_pos;
|
||||
[ProtoMember(8)]
|
||||
public UIntMinMaxState day_short_pos;
|
||||
[ProtoMember(9)]
|
||||
public DoubleMinMaxState day_pnl;
|
||||
[ProtoMember(10)]
|
||||
public IntMinMaxState total_net_pos;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState total_pnl;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState pnl_price;
|
||||
|
||||
// [ProtoMember(13)]
|
||||
// ProtoMember 13 cannot be used further
|
||||
[ProtoMember(14)]
|
||||
public SecurityType[] security_types;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,10 +21,9 @@ namespace mbts.spark {
|
||||
public ExistingOrderTicketVM(OrdersModel model, IObservable<OrderRecordTuple> order_source, IObservable<int[]> calculator_buttons) {
|
||||
uint? key = null;
|
||||
var security_md_source = order_source.Where(ot => ot.security_md.HasValue).Select(ot => ot.security_md.Value);
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
//
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source.ObserveOnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
_price = backing(nameof(price), double.NaN);
|
||||
size_calculator = new CalculatorLogic(null, calculator_buttons);
|
||||
_current_price = backing(nameof(current_price), order_source.Select(ort => ort.order.State.Price));
|
||||
@@ -83,15 +82,15 @@ namespace mbts.spark {
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
bbo = new BBOVM(security_md_source),
|
||||
order_source.ObserveOnDispatcher().Subscribe(ort => {
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
price = ort.order.State.Price;
|
||||
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
|
||||
size_calculator.bypass_value_setter(ort.order.State.Quantity);
|
||||
}),
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).ObserveOnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().ObserveOnDispatcher().Subscribe(modify_order));
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
|
||||
this.modify_order = modify_order;
|
||||
set_price_command = new DelegateCommand<double>(_price.set);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,53 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
namespace mbts.spark {
|
||||
class FillTuple : IEquatable<FillTuple> {
|
||||
public readonly FillImmutable fill;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly string key;
|
||||
public readonly SecurityMD? security_md;
|
||||
public readonly FillTupleKey key;
|
||||
|
||||
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityImmutable? security) {
|
||||
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityMD? security_md) {
|
||||
this.fill = fill;
|
||||
this.order = order;
|
||||
this.security = security;
|
||||
// a FillTuple that DOES NOT HAVE associated order/security needs to be distinguishable
|
||||
// from a FillTuple (of the same underlying FillImmutable) that DOES HAVE associated order/security
|
||||
key = fill.FillKey;
|
||||
if (!order.HasValue) key += "!o";
|
||||
if (!security.HasValue) key += "!s";
|
||||
this.security_md = security_md;
|
||||
key = new FillTupleKey(fill.FillKey, order.HasValue ? order.Value.Key : 0u, security_md.HasValue ? security_md.Value.security.Id : 0u);
|
||||
}
|
||||
|
||||
public bool Equals(FillTuple other) => other != null
|
||||
&& other.order.HasValue == order.HasValue
|
||||
&& other.security.HasValue == security.HasValue
|
||||
&& other.security_md.HasValue == security_md.HasValue
|
||||
&& other.fill.Equals(fill);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FillTuple);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
}
|
||||
|
||||
struct FillTupleKey : IEquatable<FillTupleKey> {
|
||||
public readonly string fill_key;
|
||||
public readonly uint order_key, security_key;
|
||||
|
||||
public FillTupleKey(string fill_key, uint order_key, uint security_key) {
|
||||
this.fill_key = fill_key;
|
||||
this.order_key = order_key;
|
||||
this.security_key = security_key;
|
||||
}
|
||||
|
||||
public bool Equals(FillTupleKey other) =>
|
||||
other.order_key == order_key &&
|
||||
other.security_key == security_key &&
|
||||
other.fill_key == fill_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is FillTupleKey k && Equals(k);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
fill_key.GetHashCode(),
|
||||
(int)order_key,
|
||||
(int)security_key);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using mbts.collections;
|
||||
using mbts.utils;
|
||||
@@ -7,23 +6,25 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillTupleSource : IDisposable {
|
||||
public static IObservable<ITableUpdateTransform<FillTuple, string>> create_observable(
|
||||
class FillTupleSource : IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>>, IDisposable {
|
||||
// returns a ref-count Observable.Create-wrapper that initializes/destroys the [hot-observable] FillTupleSource.
|
||||
// (initializes when the first subscription is made, and destroys when the last subscription is disposed).
|
||||
public static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> create_observable(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills
|
||||
) {
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) {
|
||||
FillTupleSource source = null;
|
||||
int ref_count = 0;
|
||||
return Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
|
||||
return Observable.Create<IIncrementalUpdate<FillTuple, FillTupleKey>>(observer => {
|
||||
if (Interlocked.Increment(ref ref_count) == 1) {
|
||||
SparkUtils.lazy_assert(source == null, () => fmt_assert_msg("source should be null, there must be a leak."));
|
||||
source = new FillTupleSource(securities, orders, fills);
|
||||
}
|
||||
IDisposable sub = source.fill_tuples.Subscribe(observer);
|
||||
IDisposable sub = source.Subscribe(observer);
|
||||
return Disposable.Create(() => {
|
||||
Disposable.dispose_once(ref sub);
|
||||
if (Interlocked.Decrement(ref ref_count) == 0)
|
||||
@@ -33,153 +34,155 @@ namespace mbts.spark {
|
||||
string fmt_assert_msg(string msg) => $"{nameof(FillTupleSource)}.{nameof(create_observable)}: {msg}";
|
||||
}
|
||||
|
||||
readonly Subject<IIncrementalUpdate<FillTuple, FillTupleKey>> _subject = new Subject<IIncrementalUpdate<FillTuple, FillTupleKey>>();
|
||||
readonly Dictionary<FillTupleKey, FillTuple> _state = new Dictionary<FillTupleKey, FillTuple>();
|
||||
readonly SetMap<uint, string> _order_fills = new SetMap<uint, string>();
|
||||
readonly SetMap<uint, string> _security_fills = new SetMap<uint, string>();
|
||||
IReadOnlyDictionary<string, FillImmutable> _fills_cache = new Dictionary<string, FillImmutable>(0);
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>(0);
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
|
||||
// _n_write_ops should never be greater than 1, bc all 3 input observables fire 'OnDispatcher'
|
||||
int _n_read_ops, _n_write_ops;
|
||||
IDisposable _dispose;
|
||||
|
||||
FillTupleSource(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) {
|
||||
State state = new State();
|
||||
var src = Observable.FromEvent<Action<TableUpdateTransform<FillTuple, string>>, TableUpdateTransform<FillTuple, string>>(cb => _updated += cb, cb => _updated -= cb);
|
||||
fill_tuples = Observable.Create<TableUpdateTransform<FillTuple, string>>(observer => {
|
||||
var init_state = state.fill_tuples.state.ToDictionary();
|
||||
observer.OnNext(new TableUpdateTransform<FillTuple, string>(init_state, init_state.Values.ToList(), new List<FillTuple>(0)));
|
||||
return src.Subscribe(observer);
|
||||
});
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) =>
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
securities.ObserveOnDispatcher().Subscribe(s => update(state.update_securities(s))),
|
||||
orders.ObserveOnDispatcher().Subscribe(o => update(state.update_orders(o))),
|
||||
fills.ObserveOnDispatcher().Subscribe(f => update(state.update_fills(f)))
|
||||
);
|
||||
void update(State new_state) {
|
||||
state = new_state;
|
||||
_updated?.Invoke(state.fill_tuples);
|
||||
securities.OnDispatcher().Subscribe(_update_securities),
|
||||
orders.OnDispatcher().Subscribe(_update_orders),
|
||||
fills.OnDispatcher().Subscribe(_update_fills));
|
||||
|
||||
public IDisposable Subscribe(IObserver<IIncrementalUpdate<FillTuple, FillTupleKey>> observer) {
|
||||
using (_read()) {
|
||||
observer.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(new Dictionary<FillTupleKey, FillTuple>(_state), new Dictionary<FillTupleKey, FillTuple>(0)));
|
||||
return _subject.Subscribe(observer);
|
||||
}
|
||||
}
|
||||
|
||||
event Action<TableUpdateTransform<FillTuple, string>> _updated;
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
IObservable<TableUpdateTransform<FillTuple, string>> fill_tuples { get; }
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
class State {
|
||||
readonly IReadOnlyDictionary<string, FillImmutable> _fills;
|
||||
readonly IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
|
||||
readonly IReadOnlyDictionary<uint, SecurityMD> _securities;
|
||||
readonly TableUpdateTransform<FillTuple, string> _fill_tuples;
|
||||
readonly SetMap<uint, string> _order_fills;
|
||||
readonly SetMap<uint, string> _security_fills;
|
||||
|
||||
public State() : this(
|
||||
new Dictionary<string, FillImmutable>(),
|
||||
new Dictionary<uint, OrderRecordImmutable>(),
|
||||
new Dictionary<uint, SecurityMD>(),
|
||||
new TableUpdateTransform<FillTuple, string>(new Dictionary<string, FillTuple>(), new List<FillTuple>(), new List<FillTuple>()),
|
||||
new SetMap<uint, string>(),
|
||||
new SetMap<uint, string>()
|
||||
) { }
|
||||
|
||||
State(
|
||||
IReadOnlyDictionary<string, FillImmutable> fills,
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> orders,
|
||||
IReadOnlyDictionary<uint, SecurityMD> securities,
|
||||
TableUpdateTransform<FillTuple, string> fill_tuples,
|
||||
SetMap<uint, string> order_fill_tuples,
|
||||
SetMap<uint, string> security_fill_tuples
|
||||
) {
|
||||
_fills = fills;
|
||||
_orders = orders;
|
||||
_securities = securities;
|
||||
_fill_tuples = fill_tuples;
|
||||
_order_fills = order_fill_tuples;
|
||||
_security_fills = security_fill_tuples;
|
||||
void _update_fills(TableUpdate<FillImmutable, string> fills) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> removed = new Dictionary<FillTupleKey, FillTuple>(), added = new Dictionary<FillTupleKey, FillTuple>();
|
||||
foreach (FillImmutable fill in fills.removed) {
|
||||
FillTuple old_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
_security_fills.remove(fill.SecurityKey, fill.FillKey);
|
||||
_order_fills.remove(fill.OrderKey, fill.FillKey);
|
||||
}
|
||||
foreach (FillImmutable fill in fills.added) {
|
||||
FillTuple new_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
|
||||
added.Add(new_ft.key, new_ft);
|
||||
_security_fills.add(fill.SecurityKey, fill.FillKey);
|
||||
_order_fills.add(fill.OrderKey, fill.FillKey);
|
||||
}
|
||||
_fills_cache = fills.state;
|
||||
_apply_changes(added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
public TableUpdateTransform<FillTuple, string> fill_tuples => _fill_tuples;
|
||||
|
||||
public State update_fills(TableUpdate<FillImmutable, string> fills) {
|
||||
var removed = fills.removed.Select(get_fill_tuple).ToList();
|
||||
var added = fills.added.Select(get_fill_tuple).ToList();
|
||||
var order_fills = _order_fills.clone();
|
||||
var security_fills = _security_fills.clone();
|
||||
HashSet<string> added_keys = new HashSet<string>(fills.added.Select(f => f.FillKey)),
|
||||
removed_keys = new HashSet<string>(fills.removed.Select(f => f.FillKey));
|
||||
foreach (var ft in removed) {
|
||||
string k = ft.fill.FillKey;
|
||||
if (!added_keys.Contains(k)) {
|
||||
order_fills.remove(ft.fill.OrderKey, k);
|
||||
security_fills.remove(ft.fill.SecurityKey, k);
|
||||
void _update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
|
||||
HashSet<uint> changed_keys = new HashSet<uint>();
|
||||
foreach (var o in orders.removed) {
|
||||
if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
OrderRecordImmutable? curr_order = null;
|
||||
if (orders.try_get(o.Key, out OrderRecordImmutable newval)) {
|
||||
curr_order = newval;
|
||||
changed_keys.Add(o.Key);
|
||||
}
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
foreach (FillImmutable fill in fills) {
|
||||
SecurityMD? sec_md = _get_security_md(fill.SecurityKey);
|
||||
FillTuple old_ft = new FillTuple(fill, o, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, curr_order, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var ft in added) {
|
||||
string k = ft.fill.FillKey;
|
||||
if (!removed_keys.Contains(k)) {
|
||||
order_fills.add(ft.fill.OrderKey, k);
|
||||
security_fills.add(ft.fill.SecurityKey, k);
|
||||
foreach (var o in orders.added) {
|
||||
if (!changed_keys.Contains(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
foreach (FillImmutable fill in fills) {
|
||||
SecurityMD? sec_md = _get_security_md(fill.SecurityKey);
|
||||
FillTuple old_ft = new FillTuple(fill, null, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, o, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new State(fills.state, _orders, _securities, _fill_tuples.update(removed, added, f => f.key), order_fills, security_fills);
|
||||
//
|
||||
FillTuple get_fill_tuple(FillImmutable fill) =>
|
||||
new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(),
|
||||
_securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
_orders_cache = orders.state;
|
||||
_apply_changes(added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
public State update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
|
||||
orders.detect_changed_keys(o => o.Key, out HashSet<uint> removed_keys, out HashSet<uint> added_keys, out HashSet<uint> changed_keys);
|
||||
foreach (var k in removed_keys) {
|
||||
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
|
||||
added.AddRange(get_fill_tuples(null, fill_keys));
|
||||
}
|
||||
}
|
||||
foreach (var k in added_keys) {
|
||||
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(null, fill_keys));
|
||||
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
|
||||
}
|
||||
}
|
||||
foreach (var k in changed_keys) {
|
||||
if (_order_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(_orders[k], fill_keys));
|
||||
added.AddRange(get_fill_tuples(orders.get(k), fill_keys));
|
||||
}
|
||||
}
|
||||
return new State(_fills, orders.state, _securities, _fill_tuples.update(removed, added, ft => ft.key), _order_fills, _security_fills);
|
||||
//
|
||||
IEnumerable<FillTuple> get_fill_tuples(OrderRecordImmutable? o, ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return new FillTuple(fill, o, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
});
|
||||
}
|
||||
|
||||
public State update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
List<FillTuple> added = new List<FillTuple>(), removed = new List<FillTuple>();
|
||||
_securities.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
void _update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
|
||||
_security_md_cache.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
foreach (var k in removed_keys) {
|
||||
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(_securities[k].security, fill_keys));
|
||||
added.AddRange(get_fill_tuples(null, fill_keys));
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD old_sec_md = _security_md_cache[k];
|
||||
foreach (FillImmutable fill in fills) {
|
||||
OrderRecordImmutable? order = _get_order(fill.OrderKey);
|
||||
FillTuple old_ft = new FillTuple(fill, order, old_sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, order, null);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var k in added_keys) {
|
||||
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
removed.AddRange(get_fill_tuples(null, fill_keys));
|
||||
added.AddRange(get_fill_tuples(securities[k].security, fill_keys));
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD new_sec_md = securities[k];
|
||||
foreach (FillImmutable fill in fills) {
|
||||
OrderRecordImmutable? order = _get_order(fill.OrderKey);
|
||||
FillTuple old_ft = new FillTuple(fill, order, null);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, order, new_sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new State(_fills, _orders, securities, _fill_tuples.update(removed, added, f => f.key), _order_fills, _security_fills);
|
||||
//
|
||||
IEnumerable<FillTuple> get_fill_tuples(SecurityImmutable? s, ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(), s);
|
||||
});
|
||||
_security_md_cache = securities;
|
||||
_apply_changes(added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
SecurityMD? _get_security_md(uint key) =>
|
||||
_security_md_cache.TryGetValue(key, out SecurityMD smd) ? smd : new SecurityMD?();
|
||||
|
||||
OrderRecordImmutable? _get_order(uint key) =>
|
||||
_orders_cache.TryGetValue(key, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?();
|
||||
|
||||
void _apply_changes(Dictionary<FillTupleKey, FillTuple> added, Dictionary<FillTupleKey, FillTuple> removed) {
|
||||
foreach (var kv in removed)
|
||||
_state.Remove(kv.Key);
|
||||
foreach (var kv in added)
|
||||
_state.Add(kv.Key, kv.Value);
|
||||
_subject.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(added, removed));
|
||||
}
|
||||
|
||||
IDisposable _read() {
|
||||
while (_n_write_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_read_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
|
||||
}
|
||||
|
||||
IDisposable _write() {
|
||||
while (_n_read_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_write_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,8 @@ namespace mbts.spark {
|
||||
IObservable<InlineOrderTicketProperties> props_source,
|
||||
IObservable<int[]> calculator_buttons
|
||||
) {
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source, props_source.Select(p => p.order_props));
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
|
||||
bbo = new BBOVM(security_md_source);
|
||||
send_order = new DelegateCommand<Side>(side => {
|
||||
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
||||
@@ -61,7 +60,7 @@ namespace mbts.spark {
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.ObserveOnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
|
||||
void on_props(InlineUserProperty<double> price_prop, InlineUserProperty<uint> size_prop) {
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace bts.spark {
|
||||
(var security, var spec) = get_security(neworder.Def.SecurityKey);
|
||||
//
|
||||
// description
|
||||
var mod_desc = modified_description(oldorder, neworder, spec.Value);
|
||||
var mod_desc = modified_description(oldorder, neworder, spec);
|
||||
if (!string.IsNullOrEmpty(mod_desc)) {
|
||||
log_status(ActivityLogCategory.modify, security, mod_desc);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate StopLimitOrderTemplate { get; set; }
|
||||
public DataTemplate StopMarketOrderTemplate { get; set; }
|
||||
public DataTemplate TimeBoxOrderTemplate { get; set; }
|
||||
public DataTemplate TickOrderTemplate { get; set; }
|
||||
public DataTemplate LeanOrderTemplate { get; set; }
|
||||
public DataTemplate JoinOrderTemplate { get; set; }
|
||||
public DataTemplate TopOrderTemplate { get; set; }
|
||||
public DataTemplate TriggerOrderTemplate { get; set; }
|
||||
public DataTemplate IcebergOrderTemplate { get; set; }
|
||||
public DataTemplate SpreaderTemplate { get; set; }
|
||||
public DataTemplate BTSStopOrderTemplate { get; set; }
|
||||
@@ -33,6 +38,11 @@ namespace mbts.ui.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Stop: return StopMarketOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.StopLimit: return StopLimitOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.TimeBox: return TimeBoxOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Tick: return TickOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Lean: return LeanOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: return JoinOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top: return TopOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return TriggerOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace mbts.spark {
|
||||
class InlineOrderProperties {
|
||||
@@ -44,9 +43,10 @@ namespace mbts.spark {
|
||||
readonly SelectorProperty<IOrderTypeVM> _order_type;
|
||||
IDisposable _order_type_param_errors_sub;
|
||||
|
||||
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityImmutable> security_source, IObservable<InlineOrderProperties> order_properties) {
|
||||
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityMD> security_md_source, IObservable<InlineOrderProperties> order_properties) {
|
||||
_model = model;
|
||||
_security = backing(nameof(security), security_source.ObserveOnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
_security = backing(nameof(security), security_source.OnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
// guarentee that "this.security" will always be set before observers are notified
|
||||
// this is important bc some observers will reference "this.security" in their callbacks
|
||||
// - namely the [SelectorProperty]order_type's "get_default" function,
|
||||
@@ -75,7 +75,7 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(() => _order_type.Changed -= on_selected_order_type_changed);
|
||||
// local methods
|
||||
IOrderTypeVM create_order_type_vm(CustomOrderDefinitionImmutable cd) {
|
||||
var vm = OrderTypeVMFactory.create(cd.CustomCase, model, security_source);
|
||||
var vm = OrderTypeVMFactory.create(cd.CustomCase, model, security_md_source);
|
||||
vm.load_from(cd);
|
||||
return vm;
|
||||
}
|
||||
@@ -136,6 +136,7 @@ namespace mbts.spark {
|
||||
protected override void Dispose(bool disposing) {
|
||||
base.Dispose(disposing);
|
||||
Disposable.dispose(ref _order_type_param_errors_sub);
|
||||
_order_type.items.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
class IcebergOrderVM : OrderTypeVMBase<IcebergOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable {
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -34,19 +34,19 @@ namespace mbts.spark {
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public IcebergOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineIcebergOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public IcebergOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineIcebergOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public IcebergOrderVM(OrdersModel model,
|
||||
IObservable<SecurityImmutable> security_source,
|
||||
IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineIcebergOrderProperties> inline_properties)
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
@@ -68,7 +68,7 @@ namespace mbts.spark {
|
||||
public ICollection<IOrderTypeVM> child_order_types {
|
||||
get {
|
||||
if (_child_order_choices == null) {
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
}
|
||||
return _child_order_choices;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineJoinOrderProperties
|
||||
{
|
||||
public InlineJoinOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
var hedgetype = CustomOneofCase.Lean;
|
||||
if(!child_order_types.Contains(hedgetype))
|
||||
hedgetype = CustomOneofCase.Limit;
|
||||
|
||||
//ExceptionAssert.InvalidOperation.That(child_order_types.Contains(hedgetype), $"Hedge order type {hedgetype} is not in hedge_order_types");
|
||||
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
|
||||
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
|
||||
join_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
enable_hedge = new InlineUserProperty<bool>(false, false);
|
||||
hedge_ticks = new InlineUserProperty<int>(1, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
|
||||
|
||||
|
||||
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> join_threshold;
|
||||
|
||||
public readonly InlineUserProperty<bool> enable_hedge;
|
||||
public readonly InlineUserProperty<int> hedge_ticks;
|
||||
|
||||
}
|
||||
|
||||
class JoinOrderVM : OrderTypeVMBase<JoinOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineJoinOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
|
||||
IOrderTypeVM _hedge_order;
|
||||
IDisposable _hedge_order_errors_sub;
|
||||
|
||||
public uint tick_offset_on_deploy { get; set; } = 0;
|
||||
public bts.spark.QuantityMeasure join_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
bool _enable_hedge = false;
|
||||
public bool enable_hedge
|
||||
{
|
||||
get { return _enable_hedge; }
|
||||
set
|
||||
{
|
||||
_enable_hedge = value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
}
|
||||
public int hedge_ticks { get; set; } = 1;
|
||||
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineJoinOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public JoinOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineJoinOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
|
||||
{
|
||||
_select_hedge_order(p.selected_hedge_order_type.value);
|
||||
}
|
||||
if (p.tick_offset_on_deploy.force_override)
|
||||
{
|
||||
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
|
||||
RaisePropertyChanged(nameof(tick_offset_on_deploy));
|
||||
}
|
||||
if (p.join_threshold.force_override)
|
||||
{
|
||||
join_threshold = p.join_threshold.value;
|
||||
RaisePropertyChanged(nameof(join_threshold));
|
||||
}
|
||||
if (p.enable_hedge.force_override)
|
||||
{
|
||||
enable_hedge = p.enable_hedge.value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
if (p.hedge_ticks.force_override)
|
||||
{
|
||||
hedge_ticks = p.hedge_ticks.value;
|
||||
RaisePropertyChanged(nameof(hedge_ticks));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
if (_hedge_order == null && enable_hedge)
|
||||
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.join_order();
|
||||
i.TickOffsetOnDeploy = tick_offset_on_deploy;
|
||||
i.JoinThreshold = join_threshold;
|
||||
i.EnableHedge = enable_hedge;
|
||||
i.HedgeTicks = hedge_ticks;
|
||||
child_order.assign_to(i.Child);
|
||||
if(enable_hedge && hedge_order != null)
|
||||
hedge_order.assign_to(i.Hedge);
|
||||
value.Join = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> hedge_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM hedge_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _hedge_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _hedge_order_errors_sub);
|
||||
_hedge_order = value;
|
||||
_hedge_order?.init();
|
||||
if (_hedge_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
if (_hedge_order_choices != null)
|
||||
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
if (_hedge_order == null && _security.HasValue && enable_hedge)
|
||||
_select_hedge_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _select_hedge_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
|
||||
_inline_props.selected_hedge_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_hedge_order(ot);
|
||||
}
|
||||
|
||||
void _select_hedge_order(CustomOneofCase ot)
|
||||
{
|
||||
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_hedge_order == null)
|
||||
_hedge_order = hedge_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
|
||||
if ( (_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
|
||||
set_error(nameof(hedge_order), string.Empty, true);
|
||||
else clear_error(nameof(hedge_order));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineLeanOrderProperties
|
||||
{
|
||||
public InlineLeanOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(10, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
|
||||
public readonly InlineUserProperty<uint> max_chase_ticks;
|
||||
}
|
||||
|
||||
class LeanOrderVM : OrderTypeVMBase<LeanOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineLeanOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineLeanOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineLeanOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (p.cross_threshold.force_override)
|
||||
{
|
||||
cross_threshold = p.cross_threshold.value;
|
||||
RaisePropertyChanged(nameof(cross_threshold));
|
||||
}
|
||||
if (p.max_chase_ticks.force_override)
|
||||
{
|
||||
max_chase_ticks = p.max_chase_ticks.value;
|
||||
RaisePropertyChanged(nameof(max_chase_ticks));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.lean_order();
|
||||
i.CrossThreshold = cross_threshold;
|
||||
i.MaxChaseTicks = max_chase_ticks;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Lean = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
@@ -12,23 +13,30 @@ namespace mbts.spark {
|
||||
public static IOrderTypeVM market(OrdersModel model) => new MarketOrderVM(model);
|
||||
public static IOrderTypeVM limit(OrdersModel model) => new LimitOrderVM(model);
|
||||
public static IOrderTypeVM stop(OrdersModel model) => new StopOrderVM(model);
|
||||
public static IOrderTypeVM stop_limit(OrdersModel model) => new StopLimitOrderVM(model);
|
||||
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityImmutable> security_source) => new TimeBoxOrderVM(model, security_source);
|
||||
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityImmutable> security_source) => new IcebergOrderVM(model, security_source);
|
||||
public static IOrderTypeVM stop_limit(OrdersModel model, IObservable<SecurityMD> security_md_source) => new StopLimitOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM time_box(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TimeBoxOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM tick(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TickOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM lean(OrdersModel model, IObservable<SecurityMD> security_md_source) => new LeanOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityMD> security_md_source) => new JoinOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TopOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM trigger(OrdersModel model, IObservable<SecurityMD> security_md_source) => new TriggerOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityMD> security_md_source) => new IcebergOrderVM(model, security_md_source);
|
||||
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source) => new SpreaderOrderVM(model, security_source);
|
||||
|
||||
static Func<CustomOneofCase, IOrderTypeVM> _get_factory(OrdersModel orders_model, IObservable<SecurityImmutable> security_source) =>
|
||||
new Func<CustomOneofCase, IOrderTypeVM>(ot => create(ot, orders_model, security_source));
|
||||
|
||||
public static IOrderTypeVM create(CustomOneofCase order_type, OrdersModel model, IObservable<SecurityImmutable> security_source) {
|
||||
public static IOrderTypeVM create(CustomOneofCase order_type, OrdersModel model, IObservable<SecurityMD> security_md_source) {
|
||||
switch (order_type) {
|
||||
case CustomOneofCase.Market: return market(model);
|
||||
case CustomOneofCase.Limit: return limit(model);
|
||||
case CustomOneofCase.Stop: return stop(model);
|
||||
case CustomOneofCase.StopLimit: return stop_limit(model);
|
||||
case CustomOneofCase.TimeBox: return time_box(model, security_source);
|
||||
case CustomOneofCase.Iceberg: return iceberg(model, security_source);
|
||||
case CustomOneofCase.Spreader: return spreader(model, security_source);
|
||||
case CustomOneofCase.StopLimit: return stop_limit(model, security_md_source);
|
||||
case CustomOneofCase.TimeBox: return time_box(model, security_md_source);
|
||||
case CustomOneofCase.Tick: return tick(model, security_md_source);
|
||||
case CustomOneofCase.Lean: return lean(model, security_md_source);
|
||||
case CustomOneofCase.Join: return join(model, security_md_source);
|
||||
case CustomOneofCase.Top: return top(model, security_md_source);
|
||||
case CustomOneofCase.Trigger: return trigger(model, security_md_source);
|
||||
case CustomOneofCase.Iceberg: return iceberg(model, security_md_source);
|
||||
case CustomOneofCase.Spreader: return spreader(model, security_md_source.Select(smd => smd.security));
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
private class SpreaderOrderVM : OrderTypeVMBase<SpreaderOrderDefinition>, IOrderTypeVM, IDisposable {
|
||||
readonly Property<SpreaderOrderDefinitionImmutable?> _template;
|
||||
readonly StrategyEditorWrapper _strategy_editor_wrapper;
|
||||
IDisposable _dispose;
|
||||
SyntheticEditorVM _strategy_editor;
|
||||
|
||||
public SpreaderOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.SelectMany(s => model.spreader.security_strategy(s.Id)
|
||||
@@ -26,15 +28,15 @@ namespace mbts.spark {
|
||||
IObservable<InlineUserProperty<SpreaderOrderDefinitionImmutable>> order_template_source)
|
||||
: base(model) {
|
||||
_template = backing(nameof(template), new SpreaderOrderDefinitionImmutable?());
|
||||
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
|
||||
_dispose = Disposable.Create(
|
||||
_template.ObserveOnDispatcher().Subscribe(vals => {
|
||||
_strategy_editor_wrapper,
|
||||
_template.OnDispatcher().Subscribe(vals => {
|
||||
_validate();
|
||||
}),
|
||||
order_template_source.ObserveOnDispatcher().Subscribe(prop => {
|
||||
order_template_source.OnDispatcher().Subscribe(prop => {
|
||||
if (prop.force_override || (!template.HasValue && prop.has_value)) {
|
||||
_template.Value = prop.value;
|
||||
if (_strategy_editor != null)
|
||||
_strategy_editor.set_synthetic(prop.value.Security, prop.value.ExecutionStrategy);
|
||||
_validate();
|
||||
}
|
||||
})
|
||||
@@ -43,23 +45,14 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public SpreaderOrderDefinitionImmutable? template => _template.Value;
|
||||
|
||||
public SyntheticEditorVM strategy_editor {
|
||||
get {
|
||||
if (_strategy_editor == null && template.HasValue) {
|
||||
var def = template.Value;
|
||||
_strategy_editor = new SyntheticEditorVM(model.spreader, def.Security, def.ExecutionStrategy, model.security_has_working_orders(def.Security.Id));
|
||||
}
|
||||
return _strategy_editor;
|
||||
}
|
||||
}
|
||||
|
||||
public SyntheticEditorVM strategy_editor => _strategy_editor_wrapper.vm;
|
||||
public IEnumerable<DialogSkirtItem> skirt_items => _strategy_editor_wrapper.skirt_items;
|
||||
public override bool editable_properties_open {
|
||||
get => base.editable_properties_open;
|
||||
set {
|
||||
if (!value && template.HasValue && _strategy_editor != null) {
|
||||
if (!value && template.HasValue && strategy_editor != null) {
|
||||
var mutable_template = template.Value.CloneToMutable();
|
||||
mutable_template.ExecutionStrategy = _strategy_editor.get_strategy().CloneToMutable();
|
||||
mutable_template.ExecutionStrategy = _strategy_editor_wrapper.vm.get_strategy().CloneToMutable();
|
||||
_template.Value = new SpreaderOrderDefinitionImmutable(mutable_template);
|
||||
}
|
||||
base.editable_properties_open = value;
|
||||
@@ -80,6 +73,39 @@ namespace mbts.spark {
|
||||
set_error(nameof(template), "Waiting for security and/or strategy.", true);
|
||||
else clear_error(nameof(template));
|
||||
}
|
||||
|
||||
// lazily initializes SyntheticEditorVM and associated dialog skirt items
|
||||
class StrategyEditorWrapper : IDisposable {
|
||||
readonly SpreaderOrderVM _owner;
|
||||
SyntheticEditorVM _vm;
|
||||
DialogSkirtItem _save_strategy_item;
|
||||
|
||||
public StrategyEditorWrapper(SpreaderOrderVM owner) {
|
||||
_owner = owner;
|
||||
_owner._template.Changed += _order_template_changed;
|
||||
}
|
||||
|
||||
public SyntheticEditorVM vm { get { _init(); return _vm; } }
|
||||
public IEnumerable<DialogSkirtItem> skirt_items { get { _init(); yield return _save_strategy_item; } }
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
_owner._template.Changed -= _order_template_changed;
|
||||
}
|
||||
|
||||
void _init() {
|
||||
if (_vm == null && _owner.template.HasValue) {
|
||||
var def = _owner.template.Value;
|
||||
_vm = new SyntheticEditorVM(_owner.model.spreader, def.Security, def.ExecutionStrategy, _owner.model.security_has_working_orders(def.Security.Id));
|
||||
_save_strategy_item = new DialogSkirtItem("Save Strategy", _vm.commit);
|
||||
}
|
||||
}
|
||||
|
||||
void _order_template_changed(SpreaderOrderDefinitionImmutable? oldval, SpreaderOrderDefinitionImmutable? newval) {
|
||||
if (_vm != null && newval.HasValue)
|
||||
_vm.set_synthetic(newval.Value.Security, newval.Value.ExecutionStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,61 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
private class StopLimitOrderVM : OrderTypeVMBase<StopLimitOrderDefinition> {
|
||||
double _stop_price = 0;
|
||||
class InlineStopLimitOrderProperties {
|
||||
readonly Func<InlineUserProperty<double>> _get_stop_price;
|
||||
|
||||
internal StopLimitOrderVM(OrdersModel model) : base(model) { }
|
||||
public static IObservable<InlineStopLimitOrderProperties> subscribe(IObservable<SecurityMD> security_md_source) {
|
||||
ulong time = 0;
|
||||
return security_md_source.Select(smd => new InlineStopLimitOrderProperties(() => {
|
||||
smd.md.get_bbo(ref time, out NativePriceSize bid, out NativePriceSize ask);
|
||||
return new InlineUserProperty<double>(bid.price);
|
||||
}));
|
||||
}
|
||||
|
||||
public InlineStopLimitOrderProperties(Func<InlineUserProperty<double>> get_stop_price) =>
|
||||
_get_stop_price = get_stop_price;
|
||||
|
||||
public InlineUserProperty<double> stop_price => _get_stop_price();
|
||||
}
|
||||
|
||||
private class StopLimitOrderVM : OrderTypeVMBase<StopLimitOrderDefinition>, IDisposable {
|
||||
UserProperty<double> _stop_price;
|
||||
InlineStopLimitOrderProperties _inline_props;
|
||||
IDisposable _dispose;
|
||||
|
||||
internal StopLimitOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source.Select(smd => smd.security.spec_or_default()), InlineStopLimitOrderProperties.subscribe(security_md_source)) { }
|
||||
|
||||
internal StopLimitOrderVM(OrdersModel model, IObservable<ContractSpecificationImmutable> spec_source, IObservable<InlineStopLimitOrderProperties> inline_properties) : base(model) {
|
||||
_stop_price = new UserProperty<double>(backing(nameof(stop_price), double.NaN));
|
||||
_dispose = Disposable.Create(
|
||||
spec_source.OnDispatcher().Subscribe(s => {
|
||||
spec = s;
|
||||
RaisePropertyChanged(nameof(spec));
|
||||
}), inline_properties.OnDispatcher().Subscribe(p => _inline_props = p));
|
||||
}
|
||||
|
||||
public override bool editable_properties_open {
|
||||
get => base.editable_properties_open;
|
||||
set {
|
||||
if (value) {
|
||||
if (_update_inline_props())
|
||||
_validate();
|
||||
}
|
||||
base.editable_properties_open = value;
|
||||
}
|
||||
}
|
||||
|
||||
public double stop_price {
|
||||
get => _stop_price;
|
||||
get => _stop_price.Value;
|
||||
set {
|
||||
_stop_price = value;
|
||||
_stop_price.Value = value;
|
||||
_validate();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +64,8 @@ namespace mbts.spark {
|
||||
SecurityDataHelper.Defaults.ContractSpecificationImmutable;
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value) {
|
||||
_update_inline_props();
|
||||
_validate();
|
||||
if (!HasErrors) {
|
||||
var sl = factory.stop_limit_order();
|
||||
sl.StopPrice = stop_price;
|
||||
@@ -28,9 +73,22 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
// no need to constantly poll the md, just check the inline props when the popup opens, and right before sending an order
|
||||
// (if the user never looks at the popup, assume that the bid_price will be used)
|
||||
// TODO: add settings so the trigger-price can default to something relative to the md, e.g "bid-price" or "ask-price - nticks" or "best-bid-price with a minimum size of 10"
|
||||
bool _update_inline_props() {
|
||||
var inline_stop_price = _inline_props.stop_price;
|
||||
if (inline_stop_price.has_value && (inline_stop_price.force_override || !_stop_price.setter_called)) {
|
||||
_stop_price.bypass_setter(inline_stop_price.value);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
void _validate() {
|
||||
using (validation())
|
||||
validation_assert(!DoubleHelper.IsNaNOrInfinity(_stop_price), string.Empty, nameof(stop_price));
|
||||
validation_assert(!DoubleHelper.IsNaNOrInfinity(stop_price), string.Empty, nameof(stop_price));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineTickOrderProperties
|
||||
{
|
||||
public InlineTickOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
cross_ticks = new InlineUserProperty<uint>(0, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(10, false);
|
||||
detach_on_toward = new InlineUserProperty<bool>(true, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> cross_threshold;
|
||||
public readonly InlineUserProperty<uint> cross_ticks;
|
||||
public readonly InlineUserProperty<uint> max_chase_ticks;
|
||||
public readonly InlineUserProperty<bool> detach_on_toward;
|
||||
}
|
||||
|
||||
class TickOrderVM : OrderTypeVMBase<TickOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTickOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public uint tick_offset_on_deploy { get; set; } = 0;
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint cross_ticks { get; set; } = 0;
|
||||
public uint max_chase_ticks { get; set; } = 10;
|
||||
public bool detach_on_toward { get; set; } = true;
|
||||
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTickOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public TickOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTickOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (p.tick_offset_on_deploy.force_override)
|
||||
{
|
||||
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
|
||||
RaisePropertyChanged(nameof(tick_offset_on_deploy));
|
||||
}
|
||||
if (p.cross_threshold.force_override)
|
||||
{
|
||||
cross_threshold = p.cross_threshold.value;
|
||||
RaisePropertyChanged(nameof(cross_threshold));
|
||||
}
|
||||
if (p.cross_ticks.force_override)
|
||||
{
|
||||
cross_ticks = p.cross_ticks.value;
|
||||
RaisePropertyChanged(nameof(cross_ticks));
|
||||
}
|
||||
if (p.max_chase_ticks.force_override)
|
||||
{
|
||||
max_chase_ticks = p.max_chase_ticks.value;
|
||||
RaisePropertyChanged(nameof(max_chase_ticks));
|
||||
}
|
||||
if (p.detach_on_toward.force_override)
|
||||
{
|
||||
detach_on_toward = p.detach_on_toward.value;
|
||||
RaisePropertyChanged(nameof(detach_on_toward));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.tick_order();
|
||||
i.TickOffsetOnDeploy = tick_offset_on_deploy;
|
||||
i.CrossThreshold = cross_threshold;
|
||||
i.CrossTicks = cross_ticks;
|
||||
i.MaxChaseTicks = max_chase_ticks;
|
||||
i.DetachOnToward = detach_on_toward;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Tick = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace mbts.spark {
|
||||
|
||||
class TimeBoxOrderVM : OrderTypeVMBase<TimeBoxOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable {
|
||||
const double _start_immediately_delay_ms = 500;
|
||||
readonly IObservable<SecurityImmutable> _security_source;
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
@@ -40,19 +40,19 @@ namespace mbts.spark {
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public TimeBoxOrderVM(OrdersModel model, IObservable<SecurityImmutable> security_source)
|
||||
: this(model, security_source, security_source.Select(s =>
|
||||
new InlineTimeBoxOrderProperties(model.factory.default_order_type(s), model.factory.default_order_types(s).ToList()))) { }
|
||||
public TimeBoxOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTimeBoxOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public TimeBoxOrderVM(OrdersModel model,
|
||||
IObservable<SecurityImmutable> security_source,
|
||||
IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTimeBoxOrderProperties> inline_properties)
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
@@ -94,7 +94,7 @@ namespace mbts.spark {
|
||||
public ICollection<IOrderTypeVM> child_order_types {
|
||||
get {
|
||||
if (_child_order_choices == null) {
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_source));
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
}
|
||||
return _child_order_choices;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineTopOrderProperties
|
||||
{
|
||||
public InlineTopOrderProperties(CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
var hedgetype = CustomOneofCase.Lean;
|
||||
if(!child_order_types.Contains(hedgetype))
|
||||
hedgetype = CustomOneofCase.Limit;
|
||||
this.selected_hedge_order_type = new InlineUserProperty<CustomOneofCase>(hedgetype, true);
|
||||
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
watch_level_tick_offset = new InlineUserProperty<uint>(0, false);
|
||||
top_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 }, false);
|
||||
enable_hedge = new InlineUserProperty<bool>(false, false);
|
||||
hedge_ticks = new InlineUserProperty<int>(1, false);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_hedge_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> hedge_order_types;
|
||||
|
||||
|
||||
public readonly InlineUserProperty<uint> watch_level_tick_offset;
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> top_threshold;
|
||||
|
||||
public readonly InlineUserProperty<bool> enable_hedge;
|
||||
public readonly InlineUserProperty<int> hedge_ticks;
|
||||
|
||||
}
|
||||
|
||||
class TopOrderVM : OrderTypeVMBase<TopOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTopOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _hedge_order_choices;
|
||||
IOrderTypeVM _hedge_order;
|
||||
IDisposable _hedge_order_errors_sub;
|
||||
|
||||
public uint watch_level_tick_offset { get; set; } = 0;
|
||||
public bts.spark.QuantityMeasure top_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
bool _enable_hedge = false;
|
||||
public bool enable_hedge
|
||||
{
|
||||
get { return _enable_hedge; }
|
||||
set
|
||||
{
|
||||
_enable_hedge = value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
}
|
||||
public int hedge_ticks { get; set; } = 1;
|
||||
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTopOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList())))
|
||||
{ }
|
||||
|
||||
public TopOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTopOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p =>
|
||||
{
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
|
||||
{
|
||||
_select_hedge_order(p.selected_hedge_order_type.value);
|
||||
}
|
||||
if (p.watch_level_tick_offset.force_override)
|
||||
{
|
||||
watch_level_tick_offset = p.watch_level_tick_offset.value;
|
||||
RaisePropertyChanged(nameof(watch_level_tick_offset));
|
||||
}
|
||||
if (p.top_threshold.force_override)
|
||||
{
|
||||
top_threshold = p.top_threshold.value;
|
||||
RaisePropertyChanged(nameof(top_threshold));
|
||||
}
|
||||
if (p.enable_hedge.force_override)
|
||||
{
|
||||
enable_hedge = p.enable_hedge.value;
|
||||
RaisePropertyChanged(nameof(enable_hedge));
|
||||
}
|
||||
if (p.hedge_ticks.force_override)
|
||||
{
|
||||
hedge_ticks = p.hedge_ticks.value;
|
||||
RaisePropertyChanged(nameof(hedge_ticks));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
if (_hedge_order == null && enable_hedge)
|
||||
_select_hedge_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.top_order();
|
||||
i.WatchLevelTickOffset = watch_level_tick_offset;
|
||||
i.TopThreshold = top_threshold;
|
||||
i.EnableHedge = enable_hedge;
|
||||
i.HedgeTicks = hedge_ticks;
|
||||
child_order.assign_to(i.Child);
|
||||
if (enable_hedge && hedge_order != null)
|
||||
hedge_order.assign_to(i.Hedge);
|
||||
value.Top = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> hedge_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM hedge_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _hedge_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _hedge_order_errors_sub);
|
||||
_hedge_order = value;
|
||||
_hedge_order?.init();
|
||||
if (_hedge_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_hedge_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
if (_hedge_order_choices != null)
|
||||
_hedge_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
if (_hedge_order == null && _security.HasValue && enable_hedge)
|
||||
_select_hedge_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _select_hedge_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_hedge_order_type.has_value ?
|
||||
_inline_props.selected_hedge_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_hedge_order(ot);
|
||||
}
|
||||
|
||||
void _select_hedge_order(CustomOneofCase ot)
|
||||
{
|
||||
_hedge_order = hedge_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_hedge_order == null)
|
||||
_hedge_order = hedge_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(hedge_order));
|
||||
}
|
||||
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
|
||||
if ((_hedge_order == null && enable_hedge) || (_hedge_order is INotifyDataErrorInfo hdei && hdei.HasErrors))
|
||||
set_error(nameof(hedge_order), string.Empty, true);
|
||||
else clear_error(nameof(hedge_order));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
class InlineTriggerOrderProperties
|
||||
{
|
||||
public InlineTriggerOrderProperties (CustomOneofCase selected_child_order_type, IReadOnlyCollection<CustomOneofCase> child_order_types)
|
||||
{
|
||||
ExceptionAssert.InvalidOperation.That(child_order_types.Contains(selected_child_order_type), $"Child order type {selected_child_order_type} is not in child_order_types");
|
||||
this.selected_child_order_type = new InlineUserProperty<CustomOneofCase>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<CustomOneofCase>>(child_order_types, true);
|
||||
|
||||
trigger_price_type = new InlineUserProperty<TriggerPriceType>(TriggerPriceType.Bid, false);
|
||||
order_price = new InlineUserProperty<double>(double.NaN, true);
|
||||
}
|
||||
public readonly InlineUserProperty<CustomOneofCase> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<CustomOneofCase>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<TriggerPriceType> trigger_price_type;
|
||||
public readonly InlineUserProperty<double> order_price;
|
||||
}
|
||||
|
||||
class TriggerOrderVM : OrderTypeVMBase<TriggerOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<CustomOneofCase> _order_types = new ObservableCollection<CustomOneofCase>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase> _child_order_choices;
|
||||
InlineTriggerOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public TriggerPriceType trigger_price_type { get; set; } = TriggerPriceType.Bid;
|
||||
public double order_price { get; set; } = double.NaN;
|
||||
|
||||
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineTriggerOrderProperties(model.factory.default_order_type(smd.security), model.factory.default_order_types(smd.security).ToList()))) { }
|
||||
|
||||
public TriggerOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineTriggerOrderProperties> inline_properties) : base(model)
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
}
|
||||
if (_child_order_choices != null && (_child_order == null || p.selected_child_order_type.force_override))
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value);
|
||||
}
|
||||
if (p.trigger_price_type.force_override)
|
||||
{
|
||||
trigger_price_type = p.trigger_price_type.value;
|
||||
RaisePropertyChanged(nameof(trigger_price_type));
|
||||
}
|
||||
if (p.order_price.force_override)
|
||||
{
|
||||
order_price = p.order_price.value;
|
||||
RaisePropertyChanged(nameof(order_price));
|
||||
}
|
||||
_inline_props = p;
|
||||
_validate();
|
||||
})
|
||||
);
|
||||
_validate();
|
||||
}
|
||||
|
||||
public IEnumerable<TriggerPriceType> trigger_price_types => EnumHelper.get_values_except<TriggerPriceType>(TriggerPriceType.LastTradePrice);
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
if (_child_order == null)
|
||||
_select_child_order(model.factory.default_order_type(_security.Value, true));
|
||||
_validate();
|
||||
if (!HasErrors)
|
||||
{
|
||||
var i = factory.trigger_order();
|
||||
i.TriggerCondition = new TriggerCondition()
|
||||
{
|
||||
PriceType = trigger_price_type,
|
||||
AbsolutePriceTrigger = new AbsolutePriceTrigger() { TriggerQuantity = new TriggerQuantity() }
|
||||
};
|
||||
i.TriggerAction = new TriggerAction()
|
||||
{
|
||||
Price = order_price,
|
||||
SecurityId = _security.Value.Id
|
||||
};
|
||||
child_order.assign_to(i.Child);
|
||||
value.Trigger = i;
|
||||
}
|
||||
}
|
||||
|
||||
public double price_tick
|
||||
{
|
||||
get { return _security.Value.ContractSpec.LowTick; }
|
||||
}
|
||||
|
||||
public ContractSpecificationImmutable spec
|
||||
{
|
||||
get { return _security.Value.ContractSpec; }
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, CustomOneofCase>(_order_types, ot => create(ot, model, _security_md_source));
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
{
|
||||
init();
|
||||
return _child_order;
|
||||
}
|
||||
set
|
||||
{
|
||||
Disposable.dispose(ref _child_order_errors_sub);
|
||||
_child_order = value;
|
||||
_child_order?.init();
|
||||
if (_child_order is INotifyDataErrorInfo ndei)
|
||||
{
|
||||
ndei.ErrorsChanged += on_errors_changed;
|
||||
void on_errors_changed(object s, DataErrorsChangedEventArgs e) =>
|
||||
_validate();
|
||||
_child_order_errors_sub = Disposable.Create(() => ndei.ErrorsChanged -= on_errors_changed);
|
||||
}
|
||||
_validate();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposable.dispose(ref _dispose);
|
||||
if (_child_order_choices != null)
|
||||
_child_order_choices.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
public override void init()
|
||||
{
|
||||
if (_child_order == null && _security.HasValue)
|
||||
_select_child_order();
|
||||
_validate();
|
||||
}
|
||||
|
||||
void _select_child_order()
|
||||
{
|
||||
CustomOneofCase ot = _inline_props != null && _inline_props.selected_child_order_type.has_value ?
|
||||
_inline_props.selected_child_order_type.value :
|
||||
model.factory.default_order_type(_security.Value, true);
|
||||
_select_child_order(ot);
|
||||
}
|
||||
|
||||
void _select_child_order(CustomOneofCase ot)
|
||||
{
|
||||
_child_order = child_order_types.FirstOrDefault(vm => vm.order_type == ot);
|
||||
if (_child_order == null)
|
||||
_child_order = child_order_types.FirstOrDefault();
|
||||
RaisePropertyChanged(nameof(child_order));
|
||||
}
|
||||
|
||||
void _validate()
|
||||
{
|
||||
if (_child_order == null || (_child_order is INotifyDataErrorInfo ndei && ndei.HasErrors))
|
||||
set_error(nameof(child_order), string.Empty, true);
|
||||
else clear_error(nameof(child_order));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,17 +31,18 @@ namespace mbts.spark {
|
||||
_service = service;
|
||||
_spreader = spreader;
|
||||
_error = error;
|
||||
on_orders = _service.subscribei_orders().Table().Replay(1).RefCount();
|
||||
on_orders = _service.subscribei_orders().Table().Cache();
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> empty_orders = new Dictionary<uint, OrderRecordImmutable>();
|
||||
working_orders =
|
||||
on_orders.Scan(empty_orders, (worders, table) => _update_working_orders(worders, table)).Replay(1).RefCount();
|
||||
on_fills = _service.subscribei_fills().Table().Replay(1).RefCount();
|
||||
on_fills = _service.subscribei_fills().Table().Cache();
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.Subscribe(_security_orders_demuxer),
|
||||
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
|
||||
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach( x => send_order(x.definition, x.price, x.size).ignore())),
|
||||
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
|
||||
@@ -53,7 +54,7 @@ namespace mbts.spark {
|
||||
public SpreaderModel spreader => _spreader;
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
|
||||
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
|
||||
public IObservable<ITableUpdateTransform<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
|
||||
|
||||
public IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.IsNotNull(Application.Current);
|
||||
Assert.IsNotNull(Application.Current.Windows);
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
|
||||
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
|
||||
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
|
||||
var thread_factory = new ShellThreadFactory();
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
|
||||
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
|
||||
Assert.AreEqual(Application.Current.Windows.Count, 0);
|
||||
|
||||
// an ISlowWindow gets its own thread
|
||||
|
||||
@@ -177,9 +177,9 @@ namespace mbts.api.Test {
|
||||
|
||||
|
||||
class ApiTestsNativeMethods {
|
||||
[DllImport("spark_lib")]
|
||||
[DllImport("libspark")]
|
||||
internal static extern IntPtr apitest_start_apitest_service(IntPtr context);
|
||||
[DllImport("spark_lib")]
|
||||
[DllImport("libspark")]
|
||||
internal static extern void apitest_stop_apitest_service(IntPtr server);
|
||||
|
||||
}
|
||||
|
||||
@@ -175,8 +175,8 @@ namespace bts.spark
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
|
||||
_tag = tag;
|
||||
_subs = Disposable.Create(
|
||||
parameter.ObserveOnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.ObserveOnDispatcher().Subscribe(ce => {
|
||||
parameter.OnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.OnDispatcher().Subscribe(ce => {
|
||||
_can_execute = ce;
|
||||
raiseCanExecuteChanged();
|
||||
})
|
||||
|
||||
@@ -102,5 +102,7 @@ namespace bts.spark
|
||||
SparkCommand.create("Cancel Sells", Icons.CancelSells, SparkTags.CancelOrders);
|
||||
public static PartialCommand<uint> ShowModifyOrder =>
|
||||
SparkCommand.create("Modify", Icons.ModifyOrder, SparkTags.ShowModifyOrderTag);
|
||||
public static PartialCommand<IEnumerable<(OrderDefinitionImmutable, double, uint)>> RecreateSelectedOrders =>
|
||||
SparkCommand.create("Recreate", Icons.Beaker, SparkTags.RecreateOrder);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using bts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using static mbts.spark.SparkBusData;
|
||||
|
||||
namespace mbts.spark
|
||||
@@ -18,6 +19,8 @@ namespace mbts.spark
|
||||
|
||||
#region Order
|
||||
public static ITag<(OrderDefinitionImmutable definition, double price, uint size)> CreateOrder { get; } = new SparkTag<(OrderDefinitionImmutable, double, uint)>();
|
||||
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
|
||||
|
||||
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> CrackOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
|
||||
@@ -34,6 +37,8 @@ namespace mbts.spark
|
||||
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
|
||||
|
||||
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
|
||||
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
|
||||
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
|
||||
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
|
||||
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
|
||||
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
|
||||
@@ -64,9 +69,11 @@ namespace mbts.spark
|
||||
public static ITag<SecurityCriteria> ShowTimeAndSalesTag { get; } = new SparkTag<SecurityCriteria>("Show TimeAndSales");
|
||||
#endregion
|
||||
|
||||
#region Persist
|
||||
public static ITag<PersistMessage> PersistCommandTag { get; } = new SparkTag<PersistMessage>("PersistCommandTag");
|
||||
public static ITag<PersistedItems> PersistedItemsTag { get; } = new SparkTag<PersistedItems>("PersistedItemsTag");
|
||||
#region Ladder
|
||||
public static ITag<Nothing> RequestRecenterLadder { get; } = new SparkTag<Nothing>(nameof(RequestRecenterLadder));
|
||||
public static ITag<DateTime> RecenterLadderBegin { get; } = new SparkTag<DateTime>(nameof(RecenterLadderBegin));
|
||||
public static ITag<DateTime> RecenterLadderNow { get; } = new SparkTag<DateTime>(nameof(RecenterLadderNow));
|
||||
|
||||
#endregion
|
||||
|
||||
public static ITag<uint> SyntheticDeleteTag { get; } = new SparkTag<uint>(nameof(SyntheticDeleteTag));
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using bts.api.core;
|
||||
using IronPython.Runtime.Types;
|
||||
using mbts;
|
||||
@@ -49,6 +50,9 @@ namespace bts.api.diagnostic {
|
||||
readonly StringBuilder _pending = new StringBuilder();
|
||||
readonly OutputPublisher _out;
|
||||
|
||||
readonly Thread _thread;
|
||||
Dispatcher _dispatcher;
|
||||
|
||||
public ShellScriptServer(ServiceContext context, ShellDiagnosticsServiceServer parent, string qualifier) : base(context, qualifier) {
|
||||
_parent = parent;
|
||||
_out = make_output_publisher();
|
||||
@@ -59,11 +63,19 @@ namespace bts.api.diagnostic {
|
||||
_scope.SetVariable("shell_objects", new Func<object,object>(o => get_exports(o,null)));
|
||||
_scope.SetVariable("shell_named_object", new Func<object,string,object>(get_export));
|
||||
_scope.SetVariable("shell_named_objects", new Func<object,string,object>(get_exports));
|
||||
|
||||
_thread = new Thread(_ => {
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
Thread.CurrentThread.Name = $"ShellScriptServer-{qualifier}";
|
||||
Dispatcher.Run();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public override Task<VoidMessage> dispose(VoidMessage request) {
|
||||
log("Finished", true);
|
||||
_parent.terminate(this);
|
||||
_dispatcher.BeginInvokeShutdown(DispatcherPriority.ApplicationIdle);
|
||||
return Task.FromResult(new VoidMessage());
|
||||
}
|
||||
|
||||
@@ -124,45 +136,58 @@ namespace bts.api.diagnostic {
|
||||
});
|
||||
}
|
||||
|
||||
Task<ScriptResultMessage> run_code(ScriptSource code) {
|
||||
ScriptResultMessage run_code(ScriptSource code) {
|
||||
try {
|
||||
object result = code.Execute(_scope);
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.Complete, Message = (result?.ToString()) ?? "" });
|
||||
return new ScriptResultMessage { Res = ScriptResult.Complete, Message = (result?.ToString()) ?? "" };
|
||||
}
|
||||
catch (Exception e) {
|
||||
//log($"Exception: {e.Message}");
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.RunError, Message = e.Message });
|
||||
return (new ScriptResultMessage { Res = ScriptResult.RunError, Message = e.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<ScriptResultMessage> run(ScriptLines request) {
|
||||
|
||||
// append lines to _pending
|
||||
foreach (var line in request.Lines) {
|
||||
_pending.AppendLine(line);
|
||||
}
|
||||
// CreateScriptSource from pending
|
||||
var code = _engine.CreateScriptSourceFromString(_pending.ToString().TrimStart(' ', '\n', '\r'), SourceCodeKind.InteractiveCode);
|
||||
var parse_result = code.GetCodeProperties();
|
||||
if (parse_result == ScriptCodeParseResult.IncompleteStatement && code.GetCode().EndsWith(Environment.NewLine+Environment.NewLine)) {
|
||||
parse_result = Microsoft.Scripting.ScriptCodeParseResult.Complete;
|
||||
}
|
||||
// todo: If code is 'incomplete' but we have 2 blank lines run it!
|
||||
switch (parse_result) {
|
||||
// If 'complete' run code
|
||||
case ScriptCodeParseResult.Complete:
|
||||
_pending.Clear();
|
||||
return run_code(code);
|
||||
// if 'invalid' return error
|
||||
case ScriptCodeParseResult.Invalid:
|
||||
Console.WriteLine("Invalid");
|
||||
_pending.Clear();
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.Invalid, Message = code.GetCode() });
|
||||
// otherwise continue buffering
|
||||
default:
|
||||
Console.WriteLine("Incomplete");
|
||||
return Task.FromResult(new ScriptResultMessage { Res = ScriptResult.Incomplete });
|
||||
var tcs = new TaskCompletionSource<ScriptResultMessage>();
|
||||
void ret(ScriptResultMessage r) {
|
||||
tcs.TrySetResult(r);
|
||||
}
|
||||
|
||||
_dispatcher.BeginInvoke(() => {
|
||||
|
||||
// append lines to _pending
|
||||
foreach (var line in request.Lines) {
|
||||
_pending.AppendLine(line);
|
||||
}
|
||||
// CreateScriptSource from pending
|
||||
var code = _engine.CreateScriptSourceFromString(_pending.ToString().TrimStart(' ', '\n', '\r'), SourceCodeKind.InteractiveCode);
|
||||
var parse_result = code.GetCodeProperties();
|
||||
if (parse_result == ScriptCodeParseResult.IncompleteStatement && code.GetCode().EndsWith(Environment.NewLine+Environment.NewLine)) {
|
||||
parse_result = Microsoft.Scripting.ScriptCodeParseResult.Complete;
|
||||
}
|
||||
// todo: If code is 'incomplete' but we have 2 blank lines run it!
|
||||
switch (parse_result) {
|
||||
// If 'complete' run code
|
||||
case ScriptCodeParseResult.Complete:
|
||||
_pending.Clear();
|
||||
ret(run_code(code));
|
||||
break;
|
||||
// if 'invalid' return error
|
||||
case ScriptCodeParseResult.Invalid:
|
||||
Console.WriteLine("Invalid");
|
||||
_pending.Clear();
|
||||
ret(new ScriptResultMessage { Res = ScriptResult.Invalid, Message = code.GetCode() });
|
||||
break;
|
||||
// otherwise continue buffering
|
||||
default:
|
||||
Console.WriteLine("Incomplete");
|
||||
ret(new ScriptResultMessage { Res = ScriptResult.Incomplete });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,10 +199,10 @@ namespace mbts.ui {
|
||||
public static BTSColor straddle_theovsmarket_positive { get { return BTSCoreColors.green_light; } }
|
||||
|
||||
// Order Types
|
||||
public static BTSColor tick_order { get { return new BTSColor(31, 4, 57);}}
|
||||
public static BTSColor lean_order { get { return new BTSColor(110, 77, 143);}}
|
||||
public static BTSColor join_order { get { return new BTSColor(15, 1, 29);}}
|
||||
public static BTSColor leader_order { get { return new BTSColor(40, 9, 72);}}
|
||||
public static BTSColor tick_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor lean_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor join_order { get { return BTSColors.purple.rgb_color();}}
|
||||
public static BTSColor top_order { get { return BTSColors.purple.rgb_color();}}
|
||||
}
|
||||
|
||||
public static class AW2 {
|
||||
|
||||
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
yield return item;
|
||||
}
|
||||
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
|
||||
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
|
||||
@@ -345,6 +345,17 @@ namespace System.Reactive.Linq
|
||||
|
||||
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
|
||||
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
|
||||
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
|
||||
}
|
||||
|
||||
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
|
||||
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,5 +54,33 @@
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
|
||||
if (args == null || args.Length != 3) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
|
||||
if (args == null || args.Length != 4) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
arg4 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
rv |= try_convert(args[3], out arg4);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,10 @@ namespace mbts {
|
||||
public static double tick_unit_frac(this TickFactors tick) =>
|
||||
tick.tick_unit / tick.tick_base;
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => factor_out(tick.tick_base, 10);
|
||||
|
||||
public static string format(this TickFactors tick, double value) =>
|
||||
format(tick, value, double.NaN, true, _nan_text);
|
||||
format(tick, value, double.NaN, false, _nan_text);
|
||||
|
||||
public static string format(this TickFactors tick, double value, bool show_commas) =>
|
||||
format(tick, value, double.NaN, show_commas, _nan_text);
|
||||
@@ -176,9 +178,10 @@ namespace mbts {
|
||||
if (DoubleHelper.IsNaNOrInfinity(input)) return input;
|
||||
return input <= regional_tick.low_tick_inclusive_end ? regional_tick.low_tick.round(input) : regional_tick.high_tick.round(input);
|
||||
}
|
||||
public static int display_digits(this TickFactors tick) => display_digits(tick.tick_base);
|
||||
#endregion
|
||||
|
||||
public static int n_display_digits(double tick_base) {
|
||||
public static int display_digits(double tick_base) {
|
||||
double log_tick_base = Math.Log10(tick_base);
|
||||
int floor = (int)Math.Floor(log_tick_base);
|
||||
double dec = log_tick_base - floor;
|
||||
@@ -216,7 +219,7 @@ namespace mbts {
|
||||
if (numerator_part >= tick_base) numerator_part /= Math.Pow(10, Math.Ceiling(Math.Log10(numerator_part) - Math.Log10(tick_base)));
|
||||
else {
|
||||
//e.g [tick_base 640] 1989-8 == 1989-80, or 1989-32 == 1989-320
|
||||
int ndd_diff = n_display_digits(tick_base) - numerator_text.Length;
|
||||
int ndd_diff = display_digits(tick_base) - numerator_text.Length;
|
||||
if (ndd_diff > 0) numerator_part *= Math.Pow(10, ndd_diff);
|
||||
if (numerator_part >= tick_base) numerator_part /= 10;
|
||||
}
|
||||
@@ -255,16 +258,21 @@ namespace mbts {
|
||||
double numerator_dec = value - integer_part;
|
||||
double unit_frac = tick.tick_unit / tick.tick_base;
|
||||
int n_units = (int)Math.Round(numerator_dec / unit_frac);
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', n_display_digits(tick.tick_base));
|
||||
if (n_units == tick.tick_base) return (integer_part + 1).ToString() + tick.separator() + new string('0', display_digits(tick.tick_base));
|
||||
double decimal_part = n_units * tick.tick_unit;
|
||||
if (decimal_part == tick.tick_base) {
|
||||
++integer_part;
|
||||
decimal_part = 0;
|
||||
} else {
|
||||
// some ticks are not formatted with 100% precision, using less than the required display_digits
|
||||
// for such cases, we need to truncate the decimal part rather than round it.
|
||||
decimal_part = Math.Floor(decimal_part);
|
||||
}
|
||||
string integer_part_text = show_commas ? integer_part.ToString("#,0") : integer_part.ToString();
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', n_display_digits(tick.tick_base)));
|
||||
return integer_part_text + tick.separator() + decimal_part.ToString(new string('0', display_digits(tick.tick_base)));
|
||||
}
|
||||
} catch (OverflowException ex) {
|
||||
}
|
||||
catch (OverflowException ex) {
|
||||
Logger.warn(bts.log.Subsystem.Ui, $"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
|
||||
return "Overflow";
|
||||
}
|
||||
@@ -272,7 +280,7 @@ namespace mbts {
|
||||
|
||||
private static double _round_tick(double tick_unit_frac, double input) {
|
||||
return input - Math.IEEERemainder(input, tick_unit_frac);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ namespace mbts.ui.native
|
||||
{
|
||||
public static class BTSUINativeMethods
|
||||
{
|
||||
[DllImport("spark_lib")]
|
||||
[DllImport("libspark")]
|
||||
public static extern void enable_limits();
|
||||
|
||||
[DllImport("spark_lib", CharSet = CharSet.Unicode)]
|
||||
[DllImport("libspark", CharSet = CharSet.Unicode)]
|
||||
public static extern void set_window_app_id(IntPtr hwnd, string stack_id);
|
||||
}
|
||||
}
|
||||
@@ -69,16 +69,18 @@ namespace mbts.ui.settings
|
||||
if (window == null || window.Owner == null) return null;
|
||||
MemoryStream out_stream = new MemoryStream();
|
||||
window.GetState(out_stream);
|
||||
|
||||
var pos = _interop.GetPosition(window);
|
||||
var state = new WindowState() {
|
||||
owner = window.Owner.key(),
|
||||
app_data = out_stream.ToArray(),
|
||||
position = _interop.GetPosition(window),
|
||||
title = window.Title
|
||||
position = pos,
|
||||
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
|
||||
};
|
||||
return state;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ using bts.api.persistence;
|
||||
|
||||
namespace mbts.ui.settings
|
||||
{
|
||||
//
|
||||
// This class is used for tools like SettingsManager.exe
|
||||
//
|
||||
[Export(typeof(IUserPersistFactory))]
|
||||
class UISettingsFactory : IUserPersistFactory
|
||||
{
|
||||
@@ -34,7 +37,7 @@ namespace mbts.ui.settings
|
||||
|
||||
public IUserPersist get_persist_for(string name) {
|
||||
var user = _users.Result.FirstOrDefault(u => u.Login == name);
|
||||
return new UserUISettings(user.Id, _persist);
|
||||
return new UserUISettings(user.Id, _persist, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ namespace mbts.ui.shell {
|
||||
[ProtoMember(102), DefaultValue(true)]
|
||||
public bool always_on_top = true;
|
||||
//[ProtoMember(103)] //used
|
||||
|
||||
[ProtoMember(104)]
|
||||
public string custom_title;
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -58,7 +61,7 @@ namespace mbts.ui.shell {
|
||||
public WindowPosition position;
|
||||
|
||||
[ProtoMember(9)]
|
||||
public string title;
|
||||
public string title; // for debugging
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace mbts.ui
|
||||
Task<IShellWindow> create(Stream load_stream);
|
||||
}
|
||||
|
||||
// ILazyModule may also impement IShellMenu to get opening/closing notification
|
||||
public interface ILazyMenuModule : IShellModule { }
|
||||
|
||||
public interface IPersistShellModule : IShellModule {
|
||||
// Called at startup before the UI is shown
|
||||
// This is an opportunity for you to kick off background services
|
||||
|
||||
@@ -17,5 +17,5 @@ namespace mbts.ui
|
||||
WindowPosition GetPosition(IShellWindow window);
|
||||
bool IsVisible(IShellWindow window);
|
||||
void PinWindow(IShellWindow window);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using bts.api.core;
|
||||
using bts.api.persistence;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reactive.Linq;
|
||||
using bts.spark;
|
||||
|
||||
namespace mbts.persist
|
||||
{
|
||||
@@ -18,11 +19,13 @@ namespace mbts.persist
|
||||
protected abstract string user();
|
||||
string uns(string ns) { return $"{user()}#{ns}"; }
|
||||
PersistenceService _persist;
|
||||
ErrorReporter _errors;
|
||||
|
||||
private const string _settings_change_base = "/ui/settingschange/";
|
||||
|
||||
public UISettingsBase(PersistenceService persist) {
|
||||
public UISettingsBase(PersistenceService persist, ErrorReporter errors) {
|
||||
_persist = persist;
|
||||
_errors = errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +53,7 @@ namespace mbts.persist
|
||||
Ns = uns(ns),
|
||||
Key = { key }
|
||||
});
|
||||
check_error(ret.value.Result, ns, key, out bool empty);
|
||||
check_error(ret, ns, key, out bool empty);
|
||||
if (empty) return default(T);
|
||||
return deserialize<T>(ret.value.Record.Single().Value);
|
||||
}
|
||||
@@ -67,20 +70,28 @@ namespace mbts.persist
|
||||
return ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(stream, ProtoBuf.PrefixStyle.Fixed32);
|
||||
}
|
||||
|
||||
private void check_error(RetrieveResponse.Types.Result result, string ns, string key, out bool empty) {
|
||||
switch (result) {
|
||||
case RetrieveResponse.Types.Result.EmptyNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchKey:
|
||||
empty = true;
|
||||
return;
|
||||
case RetrieveResponse.Types.Result.EmptyKey:
|
||||
case RetrieveResponse.Types.Result.DuplicateKey:
|
||||
throw new ArgumentException($"Invalid Key {result} '{key}'");
|
||||
default:
|
||||
case RetrieveResponse.Types.Result.Ok:
|
||||
empty = false;
|
||||
return;
|
||||
private void check_error(Result<RetrieveResponse> result, string ns, string key, out bool empty) {
|
||||
if (result.success) {
|
||||
switch (result.value.Result) {
|
||||
case RetrieveResponse.Types.Result.EmptyNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchKey:
|
||||
empty = true;
|
||||
return;
|
||||
case RetrieveResponse.Types.Result.EmptyKey:
|
||||
case RetrieveResponse.Types.Result.DuplicateKey:
|
||||
_errors?.report_error($"Invalid Key {result} '{key}'", "Requested Invalid Settings Key", System.Windows.MessageBoxImage.Information);
|
||||
empty = true;
|
||||
return;
|
||||
default:
|
||||
case RetrieveResponse.Types.Result.Ok:
|
||||
empty = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_errors?.report_error($"There was an error communicating with the settings server: '{result.error_message}'", "Failed to retrieve settings from the server");
|
||||
empty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +101,7 @@ namespace mbts.persist
|
||||
new RetrieveAllRequest() {
|
||||
Ns = uns(ns)
|
||||
});
|
||||
check_error(results.value.Result, ns, "", out bool empty);
|
||||
check_error(results, ns, "", out bool empty);
|
||||
if (empty) return Enumerable.Empty<PersistResult<T>>();
|
||||
return results.value.Record
|
||||
.Where(r => re.IsMatch(r.Key))
|
||||
@@ -131,7 +142,7 @@ namespace mbts.persist
|
||||
protected override string user() { return "all_users"; }
|
||||
|
||||
[ImportingConstructor]
|
||||
public UISettings([Import("settings")]PersistenceService p) : base(p) { }
|
||||
public UISettings([Import("settings")]PersistenceService p, ErrorReporter err) : base(p,err) { }
|
||||
}
|
||||
|
||||
[Export(typeof(IUserPersist))]
|
||||
@@ -140,8 +151,8 @@ namespace mbts.persist
|
||||
string _user;
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist)
|
||||
: base(persist) {
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
_user = $"User {user}";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
public static class EqualityComparer {
|
||||
const int _hash_seed = 63607, _hash_multiplier = 6451;
|
||||
|
||||
public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals) =>
|
||||
new AnonymousEqualityComparer<T>(equals);
|
||||
|
||||
@@ -23,12 +26,40 @@ namespace mbts.utils {
|
||||
public static IEqualityComparer<T?> CreateNullable<T>(Func<T, T, bool> equals) where T : struct =>
|
||||
new AnonymousEqualityComparer<T?>((a, b) => a.HasValue ? b.HasValue && equals(a.Value, b.Value) : !b.HasValue);
|
||||
|
||||
public static IEqualityComparer to_non_generic<T>(this IEqualityComparer<T> comparer) =>
|
||||
new AnonymousEqualityComparer<T>(comparer.Equals, comparer.GetHashCode);
|
||||
|
||||
public static bool NullCheckEquals<T>(T x, T y, Func<T, T, bool> non_null_equals) where T : class, IEquatable<T> =>
|
||||
x == null ? y == null :
|
||||
y != null &&
|
||||
non_null_equals(x, y);
|
||||
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
|
||||
public static int aggregate_hash(int item1_hash, int item2_hash, int item3_hash) {
|
||||
int rv = _hash_seed;
|
||||
unchecked {
|
||||
_accumulate_hash(ref rv, ref item1_hash);
|
||||
_accumulate_hash(ref rv, ref item2_hash);
|
||||
_accumulate_hash(ref rv, ref item3_hash);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Note: be wary of the performance cost this method may have, as frequently creating large arrays can be expensive.
|
||||
// If you wish to avoid this cost, consider making an overload like the method above, with the appropriate number of item-hash arguments
|
||||
public static int aggregate_hash(params int[] item_hashes) {
|
||||
int hash = _hash_seed;
|
||||
unchecked {
|
||||
foreach (int item_hash in item_hashes)
|
||||
hash = hash * _hash_multiplier + item_hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// WARNING: be sure to call this within in an unchecked scope
|
||||
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
|
||||
aggregate_hash = aggregate_hash * _hash_multiplier + item_hash;
|
||||
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T>, IEqualityComparer {
|
||||
readonly Func<T, T, bool> _equals;
|
||||
readonly Func<T, int> _get_hash_code;
|
||||
|
||||
@@ -40,6 +71,9 @@ namespace mbts.utils {
|
||||
|
||||
public bool Equals(T x, T y) => _equals(x, y);
|
||||
public int GetHashCode(T obj) => _get_hash_code(obj);
|
||||
|
||||
bool IEqualityComparer.Equals(object x, object y) => x is T xt && y is T yt && _equals(xt, yt);
|
||||
int IEqualityComparer.GetHashCode(object obj) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
public static class CompareUtils {
|
||||
@@ -19,5 +21,22 @@ namespace mbts.utils {
|
||||
b.HasValue ? compare(a.Value, b.Value) : 1 :
|
||||
b.HasValue ? -1 : 0;
|
||||
#endregion
|
||||
|
||||
#region Comparer
|
||||
public static IComparer to_non_generic<T>(this IComparer<T> comparer) =>
|
||||
new NonGenericComparerWrapper<T>(comparer);
|
||||
|
||||
public static IComparer<TOutter> wrap<TInner, TOutter>(this IComparer<TInner> inner_comparer, Func<TOutter, TInner> selector) =>
|
||||
Comparer<TOutter>.Create((a, b) => inner_comparer.Compare(a == null ? default : selector(a), b == null ? default : selector(b)));
|
||||
|
||||
class NonGenericComparerWrapper<T> : IComparer {
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
public NonGenericComparerWrapper(IComparer<T> comparer) => _comparer = comparer;
|
||||
|
||||
public int Compare(object x, object y) =>
|
||||
_comparer.Compare(ObjectHelper.try_convert<T>(x), ObjectHelper.try_convert<T>(y));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mbts.spark {
|
||||
interface IValueFactoryVM<T> {
|
||||
T create();
|
||||
void load_from(T value);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.utils {
|
||||
interface IObservableDemuxer<TKey, in TInput, out TOutput> : IObserver<TInput> {
|
||||
@@ -89,21 +89,17 @@ namespace mbts.utils {
|
||||
_group_key = group_key;
|
||||
}
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public IObservable<ITableUpdateTransform<T, TKey>> subscribe(TGroupKey group_key) =>
|
||||
Observable.Create<ITableUpdateTransform<T, TKey>>(o => {
|
||||
var data = _map.GetOrAdd(group_key, gk => new Data());
|
||||
lock (data) {
|
||||
var current = data.current;
|
||||
if (current.state.Count > 0) {
|
||||
var state_clone = current.state.ToDictionary();
|
||||
o.OnNext(new TableUpdateTransform<T, TKey>(state_clone, state_clone.Values.ToList(), new List<T>(0)));
|
||||
}
|
||||
using (data.read()) {
|
||||
o.OnNext(data.current.as_snapshot());
|
||||
return data.subject.Subscribe(o);
|
||||
}
|
||||
return data.subject.Subscribe(o);
|
||||
});
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public void OnNext(TableUpdate<T, TKey> table) {
|
||||
var changes = new Dictionary<TGroupKey, ChangeSet>();
|
||||
foreach (T value in table.added)
|
||||
@@ -116,16 +112,29 @@ namespace mbts.utils {
|
||||
}
|
||||
|
||||
Data _update(Data data, ChangeSet changes) {
|
||||
lock (data) {
|
||||
data.current = data.current.update(changes.removed, changes.added, _key);
|
||||
data.subject.OnNext(data.current);
|
||||
}
|
||||
using (data.write())
|
||||
data.subject.OnNext(data.current = data.current.update(changes.removed, changes.added, _key));
|
||||
return data;
|
||||
}
|
||||
|
||||
class Data {
|
||||
int _n_read_ops, _n_write_ops;
|
||||
public readonly Subject<TableUpdateTransform<T, TKey>> subject = new Subject<TableUpdateTransform<T, TKey>>();
|
||||
public TableUpdateTransform<T, TKey> current = new TableUpdateTransform<T, TKey>();
|
||||
|
||||
public IDisposable read() {
|
||||
while (_n_write_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_read_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
|
||||
}
|
||||
|
||||
public IDisposable write() {
|
||||
while (_n_read_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_write_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
|
||||
}
|
||||
}
|
||||
|
||||
// [John]I prefer this class over anonymous tuples bc it is easy to mix up Item1/Item2
|
||||
|
||||
@@ -7,12 +7,30 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.utils {
|
||||
interface IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
IReadOnlyDictionary<TKey, T> added { get; }
|
||||
IReadOnlyDictionary<TKey, T> removed { get; }
|
||||
}
|
||||
|
||||
interface ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
IReadOnlyDictionary<TKey, T> state { get; }
|
||||
IReadOnlyList<T> added { get; }
|
||||
IReadOnlyList<T> removed { get; }
|
||||
}
|
||||
|
||||
class IncrementalUpdate<T, TKey> : IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public IncrementalUpdate()
|
||||
: this(new Dictionary<TKey, T>(0), new Dictionary<TKey, T>(0)) { }
|
||||
|
||||
public IncrementalUpdate(IReadOnlyDictionary<TKey, T> added, IReadOnlyDictionary<TKey, T> removed) {
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<TKey, T> added { get; }
|
||||
public IReadOnlyDictionary<TKey, T> removed { get; }
|
||||
}
|
||||
|
||||
class TableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
readonly Dictionary<TKey, T> _state;
|
||||
readonly IReadOnlyList<T> _added, _removed;
|
||||
@@ -44,6 +62,18 @@ namespace mbts.utils {
|
||||
table._state.Add(key(item), item);
|
||||
return table;
|
||||
}
|
||||
|
||||
public TableUpdateTransform<T, TKey> update(IIncrementalUpdate<T, TKey> update, Func<T, TKey> key) {
|
||||
var table = new TableUpdateTransform<T, TKey>(new Dictionary<TKey, T>(_state), added, removed);
|
||||
foreach (var k in update.removed.Keys)
|
||||
table._state.Remove(k);
|
||||
foreach (var kv in update.added)
|
||||
table._state.Add(kv.Key, kv.Value);
|
||||
return table;
|
||||
}
|
||||
|
||||
internal TableUpdateTransform<T, TKey> as_snapshot() =>
|
||||
new TableUpdateTransform<T, TKey>(_state, _state.Values.ToList(), new T[0]);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
@@ -58,6 +88,15 @@ namespace mbts.utils {
|
||||
.Select(u => new FilteredTableUpdateTransform<T, TKey>(u, predicate))
|
||||
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a 1:1 conversion to an IncrementalUpdate sequence of TResult (TKey remains the same).
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<TResult, TKey>> convert<T, TKey, TResult>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
source.Select(update => new IncrementalUpdate<TResult, TKey>(
|
||||
update.added.ToDictionary(kv => kv.Key, kv => selector(kv.Value)),
|
||||
update.removed.ToDictionary(kv => kv.Key, kv => selector(kv.Value))));
|
||||
|
||||
/// <summary>
|
||||
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
|
||||
/// </summary>
|
||||
@@ -69,6 +108,33 @@ namespace mbts.utils {
|
||||
source.Scan(new PivotTableUpdateTransform<T, TKey, TResult, TResultKey>(selector, result_key, key),
|
||||
(pivot, table) => pivot.update(table));
|
||||
|
||||
/// <summary>
|
||||
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<TResult, TResultKey>> with_respect_to<T, TKey, TResult, TResultKey>(
|
||||
this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key)
|
||||
where TKey : IEquatable<TKey>
|
||||
where TResultKey : IEquatable<TResultKey> =>
|
||||
//
|
||||
source.Scan(new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(selector, result_key, key),
|
||||
(pivot, table) => pivot.update(table));
|
||||
|
||||
/// <summary>
|
||||
/// Uses a Scan operator to accumulate state in the resulting ITableUpdateTransform sequence.
|
||||
/// </summary>
|
||||
public static IObservable<ITableUpdateTransform<T, TKey>> table<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TKey> key) where TKey : IEquatable<TKey> =>
|
||||
source.Scan(
|
||||
new TableUpdateTransform<T, TKey>(),
|
||||
(table, update) => table.update(update, key));
|
||||
|
||||
/// <summary>
|
||||
/// Filters the incremental added/removed lists by a predicate. Observers are only notified when the filtered added/removed lists have elements.
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<T, TKey>> filter<T, TKey>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, bool> predicate)
|
||||
where TKey : IEquatable<TKey> => source
|
||||
.Select(u => new IncrementalUpdate<T, TKey>(u.added.Where(kv => predicate(kv.Value)).ToDictionary(), u.removed.Where(kv => predicate(kv.Value)).ToDictionary()))
|
||||
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
|
||||
|
||||
class FilteredTableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
internal FilteredTableUpdateTransform(ITableUpdateTransform<T, TKey> source, Func<T, bool> predicate) {
|
||||
state = source.state;
|
||||
@@ -81,6 +147,57 @@ namespace mbts.utils {
|
||||
public IReadOnlyList<T> removed { get; }
|
||||
}
|
||||
|
||||
class PivotIncrementalUpdate<T, TKey, TResult, TResultKey> : IIncrementalUpdate<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
|
||||
readonly Dictionary<TResultKey, TResult> _state;
|
||||
readonly SetMap<TResultKey, TKey> _key_map;
|
||||
readonly Func<T, TResult> _selector;
|
||||
readonly Func<TResult, TResultKey> _result_key;
|
||||
readonly Func<T, TKey> _key;
|
||||
|
||||
internal PivotIncrementalUpdate(Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) :
|
||||
this(new Dictionary<TResultKey, TResult>(), new Dictionary<TResultKey, TResult>(0), new Dictionary<TResultKey, TResult>(0), new SetMap<TResultKey, TKey>(), selector, result_key, key) { }
|
||||
|
||||
PivotIncrementalUpdate(Dictionary<TResultKey, TResult> state, IReadOnlyDictionary<TResultKey, TResult> added, IReadOnlyDictionary<TResultKey, TResult> removed,
|
||||
SetMap<TResultKey, TKey> key_map, Func<T, TResult> selector, Func<TResult, TResultKey> result_key, Func<T, TKey> key) {
|
||||
//
|
||||
_state = state;
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
_key_map = key_map;
|
||||
_selector = selector;
|
||||
_result_key = result_key;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<TResultKey, TResult> state => _state;
|
||||
public IReadOnlyDictionary<TResultKey, TResult> added { get; }
|
||||
public IReadOnlyDictionary<TResultKey, TResult> removed { get; }
|
||||
|
||||
public PivotIncrementalUpdate<T, TKey, TResult, TResultKey> update(IIncrementalUpdate<T, TKey> table) {
|
||||
Dictionary<TResultKey, TResult> removed = new Dictionary<TResultKey, TResult>(), added = new Dictionary<TResultKey, TResult>();
|
||||
var state_clone = new Dictionary<TResultKey, TResult>(_state);
|
||||
foreach (var kv in table.removed) {
|
||||
var result = _selector(kv.Value);
|
||||
var rkey = _result_key(result);
|
||||
if (_key_map.removes_key(rkey, kv.Key))
|
||||
removed.Add(rkey, result);
|
||||
}
|
||||
foreach (var kv in table.added) {
|
||||
var result = _selector(kv.Value);
|
||||
var rkey = _result_key(result);
|
||||
if (_key_map.adds_key(rkey, kv.Key)) {
|
||||
if (!removed.Remove(rkey))
|
||||
added.Add(rkey, result);
|
||||
}
|
||||
}
|
||||
foreach (var k in removed.Keys)
|
||||
state_clone.Remove(k);
|
||||
foreach (var kv in added)
|
||||
state_clone.Add(kv.Key, kv.Value);
|
||||
return new PivotIncrementalUpdate<T, TKey, TResult, TResultKey>(state_clone, added, removed, _key_map, _selector, _result_key, _key);
|
||||
}
|
||||
}
|
||||
|
||||
class PivotTableUpdateTransform<T, TKey, TResult, TResultKey> : ITableUpdateTransform<TResult, TResultKey> where TKey : IEquatable<TKey> where TResultKey : IEquatable<TResultKey> {
|
||||
readonly Dictionary<TResultKey, TResult> _state;
|
||||
readonly SetMap<TResultKey, TKey> _key_map;
|
||||
|
||||
@@ -471,7 +471,7 @@ namespace mbts.ui.shell
|
||||
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new ObservablePropertySink<T>(this, name, default_value);
|
||||
_observable_properties.Add(source.ObserveOnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
return prop;
|
||||
}
|
||||
|
||||
@@ -526,8 +526,8 @@ namespace mbts.ui.shell
|
||||
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new SelectorProperty<T>(this, name, select_default, items_match, validation_steps);
|
||||
_observable_properties.Add(items.ObserveOnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(desired_item.ObserveOnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(items.OnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(prop));
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,141 +1,220 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.extensions;
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static mbts.config.Configuration;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public class Badge: Control {
|
||||
readonly static double _dpi = get<bts.config.System>().dpi_ratio;
|
||||
public class Badge : Control {
|
||||
public const double LINE_SPACING = 1;
|
||||
// Storing the defaults here for readability
|
||||
// Warning: these properties are used to calculate the ladder's row_height. Please change the values below if you want to change properties that affect the layout.
|
||||
// ** - value is not the actual PropertyMetadata.DefaultValue
|
||||
public static readonly Thickness
|
||||
DefaultMargin = new Thickness(4, 2, 4, 2), // **
|
||||
DefaultPadding = new Thickness(1),
|
||||
DefaultBorderThickness = new Thickness(2);
|
||||
public static readonly double
|
||||
DefaultCornerRadius = 1,
|
||||
DefaultFontSize = 10,
|
||||
DefaultWidth = 63; // **
|
||||
public static readonly FontFamily DefaultFontFamily = new FontFamily("Consolas");
|
||||
|
||||
readonly GlyphContentRect _gcr = new GlyphContentRect();
|
||||
Brush _background, _foreground, _border_brush;
|
||||
|
||||
Typeface _face;
|
||||
FormattedText _center_text;
|
||||
FormattedText _top_text;
|
||||
FormattedText _bottom_text;
|
||||
static Badge() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(typeof(Badge)));
|
||||
WidthProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(15.0));
|
||||
HeightProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0));
|
||||
FontSizeProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
|
||||
ForegroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.White, update_text));
|
||||
BackgroundProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
BorderThicknessProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new Thickness(1)));
|
||||
BorderBrushProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(Brushes.Black));
|
||||
FontFamilyProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(new FontFamily("Consolas"), FrameworkPropertyMetadataOptions.AffectsRender /*| FrameworkPropertyMetadataOptions.AffectsMeasure*/, update_text));
|
||||
|
||||
//TextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
|
||||
//TopTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
|
||||
//BottomTextProperty.OverrideMetadata(typeof(Badge), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure, update_text));
|
||||
// MinWidth
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MinWidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.min_width = v,
|
||||
0);
|
||||
// Width
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
WidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.width = v,
|
||||
double.NaN);
|
||||
// Max Width
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MaxWidthProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.max_width = v,
|
||||
double.MaxValue);
|
||||
// Min Height
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MinHeightProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.min_height = v,
|
||||
0);
|
||||
// Height
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
HeightProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.height = v,
|
||||
double.NaN);
|
||||
// Max Height
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
MaxHeightProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.max_height = v,
|
||||
double.MaxValue);
|
||||
// FontSize
|
||||
WPFHelper.override_metadata<double, Badge>(
|
||||
FontSizeProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.font_size = v,
|
||||
DefaultFontSize);
|
||||
// Foreground
|
||||
WPFHelper.override_metadata<Brush, Badge>(
|
||||
ForegroundProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._foreground = v,
|
||||
Brushes.White);
|
||||
// Background
|
||||
WPFHelper.override_metadata<Brush, Badge>(
|
||||
BackgroundProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._background = v,
|
||||
Brushes.Transparent);
|
||||
// BorderThickness
|
||||
WPFHelper.override_metadata<Thickness, Badge>(
|
||||
BorderThicknessProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.border_thickness = v,
|
||||
DefaultBorderThickness);
|
||||
// BorderBrush
|
||||
WPFHelper.override_metadata<Brush, Badge>(
|
||||
BorderBrushProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._border_brush = v,
|
||||
Brushes.Black);
|
||||
// FontFamily
|
||||
WPFHelper.override_metadata<FontFamily, Badge>(
|
||||
FontFamilyProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.glyph_context = b._gcr.glyph_context.with(v),
|
||||
DefaultFontFamily);
|
||||
// HoriontalAlignment
|
||||
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
|
||||
HorizontalAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.horizontal_alignment = v,
|
||||
HorizontalAlignment.Stretch);
|
||||
// HoriontalContentAlignment
|
||||
WPFHelper.override_metadata<HorizontalAlignment, Badge>(
|
||||
HorizontalContentAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.horizontal_content_alignment = v,
|
||||
HorizontalAlignment.Center);
|
||||
// VerticalAlignment
|
||||
WPFHelper.override_metadata<VerticalAlignment, Badge>(
|
||||
VerticalAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.vertical_alignment = v,
|
||||
VerticalAlignment.Center);
|
||||
// VerticalContentAlignment
|
||||
WPFHelper.override_metadata<VerticalAlignment, Badge>(
|
||||
VerticalContentAlignmentProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._gcr.vertical_content_alignment = v,
|
||||
VerticalAlignment.Center);
|
||||
// Padding
|
||||
WPFHelper.override_metadata<Thickness, Badge>(
|
||||
PaddingProperty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(p, v) => p._gcr.padding = v,
|
||||
DefaultPadding);
|
||||
}
|
||||
|
||||
public string Text {
|
||||
get { return (string)GetValue(TextProperty); }
|
||||
set { SetValue(TextProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register("Text", typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
|
||||
format_text((b,v) => b._center_text= v)));
|
||||
|
||||
|
||||
public string TopText {
|
||||
get { return (string)GetValue(TopTextProperty); }
|
||||
set { SetValue(TopTextProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty TopTextProperty =
|
||||
DependencyProperty.Register(nameof(TopText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
|
||||
format_text((b, v) => b._top_text = v)));
|
||||
|
||||
public string BottomText {
|
||||
get { return (string)GetValue(BottomTextProperty); }
|
||||
set { SetValue(BottomTextProperty, value); }
|
||||
public Badge() {
|
||||
_gcr.min_width = MinWidth;
|
||||
_gcr.width = Width;
|
||||
_gcr.max_width = MaxWidth;
|
||||
_gcr.min_height = MinHeight;
|
||||
_gcr.height = Height;
|
||||
_gcr.max_height = Height;
|
||||
_gcr.padding = Padding;
|
||||
_gcr.border_thickness = BorderThickness;
|
||||
_gcr.horizontal_alignment = HorizontalAlignment;
|
||||
_gcr.horizontal_content_alignment = HorizontalContentAlignment;
|
||||
_gcr.vertical_alignment = VerticalAlignment;
|
||||
_gcr.vertical_content_alignment = VerticalContentAlignment;
|
||||
_background = Background;
|
||||
_foreground = Foreground;
|
||||
_border_brush = BorderBrush;
|
||||
_gcr.font_size = FontSize;
|
||||
_gcr.text_lines = TextLines;
|
||||
_gcr.glyph_context = GlyphContext.create(FontFamily, FontWeight);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BottomTextProperty =
|
||||
DependencyProperty.Register(nameof(BottomText), typeof(string), typeof(Badge), new PropertyMetadata(string.Empty,
|
||||
format_text((b,v)=>b._bottom_text=v)));
|
||||
public static readonly DependencyProperty TextLinesProperty =
|
||||
WPFHelper.create_dp<IReadOnlyList<string>, Badge>(
|
||||
nameof(TextLines),
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(b, v) => b._gcr.text_lines = v,
|
||||
new string[0]);
|
||||
|
||||
public IReadOnlyList<string> TextLines {
|
||||
get => GetValue(TextLinesProperty) as IReadOnlyList<string>;
|
||||
set => SetValue(TextLinesProperty, value);
|
||||
}
|
||||
|
||||
public double CornerRadius {
|
||||
get { return (double)GetValue(CornerRadiusProperty); }
|
||||
set { SetValue(CornerRadiusProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CornerRadiusProperty =
|
||||
DependencyProperty.Register("CornerRadius", typeof(double), typeof(Badge), new PropertyMetadata(3.0));
|
||||
WPFHelper.create_dp<double, Badge>(nameof(CornerRadius), DefaultCornerRadius);
|
||||
|
||||
static PropertyChangedCallback format_text(Action<Badge,FormattedText> f) {
|
||||
return (d, e) => {
|
||||
Badge b = (Badge)d;
|
||||
ensure_fontface(b);
|
||||
string text = e.NewValue as string;
|
||||
if (string.IsNullOrEmpty(text)) f(b, null);
|
||||
f(b, new FormattedText(text,
|
||||
System.Globalization.CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
b._face, b.FontSize, b.Foreground, _dpi));
|
||||
b.InvalidateVisual();
|
||||
};
|
||||
protected override Size ArrangeOverride(Size finalSize) => finalSize;
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize) {
|
||||
_gcr.available_size = availableSize;
|
||||
return _gcr.layout_size;
|
||||
}
|
||||
static FormattedText format_text(Badge badge, DependencyProperty source) {
|
||||
FormattedText result = null;
|
||||
var value = badge.GetValue(source);
|
||||
format_text((b, v) => result = v)(badge, new DependencyPropertyChangedEventArgs(source, value, value));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void update_text(DependencyObject d, DependencyPropertyChangedEventArgs args) {
|
||||
// Force formatted text regeneration when font/color changes
|
||||
Badge badge = (Badge)d;
|
||||
badge._face = null;
|
||||
ensure_fontface(badge);
|
||||
badge._center_text = format_text(badge, TextProperty);
|
||||
badge._top_text = format_text(badge, TopTextProperty);
|
||||
badge._bottom_text = format_text(badge, BottomTextProperty);
|
||||
}
|
||||
|
||||
private static void ensure_fontface(Badge b) {
|
||||
b._face = b._face ?? new Typeface(b.FontFamily, b.FontStyle, b.FontWeight, b.FontStretch);
|
||||
}
|
||||
|
||||
//protected override Size MeasureOverride(Size availableSize) {
|
||||
// Double calc_height = 0.0;
|
||||
// Double calc_width = 0.0;
|
||||
// Size size = new Size(0, 0);
|
||||
|
||||
// Size center_size = new Size(_center_text.Width, _center_text.Height);
|
||||
// Size top_size = new Size(_top_text.Width, _top_text.Height);
|
||||
// Size bottom_size = new Size(_bottom_text.Width, _bottom_text.Height);
|
||||
|
||||
// calc_width = Math.Max(center_size.Width, top_size.Width);
|
||||
// calc_width = Math.Max(calc_width, bottom_size.Width);
|
||||
|
||||
// calc_height += center_size.Height;
|
||||
// calc_height += top_size.Height;
|
||||
// calc_height += bottom_size.Height;
|
||||
|
||||
// size.Width = calc_width;
|
||||
// size.Height = calc_height;
|
||||
|
||||
// return size;
|
||||
|
||||
//}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
base.OnRender(dc);
|
||||
double border_thickness = BorderThickness.Top;
|
||||
var pen = new Pen(BorderBrush, border_thickness);
|
||||
dc.DrawRoundedRectangle(Background, pen, new Rect(0, 0 + border_thickness, ActualWidth, ActualHeight - 2 * border_thickness), CornerRadius, CornerRadius);
|
||||
if (_center_text != null)
|
||||
dc.DrawText(_center_text, new Point((ActualWidth-_center_text.Width)/2, (ActualHeight-_center_text.Height)/2));
|
||||
if (_top_text != null)
|
||||
dc.DrawText(_top_text, new Point(border_thickness, border_thickness));
|
||||
if (_bottom_text != null)
|
||||
dc.DrawText(_bottom_text, new Point(border_thickness, ActualHeight - _bottom_text.Height - border_thickness));
|
||||
dc.DrawRoundedRectangle(Background, pen, new Rect(0, border_thickness, _gcr.layout_size.Width, _gcr.layout_size.Height), CornerRadius, CornerRadius);
|
||||
var gr = _gcr.glyph_run;
|
||||
if (gr != null)
|
||||
dc.DrawGlyphRun(_foreground, gr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BadgeTextLinesConverter : IMultiValueConverter {
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ObjectHelper.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
|
||||
return BadgeRender.format(side, quantity, filled, style);
|
||||
else return new string[0];
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ namespace mbts.ui.controls {
|
||||
Disposable.dispose(ref timer._trigger_sub);
|
||||
IObservable<object> newval = args.NewValue as IObservable<object>;
|
||||
if (newval != null) {
|
||||
timer._trigger_sub = newval.ObserveOnDispatcher().Subscribe(timer.start);
|
||||
timer._trigger_sub = newval.OnDispatcher().Subscribe(timer.start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.spark.filters;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -21,7 +22,8 @@ namespace mbts.ui.controls {
|
||||
Time = 100,
|
||||
TimeInForce = 47,
|
||||
TradeKey = 76,
|
||||
User = 53;
|
||||
User = 53,
|
||||
Aggressor = 80;
|
||||
}
|
||||
|
||||
public static class DefaultColumnHeaders {
|
||||
@@ -39,7 +41,9 @@ namespace mbts.ui.controls {
|
||||
Size = "Qty",
|
||||
TimeInForce = "TIF",
|
||||
TradeKey = "Trade Key",
|
||||
User = "User";
|
||||
User = "User",
|
||||
Time = "Time",
|
||||
Aggressor = "Aggressor";
|
||||
}
|
||||
|
||||
internal interface IKeyedColumn<TColKey> {
|
||||
@@ -60,13 +64,19 @@ namespace mbts.ui.controls {
|
||||
WPFHelper.create_dp<ListSortDirection?, ColumnBase>(
|
||||
nameof(SortDirection), (o, v) => o.OnSortDirectionChanged(v));
|
||||
|
||||
public ListSortDirection? SortDirection { get => GetValue(SortDirectionProperty) as ListSortDirection?; set => SetValue(SortDirectionProperty, value); }
|
||||
public ListSortDirection? SortDirection {
|
||||
get => GetValue(SortDirectionProperty) as ListSortDirection?;
|
||||
set => SetValue(SortDirectionProperty, value);
|
||||
}
|
||||
|
||||
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
|
||||
|
||||
public string sort_member_path { get; set; }
|
||||
public double original_width { get; set; }
|
||||
public bool is_collapsed { get; set; }
|
||||
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) { }
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
|
||||
sort_direction_changed?.Invoke(this, new_value);
|
||||
}
|
||||
|
||||
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey> {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
public partial class DIYGrid {
|
||||
internal class TimeAndSalesColumn : RenderTextColumn<TimeAndSalesVM.Row> {
|
||||
|
||||
readonly Func<TimeAndSalesVM.Row, string> _get_text;
|
||||
readonly Func<TimeAndSalesVM.Row, Brush> _get_brush;
|
||||
|
||||
public TimeAndSalesColumn(Func<TimeAndSalesVM.Row, string> get_text, Func<TimeAndSalesVM.Row, Brush> get_brush, IDataGridColumnFilter filter = null, string filter_template = null, GlyphContext glyph_context = null) : base(get_text, filter, filter_template, glyph_context) {
|
||||
_get_text = get_text;
|
||||
_get_brush = get_brush;
|
||||
}
|
||||
|
||||
public override void draw(TimeAndSalesVM.Row row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc
|
||||
, glyph_context
|
||||
, font_size
|
||||
, _get_text(row)
|
||||
, position
|
||||
, row_height
|
||||
, foreground
|
||||
, _get_brush(row)
|
||||
, horizontal_alignment
|
||||
, vertical_alignment
|
||||
, padding);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark.filters {
|
||||
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer, comparer);
|
||||
|
||||
public static IDataGridColumnPersistentFilter<TRow, TState> Create<TRow, TValue, TState>(
|
||||
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
|
||||
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
|
||||
Func<TRow, TValue> get_row_val,
|
||||
Func<ISet<TValue>, TState> get_state,
|
||||
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
|
||||
@@ -115,14 +115,14 @@ namespace mbts.spark.filters {
|
||||
IComparer<TValue> comparer = null
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.ObserveOnDispatcher().Subscribe(new_items => {
|
||||
_items_sub = items.OnDispatcher().Subscribe(new_items => {
|
||||
_items_set.SetItems(new_items.Concat(_selected_items_set));
|
||||
_unsorted_items.sync_with(_items_set);
|
||||
});
|
||||
}
|
||||
|
||||
public DynamicItemsFilter(
|
||||
IObservable<(IReadOnlyList<TValue> added, IReadOnlyList<TValue> removed)> items,
|
||||
IObservable<(IEnumerable<TValue> added, IEnumerable<TValue> removed)> items,
|
||||
Func<TRow, TValue> get_row_val,
|
||||
Func<ISet<TValue>, TState> get_state,
|
||||
Func<TState, IObservable<IEnumerable<TValue>>> load_state,
|
||||
@@ -131,7 +131,7 @@ namespace mbts.spark.filters {
|
||||
IComparer<TValue> comparer = null
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.ObserveOnDispatcher().Subscribe(update => {
|
||||
_items_sub = items.OnDispatcher().Subscribe(update => {
|
||||
foreach (var v in update.added)
|
||||
_items_set.Add(v);
|
||||
foreach (var v in update.removed.Except(_selected_items_set))
|
||||
@@ -182,7 +182,7 @@ namespace mbts.spark.filters {
|
||||
|
||||
protected override void _load_state_internal(TState state) {
|
||||
Disposable.dispose(ref _desired_items_sub);
|
||||
_desired_items_sub = _load_state(state).ObserveOnDispatcher().Subscribe(_on_desired_item);
|
||||
_desired_items_sub = _load_state(state).OnDispatcher().Subscribe(_on_desired_item);
|
||||
}
|
||||
|
||||
protected override void _apply() {
|
||||
|
||||
@@ -54,8 +54,9 @@ namespace mbts.ui {
|
||||
public static TimeTextTuple FromTicks(long ticks) =>
|
||||
FromDateTime(new DateTime(ticks));
|
||||
|
||||
public static TimeTextTuple FromDateTime(DateTime date_time) =>
|
||||
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString("t"));
|
||||
public static TimeTextTuple FromDateTime(DateTime date_time) => FromDateTime(date_time, "t");
|
||||
public static TimeTextTuple FromDateTime(DateTime date_time, string time_format) =>
|
||||
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString(time_format));
|
||||
|
||||
public TimeTextTuple(string month_text, string day_text, string year_text, string time_text) {
|
||||
_month_text = month_text;
|
||||
@@ -107,14 +108,23 @@ namespace mbts.ui {
|
||||
|
||||
|
||||
static DateTime? _parse(string month_text, string day_text, string year_text, string time_text) {
|
||||
DateTime? t = _parse(time_text);
|
||||
TimeSpan? t = _parse_time(time_text);
|
||||
if (t.HasValue) {
|
||||
DateTime? d = _parse($"{month_text}/{day_text}/{year_text}");
|
||||
if (d.HasValue) return d.Value.Date.AddDays(t.Value.TimeOfDay.TotalDays);
|
||||
if (d.HasValue) return d.Value.Date.Add(t.Value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static TimeSpan? _parse_time(string text) {
|
||||
DateTime? dt = _parse(text);
|
||||
if (dt.HasValue) {
|
||||
return dt.Value.TimeOfDay;
|
||||
} else if (TimeSpan.TryParse(text, out TimeSpan result) && result < TimeSpan.FromDays(1)) {
|
||||
return result;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
static DateTime? _parse(string text) {
|
||||
if (!string.IsNullOrEmpty(text) && DateTime.TryParse(text, out DateTime result))
|
||||
return result;
|
||||
|
||||
Loaded 100 of 491 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user