Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5880edaf7 | ||
|
|
175061a81e | ||
|
|
3daad4f7e7 | ||
|
|
8bcf2fde87 | ||
|
|
a5d20157f0 | ||
|
|
d3deac46d4 | ||
|
|
288e8a364a | ||
|
|
61c6d0513c | ||
|
|
b0f2b8e906 | ||
|
|
c194e76e46 | ||
|
|
e7f8cfaec1 | ||
|
|
0aa4f80752 | ||
|
|
b8eecfd853 | ||
|
|
2960ff0996 | ||
|
|
a071b62560 | ||
|
|
95004adbad | ||
|
|
f28334f551 | ||
|
|
718a12200b | ||
|
|
d6dab59254 | ||
|
|
9e6bdf74f6 | ||
|
|
030a8d5614 | ||
|
|
39946baf94 | ||
|
|
19be7d7740 | ||
|
|
37856b9768 | ||
|
|
dd2067802f | ||
|
|
de6c53a765 | ||
|
|
b2a20b47ff | ||
|
|
be16bc2a27 | ||
|
|
71c66f127d | ||
|
|
4987fc9fc1 | ||
|
|
16a4c315a2 | ||
|
|
71619c2e15 | ||
|
|
5392b706df | ||
|
|
cbda28c8b7 | ||
|
|
3956455e81 | ||
|
|
76eb541878 | ||
|
|
d17c56b799 | ||
|
|
e3795f9498 | ||
|
|
8da2569a27 | ||
|
|
a598188c14 | ||
|
|
3648f18a09 | ||
|
|
736e051225 | ||
|
|
e007958afd | ||
|
|
1682513af1 | ||
|
|
cc7e66aa61 | ||
|
|
cea153df5a | ||
|
|
9903b340a6 | ||
|
|
f07b272cff | ||
|
|
9d74a9d114 | ||
|
|
e8f9bb8226 | ||
|
|
033c1ec5de | ||
|
|
7fd2c075a1 | ||
|
|
125773b840 | ||
|
|
4cb743004b | ||
|
|
9dbc9df2a4 | ||
|
|
7c1016d940 | ||
|
|
43198f9602 | ||
|
|
8b1a195121 | ||
|
|
1cde234be4 | ||
|
|
bba6da76fb | ||
|
|
0f68a7f56b | ||
|
|
a4963f8bd9 | ||
|
|
58a762a18a | ||
|
|
90c13d526a | ||
|
|
743d8e0e81 | ||
|
|
eb7a79488f | ||
|
|
3dd7b9638b | ||
|
|
771d4976e9 | ||
|
|
f0a08c6f83 | ||
|
|
c7097faba2 | ||
|
|
e9a341a013 | ||
|
|
15839ba75d | ||
|
|
cd0b24c16a | ||
|
|
7151619645 | ||
|
|
684c4037b6 | ||
|
|
97a50faf16 | ||
|
|
ca2a6a1e60 | ||
|
|
61a112c221 | ||
|
|
8e079a970f | ||
|
|
a4bc22fc3b | ||
|
|
0f81ce1716 | ||
|
|
97deb63deb | ||
|
|
a3b91a4363 | ||
|
|
2551a0f11e | ||
|
|
2f5b06b628 | ||
|
|
575195c67a | ||
|
|
9c20fe892e | ||
|
|
4b626a160d | ||
|
|
bfef852de7 | ||
|
|
dbb859cce9 | ||
|
|
abb451349c | ||
|
|
e77ecaa48c | ||
|
|
1895a75bc7 | ||
|
|
0da184ab73 | ||
|
|
f9357d0860 | ||
|
|
f7bd209efa | ||
|
|
f794ffdace | ||
|
|
504323d5b7 | ||
|
|
9450a952d4 | ||
|
|
62394d0e71 | ||
|
|
8d9697d06d | ||
|
|
d4744d300b | ||
|
|
9b7275e713 | ||
|
|
51ed3a4c2c | ||
|
|
d9e967610e | ||
|
|
2cfbd655cb | ||
|
|
3ed5e5275e | ||
|
|
d997aeca1e | ||
|
|
3b371f3588 | ||
|
|
256b296521 | ||
|
|
38668f8de1 | ||
|
|
b68aaa0a00 | ||
|
|
36e58378e4 | ||
|
|
0ef2378dc9 | ||
|
|
d1d4a1bfb7 | ||
|
|
e22649c19d | ||
|
|
25f749bb0c | ||
|
|
e45ee1730e | ||
|
|
6bd3c6b0ca | ||
|
|
6f65a958ae | ||
|
|
53a4ab4974 | ||
|
|
6e4ed3f328 | ||
|
|
4a292c8030 | ||
|
|
29fbfd4479 | ||
|
|
500e5d508a | ||
|
|
396ac023c4 | ||
|
|
e32f40caee | ||
|
|
4b3cdf9ca4 | ||
|
|
2fc4c8bd60 | ||
|
|
47e7d69897 | ||
|
|
4f830cdd40 | ||
|
|
00e9de3b1e | ||
|
|
ddb4e55d13 | ||
|
|
7e0d79ac68 | ||
|
|
c2dfcbe343 | ||
|
|
582fffb1f4 | ||
|
|
e67756d055 | ||
|
|
42b73006a8 | ||
|
|
2fc85d6810 | ||
|
|
141e1a4ea0 | ||
|
|
097db2f340 | ||
|
|
b49c764c54 | ||
|
|
385387b739 | ||
|
|
87218962e1 | ||
|
|
88d8248f63 | ||
|
|
ffbda10200 | ||
|
|
7eb8b21cea | ||
|
|
986a791801 | ||
|
|
c834a6e840 | ||
|
|
f14c36223f | ||
|
|
d66c8e0ad8 | ||
|
|
8673f366d7 | ||
|
|
88e48ebcf7 | ||
|
|
ea3eacbbae | ||
|
|
9f3941d1a1 | ||
|
|
1a6a10a834 | ||
|
|
d780e2aab2 | ||
|
|
a4bc34e633 | ||
|
|
9c8bb7efeb | ||
|
|
d9b37efbbd | ||
|
|
d23b7942b6 | ||
|
|
f41c3a5334 | ||
|
|
d8e8f90131 | ||
|
|
0358afccf9 | ||
|
|
9f58327d0a | ||
|
|
d3ed6e21e4 | ||
|
|
a171abb741 | ||
|
|
52825e0e59 | ||
|
|
de83f19388 | ||
|
|
4d993f2ef7 | ||
|
|
ca81be3865 | ||
|
|
19ae05cb7d | ||
|
|
79ec41556e | ||
|
|
088377582d | ||
|
|
c8b3973b8f | ||
|
|
efc490cc0d | ||
|
|
82a2ba4a86 | ||
|
|
ccbe20eb57 | ||
|
|
70a2feb92a | ||
|
|
a4d542067a | ||
|
|
efd222133e | ||
|
|
361491ebd2 | ||
|
|
6aeba4f599 | ||
|
|
559ab69940 | ||
|
|
b35dae9fab | ||
|
|
9d96fcaa45 | ||
|
|
e75be340e2 | ||
|
|
523f02b4c2 | ||
|
|
a5b7332fa4 | ||
|
|
edb4ed02c9 | ||
|
|
96da8e3276 | ||
|
|
c056bfcf7a | ||
|
|
2ddf1a1762 | ||
|
|
8a9ddfded7 | ||
|
|
a83f97e1ef | ||
|
|
3630bf5b9a | ||
|
|
b9d3a4d04c | ||
|
|
c7fb636e49 | ||
|
|
c7ea6c4ca4 | ||
|
|
1ffda9c413 | ||
|
|
e2f314d007 | ||
|
|
80d8307822 | ||
|
|
1ca5ed4520 | ||
|
|
8115f9df74 | ||
|
|
306cbaa837 | ||
|
|
efa31059d9 | ||
|
|
73089634f9 | ||
|
|
a84e5e3460 | ||
|
|
eca11d0317 | ||
|
|
ced485856e | ||
|
|
08c54f75eb | ||
|
|
6c0cc76773 | ||
|
|
4d53bfb3f0 | ||
|
|
e332edb36b | ||
|
|
70b5d9adc6 | ||
|
|
23ee9508c2 | ||
|
|
717391afb3 | ||
|
|
38e0d40364 | ||
|
|
7053c2ee03 | ||
|
|
cdde730fc5 | ||
|
|
95b0119d22 | ||
|
|
07a965a3e0 | ||
|
|
eba153e289 | ||
|
|
fc5238c56f | ||
|
|
bc367b9f56 | ||
|
|
8adcc98ff1 | ||
|
|
ba07041c26 | ||
|
|
d3101b1cd8 | ||
|
|
3a9a86020b | ||
|
|
42afdc2994 | ||
|
|
1dc87de975 | ||
|
|
6c93208e11 | ||
|
|
49ba2e72a9 | ||
|
|
bf1ca4e5fa | ||
|
|
3f9e64016b | ||
|
|
c672967caf | ||
|
|
1e0dac0fbc | ||
|
|
aec8de54bd | ||
|
|
a960093cf6 | ||
|
|
4601e49106 | ||
|
|
7e65db38cf | ||
|
|
d70bd63659 | ||
|
|
47dc0d579b | ||
|
|
ef6662d4f8 | ||
|
|
246f9240e7 | ||
|
|
a8ebb9cdcb | ||
|
|
bdb2d6cf9e | ||
|
|
b9aaaa7d37 | ||
|
|
ec43039b94 | ||
|
|
4939b85819 | ||
|
|
fab7596d06 | ||
|
|
81d67f640b | ||
|
|
1966c8e0c6 | ||
|
|
a418f47d1c | ||
|
|
df516fbd2d | ||
|
|
b8e298c524 | ||
|
|
d96c9b16bc | ||
|
|
3df7156ba3 | ||
|
|
4376605d5e | ||
|
|
3233187fd9 | ||
|
|
4d8a2f28d3 | ||
|
|
930f74ff27 | ||
|
|
4ddb8fe9c5 | ||
|
|
74aed2144d | ||
|
|
87437883a6 | ||
|
|
d946cb4765 | ||
|
|
37c9707c63 | ||
|
|
02d1badf0d | ||
|
|
2a295237a6 | ||
|
|
dc8b0b8359 | ||
|
|
d36a5b30e6 | ||
|
|
fe8f217587 | ||
|
|
b69958007d | ||
|
|
616fbdba00 | ||
|
|
f433a3163b | ||
|
|
753221e429 | ||
|
|
d5f41d8405 | ||
|
|
3490834108 | ||
|
|
64bef0bd06 | ||
|
|
e4ea5cd769 | ||
|
|
c8790f766a | ||
|
|
2243dc566e | ||
|
|
94c2d9182c | ||
|
|
12d6374158 | ||
|
|
58b9c27098 | ||
|
|
f68aac2015 | ||
|
|
ca5334fd87 | ||
|
|
71961637f1 | ||
|
|
5aab80aed6 | ||
|
|
12e7c82698 | ||
|
|
bb82590e5e | ||
|
|
1855fe3045 | ||
|
|
e3dba306a7 | ||
|
|
74f79a2f52 | ||
|
|
ca0c17a41b | ||
|
|
3acaf5b2c0 | ||
|
|
eedf19474e | ||
|
|
a3de2c1983 | ||
|
|
8bd2e5e5f3 | ||
|
|
5e2a7c82c0 | ||
|
|
21b65a92fe | ||
|
|
2c384af157 | ||
|
|
7149a20865 | ||
|
|
407dbc4c0a | ||
|
|
eff1954260 | ||
|
|
81880779c3 | ||
|
|
21a3386574 | ||
|
|
a9ef7e47b5 | ||
|
|
0c19f82a24 | ||
|
|
9762a81a59 | ||
|
|
c34c99a874 | ||
|
|
c19aad55a7 | ||
|
|
744260bea5 | ||
|
|
dfe480d1f7 | ||
|
|
7620b7878e | ||
|
|
99fd77c1f1 | ||
|
|
e0f1137ee2 | ||
|
|
079a7ad439 | ||
|
|
ae100f66c7 | ||
|
|
f539659ab5 | ||
|
|
e4d9b902d8 | ||
|
|
9fb180ff20 | ||
|
|
a6fde760ff | ||
|
|
7b0e3edfcf | ||
|
|
019bbaa0d2 | ||
|
|
94f3c5a6c4 | ||
|
|
c3f9e5fa91 | ||
|
|
99b71ad2aa | ||
|
|
c127f488ff | ||
|
|
ba7a28aa8a | ||
|
|
95a7f7ccb6 | ||
|
|
f90961ce22 | ||
|
|
58c5eeb6d1 | ||
|
|
6c03a263a1 | ||
|
|
862e199495 | ||
|
|
5218b748e8 | ||
|
|
ba9bd53c1e | ||
|
|
8b816f1491 | ||
|
|
04cc18553b | ||
|
|
cbb638ff7a | ||
|
|
9cd2518853 | ||
|
|
2db8d717d4 | ||
|
|
df56c85e7b | ||
|
|
fb12d429cb | ||
|
|
c1c067522c | ||
|
|
cdf537afaa | ||
|
|
7867cc934b | ||
|
|
1ec7c141b9 | ||
|
|
0b7cdd656e | ||
|
|
9b95625603 | ||
|
|
76ef5558ea | ||
|
|
c0e1951f27 | ||
|
|
00f2ea75ac | ||
|
|
e4d59e702f | ||
|
|
3def034af8 | ||
|
|
779466ed8d | ||
|
|
38a1139475 | ||
|
|
a5f02c09d8 | ||
|
|
c795feff74 | ||
|
|
c99d7db058 | ||
|
|
8beccfb510 | ||
|
|
16d43c6766 | ||
|
|
6a0147e0f4 | ||
|
|
4d1ea957c3 | ||
|
|
6f9bad5f11 | ||
|
|
3182bc68ab | ||
|
|
f24ac03034 | ||
|
|
74f00e4763 | ||
|
|
1ce33fe146 | ||
|
|
19827be1d8 | ||
|
|
c789392e0a | ||
|
|
c09485affc | ||
|
|
490ad06cf5 | ||
|
|
76ce62f942 | ||
|
|
6bcdc944bf | ||
|
|
183e070cad | ||
|
|
6dad1e9965 | ||
|
|
fe3b46eb14 | ||
|
|
d03bde5e9d | ||
|
|
70426087fc | ||
|
|
8bcec6b898 | ||
|
|
195049f010 | ||
|
|
a26955e07d | ||
|
|
9330b40d14 | ||
|
|
e61f4c96b3 | ||
|
|
58e4de2f77 | ||
|
|
9be342f0c1 | ||
|
|
f1af9e31d0 | ||
|
|
51e58c0e58 | ||
|
|
81ae1ef059 | ||
|
|
a7caf78e6c | ||
|
|
05ca579dcb | ||
|
|
498d46320a | ||
|
|
58a1e486fb | ||
|
|
2eb3ff4347 | ||
|
|
79e1adbf7b | ||
|
|
5f214dc933 | ||
|
|
6582065be0 | ||
|
|
3b6c998f67 | ||
|
|
83889f2cbc | ||
|
|
d8810478fe | ||
|
|
1a4ba59a2a | ||
|
|
ff19a700d9 | ||
|
|
c5ee98ebaa | ||
|
|
3dbfd2ebfd | ||
|
|
ec3f246576 | ||
|
|
78b1fe8663 | ||
|
|
cfe51cdf82 | ||
|
|
a71b6b3b99 | ||
|
|
0e28c9d01c | ||
|
|
e722e666c2 | ||
|
|
8730a7def3 | ||
|
|
5fa6bee510 | ||
|
|
8adf682946 | ||
|
|
b2bf529ed7 | ||
|
|
2203086c87 | ||
|
|
094fc47990 | ||
|
|
92c1508b8c | ||
|
|
466915f6e9 | ||
|
|
1c68cfa08e | ||
|
|
cc75872c04 | ||
|
|
7e93866596 | ||
|
|
f4213eefe9 | ||
|
|
72bfafb376 | ||
|
|
5dbacfddd5 | ||
|
|
e29ea5d89b | ||
|
|
2885d929a8 | ||
|
|
a3e5bc4fba | ||
|
|
bb19f737d5 |
No files matched your search
Vendored
+14
-61
@@ -42,7 +42,18 @@
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Excel Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": ["${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64.xll"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
@@ -54,7 +65,7 @@
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
"request": "attach",
|
||||
@@ -198,7 +209,7 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test","Co2RelayTests"],
|
||||
"args": ["test","Co2Tests"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
@@ -264,63 +275,5 @@
|
||||
"RedirectOutput"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "edge-asp051",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/sparkd",
|
||||
"args": ["run"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@edge-asp051"],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/bts/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Docker Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/bts/build/gcc_debug/src/server/sparkd/sparkd",
|
||||
"args": ["test"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/bts/build/gcc_debug/src/server/sparkd",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["-p 2000","root@localhost"],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/bts/": "${workspaceRoot}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+4
-2
@@ -39,21 +39,23 @@ else ()
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} \
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
--std=c++17 -fdiagnostics-color -Wl,--no-undefined")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
add_subdirectory(src/web)
|
||||
add_subdirectory(doc/user)
|
||||
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
|
||||
COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/server/plugins
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/sdk/examples/order_plugin_example.so ${CMAKE_BINARY_DIR}/server/plugins/
|
||||
COMMENT "Creating server runtime layout"
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
|
||||
@@ -25,3 +25,47 @@
|
||||
***** FillStore
|
||||
***** SecuritySrc
|
||||
* Benchmarks
|
||||
* OrderMan
|
||||
** OrderServices
|
||||
*** callbacks
|
||||
**** standard
|
||||
- update
|
||||
- cancel
|
||||
- crack
|
||||
- activate
|
||||
- book
|
||||
- settlement
|
||||
- child order state
|
||||
- timer
|
||||
**** low level
|
||||
- exe reports
|
||||
- cancel rejects
|
||||
- gateway status
|
||||
*** services
|
||||
**** standard
|
||||
- secdb
|
||||
- get time
|
||||
- schedule actions
|
||||
- order builder
|
||||
- activate
|
||||
- update
|
||||
- cancel
|
||||
- crack
|
||||
**** low level
|
||||
- governor
|
||||
- send fix
|
||||
- get time str
|
||||
- limiter
|
||||
** Internalize plan
|
||||
- change order initiated update/cancel/crack/activate/report_state to
|
||||
use direct method calls
|
||||
- need to watch out for reentrancy
|
||||
- need to also report state externally
|
||||
- also want to separate active from non active orders in map to speed
|
||||
up look ups
|
||||
- want to use direct references in orders to avoid lookups
|
||||
- want to change how time is captured, the trace is too big to copy
|
||||
around
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;/home/ppinto/Dropbox/bts/spark/doc/user/mkdocs.yml;bts-help-theme/404.html;bts-help-theme/base.html;bts-help-theme/content.html;bts-help-theme/css/base.css;bts-help-theme/css/bts.css;bts-help-theme/css/font-awesome.min.css;bts-help-theme/css/highlight.css;bts-help-theme/css/local.css;bts-help-theme/fonts/fontawesome-webfont.eot;bts-help-theme/fonts/glyphicons-halflings-regular.eot;bts-help-theme/img/favicon.ico;bts-help-theme/js/base.js;bts-help-theme/js/bootstrap-3.0.3.min.js;bts-help-theme/js/highlight.pack.js;bts-help-theme/js/jquery-1.10.2.min.js;bts-help-theme/keyboard-modal.html;bts-help-theme/main.html;bts-help-theme/nav-sub.html;bts-help-theme/nav.html;bts-help-theme/search-modal.html;bts-help-theme/toc.html;docs/Help.md;docs/Launcher.md;docs/Logging-In-to-the-System.md;docs/Market.md;docs/My-Windows.md;docs/Settings.md;docs/Spreader.md;docs/Trading.md;docs/images/image1.png;docs/images/image10.png;docs/images/image11.png;docs/images/image12.png;docs/images/image13.png;docs/images/image14.png;docs/images/image15.png;docs/images/image16.png;docs/images/image17.png;docs/images/image18.png;docs/images/image19.png;docs/images/image2.png;docs/images/image20.png;docs/images/image21.png;docs/images/image22.png;docs/images/image23.png;docs/images/image24.png;docs/images/image25.png;docs/images/image26.png;docs/images/image27.png;docs/images/image28.png;docs/images/image29.png;docs/images/image3.png;docs/images/image30.png;docs/images/image31.png;docs/images/image32.png;docs/images/image33.png;docs/images/image34.png;docs/images/image35.png;docs/images/image36.png;docs/images/image37.png;docs/images/image38.png;docs/images/image39.png;docs/images/image4.png;docs/images/image40.png;docs/images/image41.png;docs/images/image42.png;docs/images/image43.png;docs/images/image44.png;docs/images/image45.png;docs/images/image46.png;docs/images/image47.png;docs/images/image48.png;docs/images/image49.png;docs/images/image5.png;docs/images/image50.png;docs/images/image51.png;docs/images/image52.png;docs/images/image53.png;docs/images/image54.png;docs/images/image55.png;docs/images/image56.png;docs/images/image57.png;docs/images/image58.png;docs/images/image59.png;docs/images/image6.png;docs/images/image60.png;docs/images/image61.png;docs/images/image62.png;docs/images/image63.png;docs/images/image64.png;docs/images/image65.png;docs/images/image66.png;docs/images/image67.png;docs/images/image68.png;docs/images/image69.png;docs/images/image7.png;docs/images/image70.png;docs/images/image71.png;docs/images/image72.png;docs/images/image73.png;docs/images/image74.png;docs/images/image76.png;docs/images/image8.png;docs/images/image9.png;docs/index.md;docs/release_notes.md)
|
||||
@@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(doc)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/tools/safe_dep_glob.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/tools/Protobuf.cmake)
|
||||
|
||||
# DEPENDENCIES
|
||||
set(DOC_GLOBS
|
||||
*.md
|
||||
*.jpeg
|
||||
*.png
|
||||
*.html
|
||||
*.css
|
||||
*.js
|
||||
*.eot
|
||||
favicon.ico
|
||||
)
|
||||
file (GLOB_RECURSE All_Deps RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${DOC_GLOBS})
|
||||
set (All_Deps ${All_Deps} ${CMAKE_CURRENT_SOURCE_DIR}/build.sh ${CMAKE_CURRENT_SOURCE_DIR}/mkdocs.yml)
|
||||
update_deps_file("${All_Deps}")
|
||||
# END DEPENDENCIES
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/web/docs/index.html
|
||||
COMMAND /bin/bash ${CMAKE_CURRENT_SOURCE_DIR}/build.sh
|
||||
ARGS ${CMAKE_BINARY_DIR}/web/docs/
|
||||
COMMENT "Building Manual UI"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
DEPENDS ${All_Deps})
|
||||
|
||||
add_custom_target(doc_manual_bundle ALL DEPENDS ${CMAKE_BINARY_DIR}/web/docs/index.html)
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/css/local.css?1529096332" type="text/css" />
|
||||
<!--link rel="stylesheet" href="https://www.bluetradingsystems.com/templates/bluetradesys/scripts/colorbox-master/example5/colorbox.css" type="text/css" /-->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(../fonts/Source-Sans-Pro-300.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(../fonts/Source-Sans-Pro-400.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), url(../fonts/Source-Sans-Pro-600.ttf) format('truetype');
|
||||
}
|
||||
@@ -30,9 +30,8 @@
|
||||
{% if nav_item.active%}
|
||||
<ul>
|
||||
{%- for toc_item in page.toc %}
|
||||
<li class="main {% if toc_item.active %}active{% endif %}"><a href="{{ toc_item.url|url }}">{{ toc_item.title }}</a></li>
|
||||
{%- for toc_item in toc_item.children %}
|
||||
<li><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
|
||||
{%- for toc_subitem in toc_item.children %}
|
||||
<li><a href="{{ toc_subitem.url }}">{{ toc_subitem.title }}</a></li>
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
|
||||
+10
-1
@@ -1,2 +1,11 @@
|
||||
#!/bin/bash
|
||||
python -m mkdocs build -d $1
|
||||
tmpfile=$(mktemp /tmp/quiet_build_log.XXXX)
|
||||
python -m mkdocs build -d $1 >$tmpfile 2>&1
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
rm $tmpfile
|
||||
exit 0
|
||||
else
|
||||
cat $tmpfile
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,5 +1,9 @@
|
||||
#Help
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGQfr_puT3w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
##Report a Problem
|
||||
If there is any issue at all, you can click here
|
||||
and it’ll bring up a “Report a Problem” window, which you can type
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#Module Launcher
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/OxVqQBKFIDw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- The module launcher is the first item that will come up after
|
||||
logging in to Spark.
|
||||
- This is your central control for all modules
|
||||
@@ -9,7 +13,7 @@
|
||||
|
||||

|
||||
|
||||
- If you right click anywhere on the launcher you can modify
|
||||
- If you right-click anywhere on the launcher you can modify
|
||||
various display settings including:
|
||||
- Vertical layout, instead of horizontal
|
||||
- Showing the text of the individual modules
|
||||
@@ -25,5 +29,5 @@
|
||||
Some common features within BTS Spark
|
||||
|
||||
- All Windows are tabbed so they can be combined or separated to organize your workspace
|
||||
- Some Spark windows have a menu on the left hand side where you can launch related modules or change settings. You can see the menu item names either by hovering your mouse over the icon, or clicking on the right edge of the menu (a blue bar will appear).
|
||||
- Some Spark windows have a menu on the left-hand side where you can launch related modules or change settings. You can see the menu item names either by hovering your mouse over the icon or clicking on the right edge of the menu (a blue bar will appear).
|
||||
- Some table columns have filtering capabilities, where if you hover over the column header, it will display a filter icon, which enables you to filter the table based on the information in that column.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The shortcut icon will appear on your desktop like below:
|
||||
|
||||

|
||||

|
||||
|
||||
Simply double click or single click and hit enter to launch the
|
||||
login screen:
|
||||
|
||||
+16
-8
@@ -9,6 +9,10 @@
|
||||
|
||||
##Market View
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y54S1uscT_g" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- This provides a grid of outright, spread, and synthetic products
|
||||
showing real-time market data all in one place
|
||||
- You can create multiple market view grids and customize the
|
||||
@@ -29,7 +33,7 @@
|
||||
and all securities will be displayed. This will show up in the
|
||||
selected securities window as “<symbol> future at
|
||||
<exchange>”
|
||||
- Synthetic’s can be displayed by clicking on exchange “BTS”, then
|
||||
- Synthetics can be displayed by clicking on exchange “BTS”, then
|
||||
synthetic, symbol, and finally under the securities window.
|
||||
- Below is an example of an outright, spread, all futures, and
|
||||
synthetic chosen:
|
||||
@@ -98,10 +102,10 @@
|
||||
either the Bid or Ask price and volume.
|
||||
- Highlighting a single security and clicking on the order ticket
|
||||
 icon
|
||||
on the left hand browser window of the market view
|
||||
on the left-hand browser window of the market view
|
||||
|
||||
- Launch a ladder for 1 security
|
||||
- You can double click on any security in the market view, besides
|
||||
- You can double-click on any security in the market view, besides
|
||||
the Bid Qty, Bid Price, Ask Price, or Ask Qty columns to launch a
|
||||
ladder for that Security.
|
||||
|
||||
@@ -110,7 +114,7 @@
|
||||
- If you hold down the Ctrl key and select multiple securities, you
|
||||
can then click on the ladder icon
|
||||
 on
|
||||
the left hand side to bring up the selected ladders all at
|
||||
the left-hand side to bring up the selected ladders all at
|
||||
once.
|
||||
|
||||
- Launching a Fills window for 1 or many securities
|
||||
@@ -118,7 +122,7 @@
|
||||
- This is done by either highlighting 1 or many securities and then
|
||||
clicking on the Fills icon
|
||||
 on
|
||||
the left hand side to bring up a fill window for the selected
|
||||
the left-hand side to bring up a fill window for the selected
|
||||
securities.
|
||||
|
||||
- Please note - If there haven’t been any fills for the day in that
|
||||
@@ -129,14 +133,14 @@
|
||||
- This is accomplished by highlighting 1 or many securities and then
|
||||
clicking on the time and sales icon
|
||||
 on
|
||||
the left hand side
|
||||
the left-hand side
|
||||
|
||||
- Launching the synthetic window to create a synthetic product
|
||||
|
||||
- This is accomplished by highlighting either 1 or 2 securities and
|
||||
clicking on the Synthetic icon
|
||||
 on
|
||||
the lower left hand side
|
||||
the lower left-hand side
|
||||
|
||||
- Please note - If only 1 security is chosen, a create synthetic
|
||||
window will appear, and you will be required to add another leg from
|
||||
@@ -144,7 +148,11 @@
|
||||
|
||||
##Time and Sales
|
||||
|
||||
- Provides a display of real time fills as they happen for 1 or many
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/nEkSGacjt0s" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- Provides a display of real-time fills as they happen for 1 or many
|
||||
securities
|
||||
- You can create multiple time and sale windows and customize the
|
||||
security selection to fit your needs
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#My Windows
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/pjm1JJ9Q3jM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
This is the housing of the windows that you have manually named and
|
||||
creates a place where you can quickly and easily launch windows
|
||||
which you’ve customized.
|
||||
@@ -18,9 +22,9 @@ section of the module launcher.
|
||||
|
||||

|
||||
|
||||
Right clicking on a tab that has already had its default title modified
|
||||
Right-clicking on a tab that has already had its default title modified
|
||||
will show the following which will allow you to rename the modified
|
||||
title or remove it from the my windows, which will then restore the
|
||||
title or remove it from the My Windows, which will then restore the
|
||||
default standard title.
|
||||
|
||||

|
||||
@@ -1,6 +1,10 @@
|
||||
|
||||
#Settings
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/yGQfr_puT3w" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
##Workspaces
|
||||
|
||||
This is where your workspaces can be saved or switched
|
||||
|
||||
+34
-14
@@ -1,14 +1,35 @@
|
||||
|
||||
#Spreader
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/XEZpuPAgMC0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/EVFK2yNQFS4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/d5ywlHen6yo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Mmw8PNJA0Dw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/QK-bBSH6Rc0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- Launching the spreader from the module launcher will bring up the
|
||||
synthetic management browser which appears like below:
|
||||
|
||||

|
||||
|
||||
- From this window it’ll show any synthetics that you have already
|
||||
- From this window, it will show any synthetics that you have already
|
||||
created as well as various other options of both managing the
|
||||
synthetics and other tasks all from the left hand side pane within
|
||||
synthetics and other tasks all from the left-hand side pane within
|
||||
the window, including:
|
||||
|
||||
- Adding a new synthetic 
|
||||
@@ -30,7 +51,7 @@
|
||||
- From the main module launcher, you can go to spreader, then
|
||||
synthetics, then click add
|
||||
- Highlighting one or more securities in a market view and clicking
|
||||
on create synthetic
|
||||
on Create Synthetic
|
||||
- Cloning an existing security
|
||||
|
||||
- An empty synthetic browser will look like below without any legs
|
||||
@@ -43,10 +64,10 @@
|
||||
- Upon adding legs, you can then go through each of the parameters to
|
||||
customize how you would like the spread to be worked in the
|
||||
market
|
||||
- We’ll start at the top of create synthetic window and work our way
|
||||
- We will start at the top of the Create Synthetic window and work our way
|
||||
down:
|
||||
|
||||
- At the very top of the create synthetic window you will have to
|
||||
- At the very top of the Create Synthetic window you will have to
|
||||
name your synthetic product. By default, it will auto-populate
|
||||
based on the symbols you have chosen:
|
||||
|
||||
@@ -73,16 +94,15 @@
|
||||
exchange
|
||||
- Furthermore, if you are ever unsure of what a parameter’s function
|
||||
is and how it is controlled you can hover over the parameter’s title
|
||||
and a “?” will be shown. Clicking on this will expand built in
|
||||
and a “?” will be shown. Clicking on this will expand built-in
|
||||
details of how that parameter functions. Below is an example of the
|
||||
throttle details displayed:
|
||||
|
||||

|
||||
|
||||
- Main parameters - these are the top 4 rows in the create synthetic
|
||||
- Main parameters - these are the top 4 rows in the Create Synthetic
|
||||
window which all synthetics will be required to have set.
|
||||
Everything below these are custom to a traders needs, however the
|
||||
top 4 are the main foundational aspects used for the calculation of
|
||||
Everything below these can be customized to a trader's needs, however, the top 4 are the main foundational aspects used for the calculation of
|
||||
the synthetic:
|
||||
|
||||

|
||||
@@ -95,7 +115,7 @@
|
||||
be priced, by default, it’ll be based on the same as the quantity of
|
||||
the synthetic produced, but you can change it to modify the balance
|
||||
of the price, giving more weighting to one leg over another.
|
||||
- Enable quoting - This parameter is in the form of a check box,
|
||||
- Enable quoting - This parameter is in the form of a checkbox,
|
||||
where if the box is checked the leg will be quoted versus not
|
||||
checked, it will not be shown in the market. If it is not actively
|
||||
quoted in the market, the leg will still be used for calculation of
|
||||
@@ -107,7 +127,7 @@
|
||||
If your Hedge Ratio is 3 on the NQ leg, then you’ll work the ES
|
||||
order based on where you could fill 6 (2 (Qty of NQ) \* 3 (Hedge
|
||||
Ratio)).
|
||||
- The next 6 parameters are all completely custom to a traders needs
|
||||
- The next 6 parameters are all completely custom to a trader's needs
|
||||
and can further define how a synthetic is worked. As previously
|
||||
mentioned, if you hover over any of these parameters a “?” will be
|
||||
displayed and click on such will display further details on how this
|
||||
@@ -118,7 +138,7 @@
|
||||
- Quote
|
||||
|
||||
- Throttle - This can be enabled or disabled based on if the box is
|
||||
checked per leg. It’s main function is to reduce re-quoting updates
|
||||
checked per leg. Its main function is to reduce re-quoting updates
|
||||
by defining a region on either side of the working quote order,
|
||||
inside which quote updates will not be sent:
|
||||
|
||||
@@ -148,8 +168,8 @@
|
||||
|
||||

|
||||
|
||||
- Time Based Cross - Basically the exact same as ticks away cross,
|
||||
however this is based on time (in ms) on when to automatically cross
|
||||
- Time Based Cross - Basically the same as Ticks Away Cross,
|
||||
however, this is based on time (in ms) on when to automatically cross
|
||||
(fill) the market if they get too far away from the opposite inside
|
||||
market. If a legged hedge order’s price gets X amount of “ticks”
|
||||
away from the opposite inside market, it is re-priced to cross the
|
||||
|
||||
+57
-50
@@ -9,7 +9,11 @@
|
||||
|
||||
##Order Book
|
||||
|
||||
- This provides list of all orders a user has placed, by default it
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/ygMFkjBBGeU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- This provides a list of all orders a user has placed, by default it
|
||||
will display only working orders
|
||||
- You can create multiple order books filtered however you would
|
||||
like
|
||||
@@ -44,39 +48,39 @@
|
||||
- Anytime
|
||||
- Today
|
||||
- Since Open
|
||||
- A specific user defined period of time
|
||||
- A specific user-defined period of time
|
||||
|
||||

|
||||
|
||||
- The left hand box within an order book provides users with further
|
||||
- The left-hand box within an order book provides users with further
|
||||
functionality to quickly perform various tasks with live orders as
|
||||
well as launch other modules. It’s appears like the below:
|
||||
well as launch other modules. It appears like the below:
|
||||
|
||||

|
||||
|
||||
- ][Fills -
|
||||
- Fills -
|
||||
clicking on this will launch a fills window
|
||||
- ][ Modify -
|
||||
-  Modify -
|
||||
clicking on this will launch an order ticket for a live order
|
||||
highlighted in the Order Book.
|
||||
- ][Cancel
|
||||
- Cancel
|
||||
selected - clicking on this will cancel the selected orders you have
|
||||
highlighted in the order book. You can choose more than one by
|
||||
holding hold the Ctrl key when click on each row of orders
|
||||
- ][Cancel
|
||||
holding hold the Ctrl key when clicking on each row of orders
|
||||
- Cancel
|
||||
buys - clicking on this will cancel all the buy orders you have in
|
||||
the order book
|
||||
- ][Cancel
|
||||
- Cancel
|
||||
sells - clicking on this will cancel all the sell orders you have in
|
||||
the order book
|
||||
- ][Cancel
|
||||
- Cancel
|
||||
all - clicking on this will cancel all working orders you have the
|
||||
order book
|
||||
|
||||
##Fills
|
||||
|
||||
- This provides list of all fills a user has had for a given time
|
||||
period, by default it will open up with “since open” time frame
|
||||
- This provides a list of all fills a user has had for a given time
|
||||
period, by default it will open up with “since open” timeframe
|
||||
selected showing you fills on the day thus far
|
||||
- You can create multiple fill windows, filtered however you
|
||||
like
|
||||
@@ -85,8 +89,12 @@
|
||||
|
||||
##P&L
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/xj6cEQ6By34" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- This provides a running ledger of a users account(s) profit and
|
||||
loss broken down per symbol
|
||||
loss, broken down per symbol
|
||||
- You can create multiple P&L windows, filtered however you would
|
||||
like
|
||||
- All P&L windows are brought up as tabs and can be combined with all
|
||||
@@ -101,6 +109,10 @@
|
||||
|
||||
##Ladder
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Q0qsyc1nvYo" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- The ladder was designed to be the most logically produced ladder on
|
||||
the market, engineered and designed by professional traders for
|
||||
professional traders
|
||||
@@ -112,8 +124,8 @@
|
||||
|
||||

|
||||
|
||||
- The ladder consist of 2 major parts, the Ladder itself and the
|
||||
information and control section on the left hand side
|
||||
- The ladder consists of 2 major parts, the Ladder itself and the
|
||||
information and control section on the left-hand side
|
||||
|
||||
- The information section on the left contains the following: 
|
||||
- Day - showing the current price of the security on the day
|
||||
@@ -126,17 +138,17 @@
|
||||
|
||||
|
||||
- The control section right underneath the information section on the
|
||||
left hand side offers the following:
|
||||
left-hand side offers the following:
|
||||
|
||||

|
||||
|
||||
- Account in which the orders will flow into when trading this
|
||||
- The account in which the orders will flow into when trading this
|
||||
security
|
||||
- Length of time the order will remain in market, either DAY or
|
||||
- Length of time the order will remain in the market, either DAY or
|
||||
GTC
|
||||
- Order type, by default orders will go in as Limit, however all of
|
||||
- Order type, by default, orders will go in as Limit, however all of
|
||||
the following are offered out of the box. The top 5 are all fairly
|
||||
standard order types, however the bottom 5 are all specialized
|
||||
standard order types, however, the bottom 5 are all specialized
|
||||
algos. They are only offered by BTS and require specialized
|
||||
parameters. Please see below for each order type:
|
||||
|
||||
@@ -147,8 +159,8 @@
|
||||
|
||||
- Limit
|
||||
|
||||
- This is a buy or sell order designed to take place on the exact
|
||||
price level you have entered the order at.
|
||||
- This is a buy or sell order designed to take place on the
|
||||
price level where you entered the order.
|
||||
|
||||
- Stop
|
||||
|
||||
@@ -168,10 +180,10 @@
|
||||
- Iceberg
|
||||
|
||||
- This is a buy or sell order where the order is divided into a
|
||||
visible or “show” portion which is seen at the exchange. ][The
|
||||
visible or “show” portion which is seen at the exchange. The
|
||||
rest of the order is hidden and will remain hidden until the show
|
||||
quantity is filled, essentially reloading the order back to the show
|
||||
quantity. You will need to ][define what type of order that will
|
||||
quantity. You will need to define what type of order that will
|
||||
be used for the child order to be sent.
|
||||
|
||||

|
||||
@@ -198,7 +210,7 @@
|
||||
|
||||

|
||||
|
||||
- Tick
|
||||
- Lean
|
||||
|
||||
- This is a specialized algo order which allows the user to
|
||||
essentially watch the opposite-inside market size and cross when
|
||||
@@ -207,16 +219,9 @@
|
||||
buying into the asks. This is often referred to as “paying up” as
|
||||
well
|
||||
|
||||

|
||||

|
||||
|
||||
- Lean
|
||||
|
||||
- This is a specialized algo order which allows the user to enter an
|
||||
order which will watch the opposite-inside market size and cross
|
||||
when that size goes below the threshold defined in the
|
||||
parameters
|
||||
|
||||

|
||||
|
||||
- Join
|
||||
|
||||
@@ -225,8 +230,7 @@
|
||||
market flips and has at least threshold size as defined.
|
||||
- “Flip” refers to the event where the best offer becomes the best
|
||||
bid or the market trades away from you, i.e. the line moves up in
|
||||
the GUI. This is all true in the reverse way as well if
|
||||
selling.
|
||||
the GUI. The reverse is true if selling.
|
||||
|
||||

|
||||
|
||||
@@ -248,7 +252,7 @@
|
||||
|
||||
- You can manually type in a quantity here as well
|
||||
- Furthermore, if you have a value in the volume bar, you can
|
||||
increase this size by right clicking and the value will increase
|
||||
increase this size by right-clicking and the value will increase
|
||||
incrementally by 1. The same is true if you would like to decrease
|
||||
this value by left click and the value will decrease.
|
||||
- By having the auto clear box checked, it will bring the value back
|
||||
@@ -259,12 +263,12 @@
|
||||
- The bottom section of the control area of a ladder has 4 aspects
|
||||
to it
|
||||
|
||||
- Cancel Buys - which will cancel all buys currently working on the
|
||||
- Cancel Buys, which will cancel all buys currently working on the
|
||||
ladder
|
||||
- Cancel Sells - which will cancel all sells currently working on the
|
||||
- Cancel Sells, which will cancel all sells currently working on the
|
||||
ladder
|
||||
- Cancel All - which will cancel all orders on the ladder
|
||||
- Settings - this will launch the ladder settings window which
|
||||
- Cancel All, which will cancel all orders on the ladder
|
||||
- Settings, which will launch the ladder settings window which
|
||||
appears like below
|
||||
|
||||

|
||||
@@ -276,27 +280,26 @@
|
||||
|
||||
- There are 5 main columns
|
||||
|
||||
- Orders - which display current orders working in the market
|
||||
- Bid - which displays the current bid volumes at each price
|
||||
- Orders, which display current orders working in the market
|
||||
- Bid, which displays the current bid volumes at each price
|
||||
level
|
||||
- Price - which displays the price levels of the symbol shown
|
||||
- Ask - which displays the current ask volumes at each price
|
||||
- Price, which displays the price levels of the symbol shown
|
||||
- Ask, which displays the current ask volumes at each price
|
||||
level
|
||||
- Last Trade - which displays where the price level and volume of
|
||||
- Last Trade, which displays the price level and volume
|
||||
where the last trade occurred
|
||||
|
||||
- Functionality of the ladder
|
||||
|
||||
- To enter an order directly by clicking in the ladder, left click on
|
||||
either the bid or ask volume with a volume greater than 0 on the
|
||||
predefined value box on the left hand section
|
||||
predefined value box on the left-hand section
|
||||
- A badge will be displayed within the orders column of the value you
|
||||
have chosen and the quantity filled as displayed below
|
||||
|
||||

|
||||
|
||||
- To change the price level you wish to work the order, you can right
|
||||
click and hold on the badge to move it up or down the ladder for the
|
||||
- To change the price level you wish to work the order, you can right-click and hold on the badge to move it up or down the ladder for the
|
||||
desired price
|
||||
- To cancel the order, left click on the badge
|
||||
- Furthermore, you can click down on the scroll wheel of your mouse
|
||||
@@ -305,6 +308,10 @@
|
||||
|
||||
## Order Ticket
|
||||
|
||||
<div>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/bdC5pZ6T35I" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
- The order ticket was designed to provide another means other than
|
||||
the ladder to enter orders into the market
|
||||
- You can bring up an order ticket directly from the module launcher,
|
||||
@@ -314,7 +321,7 @@
|
||||
|
||||

|
||||
|
||||
- On the top left you have the price for the order as well as the
|
||||
- On the top left, you have the price for the order as well as the
|
||||
volume
|
||||
- Beneath are the predefined volumes as well as a clear button to
|
||||
clear out the volume
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 11 KiB |
+1
-35
@@ -1,37 +1,3 @@
|
||||
#Getting Started
|
||||
|
||||
If you are new to BTS Spark we recommend starting with these pages to learn the basics.
|
||||
Click on the headings on the left to expand their contents. If you need specific information quickly use the Search button in the top right of this page.
|
||||
|
||||
If you need specific information quickly use the Search button in the top right of the navigation of use the table of contents at the left.
|
||||
|
||||
<ol>
|
||||
<li><a href="Logging-In-to-the-System">Login</a></li>
|
||||
<li><a href="Launcher">Module Launcher</a></li>
|
||||
<li><a href="Market">Market</a>
|
||||
<ul>
|
||||
<li><a href="Market/#market-view">Market View</a></li>
|
||||
<li><a href="Market/#time-and-sales">Time and Sales</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="Trading">Trading</a>
|
||||
<ul>
|
||||
<li><a href="Trading/#order-book">Order Book</a></li>
|
||||
<li><a href="Trading/#fills">Fills</a></li>
|
||||
<li><a href="Trading/#pwzxhzdk0l">P&L</a></li>
|
||||
<li><a href="Trading/#ladder">Ladder</a></li>
|
||||
<li><a href="Trading/#order-ticket">Order Ticket</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="Spreader">Spreader</a></li>
|
||||
<li><a href="My-Windows">My Windows</a></li>
|
||||
<li><a href="Settings">Settings</a>
|
||||
<ul><li><a href="Settings/#workspaces">Workspaces</a></li></ul>
|
||||
</li>
|
||||
<li><a href="Help">Help</a>
|
||||
<ul>
|
||||
<li><a href="Help/#report-a-problem">Report a Problem</a></li>
|
||||
<li><a href="Help/#about-spark">About Spark</a></li>
|
||||
<li><a href="Help/#activity-log">Activity Log</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -0,0 +1,174 @@
|
||||
# Release Notes
|
||||
## 1.4.2
|
||||
|
||||
### User Interface Improvements
|
||||
- When creating a synthetic from the Market View window, securities are now added to the Create Synthetic dialog in the same sort order as they appear in grid
|
||||
### Bug Fixes
|
||||
- Shell no longer crashes when removing a security from the synthetics window
|
||||
- It is now possible to create a new synthetic using the same name as a previously deleted synthetic
|
||||
### Performance Improvements
|
||||
- Synthetic calculation speed was improved by 400%.
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Tabs
|
||||
- Tabs now shrink and expand to make better use of horizontal space
|
||||
### Bug Fixes
|
||||
- Fixed a bug that could cause excessive CPU usage when a connection broke
|
||||
- Fixed a layout bug in hedging/requoting performance reports
|
||||
- Fixed account name mismatching bug for old fills
|
||||
### Absolute Position limits
|
||||
- Positions can now be netted or absolute for purposes of limit enforcing
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Speed Enhancements
|
||||
- Improved requoting and hedging performance
|
||||
- Improved order book form performance
|
||||
### Server Side SDK
|
||||
- Added a beta version of the server side SDK
|
||||
### Front End
|
||||
- Added status bars to all UI grids
|
||||
### Reporting
|
||||
- Added reporting of hedging and requoting performance
|
||||
|
||||
## 1.3.8
|
||||
|
||||
### Speed Enhancements
|
||||
- Further speed enhancements
|
||||
### Conditional (new quoting parameter)
|
||||
- Reduces re-quoting updates by only quoting if N ticks away from the opposite inside market.
|
||||
- Quotes are canceled when it gets further away.
|
||||
- "Price Type" dictates if this logic applies to the quoting market or to the synthetic spread market.
|
||||
|
||||
## 1.3.7
|
||||
|
||||
### Net Change pricing
|
||||
- The ability to price synthetics based off of net change
|
||||
|
||||
## 1.3.5
|
||||
|
||||
### Speed Enhancements
|
||||
- Further speed enhancements
|
||||
### Separation of implied and direct market data
|
||||
- Viewing within a market view will now show 3 columns
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
- Ability to configure the market data size in a ladder as the following
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
### Synthetic creation option to choose Merged or Direct
|
||||
- The ability to choose Merged or Direct per leg when creating a synthetic
|
||||
### Volume at price
|
||||
- Within a ladder, you can now see a volume at price graph
|
||||
- This is configurable from a ladder's settings as well
|
||||
### Customized settings in all windows
|
||||
- All windows now contain a settings page where grid settings (font color, size, etc) are applied
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
|
||||
|
||||
## 1.3.1 Through 1.3.4
|
||||
|
||||
### Speed Enhancements
|
||||
- Further speed enhancements
|
||||
|
||||
## 1.2
|
||||
|
||||
### Market View
|
||||
- Synthetic future leg working orders are now shown
|
||||
|
||||
### Order Book
|
||||
- Many visual facets of the Order Book grid can now be customized
|
||||
- The Order Book settings page can be accessed from the gears icon in the Order Book toolbar or from the right-click menu
|
||||
- The Expand/Collapse All button has been separated into two different buttons on the toolbar
|
||||
|
||||
### Sound Alerts
|
||||
- Sounds alerts can now be configured for various order events
|
||||
- Configure sounds under the Sound/Notification Triggers on the Order Book settings page
|
||||
|
||||
### Fills
|
||||
- Newly launched fill windows will filter out start-of-day records by default
|
||||
|
||||
### PnL
|
||||
- Prices are now formatted with penny precision
|
||||
- Day PnL has been added to the Totals footer at the bottom of the window
|
||||
|
||||
### Ladder
|
||||
- Order badges now have been improved to communicate a variety of order state
|
||||
- Background color now indicates order side
|
||||
- Child orders will display a one-character abbreviation to their parent order enclosed in a rectangle; the rectangle will have a fill color matching the parent order
|
||||
- Border color around badge will match the associated algo color
|
||||
- Exchange orders will present with a solid dot in the badge's top-right corner
|
||||
- When multiple orders reside at the same level, a solid triangle will be present in badge's bottom-right corner; all other badge decorations will not be visible in this state
|
||||
- Hedge orders will present with a vertical line inside the badge's left border
|
||||
- Trigger orders, once triggered, will no longer be shown in the ladder
|
||||
- Session High and Low visual indicators have been added to the Price column
|
||||
- Session range is now indicated in the Price column
|
||||
- A button reflecting position has been added above the quantity field
|
||||
- This button reflects the position for the currently selected account
|
||||
- Pressing the button will copy the position size to the quantity field
|
||||
- Order quantity button size values are now customizable
|
||||
- Default size preset values can now be changed, as well as added or removed
|
||||
- This can be configured per ladder instance (i.e. per security) by making use of the Custom tab
|
||||
- Synthetic ladders that were saved as part of a workspace are now restored on startup
|
||||
|
||||
### Synthetics
|
||||
- Hedge Ratio parameter has been renamed to Lean Qty Factor
|
||||
- Spreader now includes a Hidden mode parameter
|
||||
- A quoting leg can be hidden, ie an order will not be sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order.
|
||||
|
||||
### Algos
|
||||
- Trigger orders now can contain a Trigger Exit parameter based on time or price; once the exit condition has been met the Trigger order will cancel
|
||||
- IOC/FAK order type added
|
||||
- Cover order type added
|
||||
- Canceling a synthetic order no longer cancels any working hedge orders
|
||||
|
||||
### General
|
||||
- A manual page has been added to the launcher Help menu
|
||||
- Various help buttons and tooltips were added throughout the application
|
||||
- Various cash per point values updated
|
||||
- Time-based grid filters now have tick up/down buttons
|
||||
- Numerous performance improvements and stability enhancements
|
||||
- Automated position and Order Book maintenance during daily recycle
|
||||
- Individual fills will be compacted into the Start of Day position records
|
||||
- Orders from the day before the recycle will be deleted
|
||||
|
||||
|
||||
##1.1
|
||||
|
||||
###1.1.22
|
||||
- Order Book: Orders in Error status can now be recreated
|
||||
- Order Book: Orders containing child orders will now be collapsed by default.
|
||||
- Order Book: Expand / Collapse All button added to the right-click menu and window action pane, denoted by ▲ icon
|
||||
- Order Book: Symbol column added to allow for filtering based on group symbol
|
||||
- Time filters now have a quick Set Last Hour hyperlink which will set the Period Begin filter to (Now - 1 hour)
|
||||
|
||||
###1.1.20
|
||||
- Update CME market data handler for price precision extension.
|
||||
https://www.cmegroup.com/confluence/display/EPICSANDBOX/Price+Precision+Extension
|
||||
|
||||
###1.1.8
|
||||
- Enabled wild cards in risk management UI
|
||||
- Fixed spreader order updates
|
||||
|
||||
###1.1.7
|
||||
- Fixed spread leg price fill
|
||||
- Fixed HO cash per point value
|
||||
|
||||
###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 (rather than an 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 ability to recreate canceled orders
|
||||
- Added leg fills to facilitate futures 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,61 +0,0 @@
|
||||
# 1.2
|
||||
|
||||
### Market View
|
||||
- Synthetic future leg working orders are now shown
|
||||
|
||||
### Order Book
|
||||
- Many visual facets of the Order Book grid can now be customized
|
||||
- The Order Book settings page can be access from the gears icon in the Order Book toolbar or from the right-click menu
|
||||
- The Expand/Collapse All button has been separated into two different buttons on the toolbar
|
||||
|
||||
### Sound Alerts
|
||||
- Sounds alerts can now be configured for various order events
|
||||
- Configure sounds under the Sound/Notification Triggers on the Order Book settings page
|
||||
|
||||
### Fills
|
||||
- Newly launched fill windows will filter out start-of-day records by default
|
||||
|
||||
### PnL
|
||||
- Prices are now formatted with penny precision
|
||||
- Day PnL has been added to the Totals footer at the bottom of the window
|
||||
|
||||
### Ladder
|
||||
- Order badges now have been improved to communicate a variety of order state
|
||||
- Background color now indicates order side
|
||||
- Child orders will display a one-character abbreviation to their parent order enclosed in a rectangle; the rectangle will have a fill color matching the parent order
|
||||
- Border color around badge will match the associated algo color
|
||||
- Exchange orders will present with a solid dot in the badge's top-right corner
|
||||
- When multiple orders reside at the same level, a solid triangle will be present in badge's bottom-right corner; all other badge decorations will not be visible in this state
|
||||
- Hedge orders will present with a vertical line inside the badge's left border
|
||||
- Trigger orders, once triggered, will no longer be shown in the ladder
|
||||
- Session High and Low visual indicators have been added to the Price column
|
||||
- Session range is now indicated in the Price column
|
||||
- A button reflecting position has been added above the quantity field
|
||||
- This button reflects the position for the currently selected account
|
||||
- Pressing the button will copy the position size to the quantity field
|
||||
- Order quantity button size values are now customizable
|
||||
- Default size preset values can now be changed, as well as added or removed
|
||||
- This can be configured per ladder instance (i.e. per security) by making use of the Custom tab
|
||||
- Synthetic ladders that were saved as part of a workspace are now restored on startup
|
||||
|
||||
### Synthetics
|
||||
- Hedge Ratio parameter has been renamed to Lean Qty Factor
|
||||
- Spreader now includes a Hidden mode parameter
|
||||
- A quoting leg can be hidden, ie an order will not sent out until the market crosses the computed quoting price. When that happens, the quote is sent out as an IOC order.
|
||||
|
||||
### Algos
|
||||
- Trigger orders now can contain a Trigger Exit parameter based on time or price; once the exit condition has been met the Trigger order will cancel
|
||||
- IOC/FAK order type added
|
||||
- Cover order type added
|
||||
- Cancelling a synthetic order no longer cancels any working hedge orders
|
||||
|
||||
### General
|
||||
- A manual page has been added to the launcher Help menu
|
||||
- Various help buttons and tooltips were added throughout the application
|
||||
- Various cash per point values updated
|
||||
- Time-based grid filters now have tick up/down buttons
|
||||
- Numerous performance improvements and stability enhancements
|
||||
- Automated position and orderbook maintenance during daily recycle
|
||||
- Individual fills be compacted into start of day position records
|
||||
- Orders from the day before the recycle will be deleted
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
1.4.2
|
||||
- Be sure the system has a bts user before deployment. bts is the superuser, he has all permissions and can see all market data
|
||||
- After deployment re-permission all existing users for trading, riskadmin, and/or md admin permissions as appropriate
|
||||
- Also note which users can add other users and accounts and permission them appropriately (Can Add?)
|
||||
- Permission users for appropriate market data entitlements; this has monetary consequences so ask Kevin if you aren't sure.
|
||||
- MIC reporting needs to be added to audit Jenkins job (https://bluetradesys.atlassian.net/browse/SPARK-1900)
|
||||
|
||||
1.3.5
|
||||
- Change in market data wire format. Old shell will get only direct market data instead of merged which may not match what the servers is driving from.
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
1.3.5
|
||||
- Speed Enhanments
|
||||
- Further speed enhancements
|
||||
- Net Change pricing
|
||||
- The ability to price synthetics based off of net change
|
||||
- Separation of implied and direct market data
|
||||
- Viewing within a market view will now show 3 columns
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
- Ability to configure the market data size in a ladder as the following
|
||||
- Direct
|
||||
- Implied
|
||||
- Merged
|
||||
- Synthetic creation option to choose Merged or Direct
|
||||
- The ability to choose Merged or Direct per leg when creating a synthetic
|
||||
- Volume at price
|
||||
- Within a ladder you can now see a volume at price graph
|
||||
- This is configurable from a ladder's settings as well
|
||||
- - Volume at price
|
||||
- Within a ladder you can now see a volume at price graph
|
||||
- This is configurable from a ladder's settings as well
|
||||
- Customized settings in all windows
|
||||
- All windows now contain a settings parge where grid settings (font color, size, etc) are applied
|
||||
- These settings can be saved on a per-window basis or as a Default for newly-launched windows
|
||||
|
||||
1.3.4
|
||||
- Significantly reduced client memory use
|
||||
- Improved OrderBook performance
|
||||
- Improved workspace load time
|
||||
|
||||
1.3.3
|
||||
- Reduced server memory usage
|
||||
|
||||
1.3.2
|
||||
- Improved OrderBook performance
|
||||
- Fix some memory leaks when switching workspaces
|
||||
|
||||
1.3.1
|
||||
- Improved server side logging
|
||||
- Fixed leaking of market data subscriptions on market view form closing
|
||||
|
||||
1.3.0
|
||||
- Significantly improved order execution latency
|
||||
- Added latency monitoring tools
|
||||
- Provided core binding controls to system admins so as to reduce latency jitter
|
||||
|
||||
1.1.22
|
||||
- Orderbook: Orders in Error status can now be recreated
|
||||
- Orderbook: Orders containing child orders will now be collapsed by default.
|
||||
- Orderbook: Expand / Collapse All button added to the right-click menu and window action pane, denoted by ▲ icon
|
||||
- Orderbook: Symbol column added to allow for filtering based on group symbol
|
||||
- Time filters now have a quick Set Last Hour hyperlink which will set the Period Begin filter to (Now - 1 hour)
|
||||
|
||||
1.1.20
|
||||
- Update CME market data handler for price precision extension.
|
||||
https://www.cmegroup.com/confluence/display/EPICSANDBOX/Price+Precision+Extension
|
||||
|
||||
1.1.8
|
||||
- Enabled wild cards in risk management UI
|
||||
- Fixed spreader order updates
|
||||
|
||||
1.1.7
|
||||
- Fixed spread leg price fill
|
||||
- Fixed HO cash per point value
|
||||
|
||||
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
|
||||
+57
-35
@@ -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}") = "libspark", "build\src\lib\libspark.vcxproj", "{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{3335AAF0-489D-359D-8305-9B7A44E2C760}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
|
||||
EndProject
|
||||
@@ -12,15 +12,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sparkcmd", "src\client\spar
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{2256015B-E7D5-3614-8F39-814CA68A4444}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C} = {69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760} = {3335AAF0-489D-359D-8305-9B7A44E2C760}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -57,22 +59,22 @@ Global
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|x64.Build.0 = Debug|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|x64.ActiveCfg = Release|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|x64.Build.0 = Release|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.Release|x86.ActiveCfg = Release|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{69D73CF1-9D7A-38CA-9817-0D0C3E3BCB3C}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x64.Build.0 = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x64.ActiveCfg = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x64.Build.0 = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x86.ActiveCfg = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
|
||||
@@ -137,22 +139,22 @@ Global
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|x64.Build.0 = Debug|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|x64.ActiveCfg = Release|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|x64.Build.0 = Release|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.Release|x86.ActiveCfg = Release|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{33E7E6C5-04E3-36A8-A751-639BE5E72B9B}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x64.Build.0 = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x64.ActiveCfg = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x64.Build.0 = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x86.ActiveCfg = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
|
||||
@@ -197,6 +199,26 @@ Global
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x64.Build.0 = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x64.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
<AvailableItemName Include="Proto3">
|
||||
<Targets>CompileProto3</Targets>
|
||||
</AvailableItemName>
|
||||
<AvailableItemName Include="Proto3Excel">
|
||||
<Targets>CompileProto3Excel</Targets>
|
||||
</AvailableItemName>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="ProtoBuf">
|
||||
@@ -48,9 +51,14 @@
|
||||
<OutCS>%(OutBase)%(FileName).cs</OutCS>
|
||||
<OutCSClient>%(OutBase)%(FileName).client.pb.cs</OutCSClient>
|
||||
<OutCSServer>%(OutBase)%(FileName).server.pb.cs</OutCSServer>
|
||||
<OutCSImmutable>%(OutBase)%(FileName).immutable.pb.cs</OutCSImmutable>
|
||||
<OutCSImmutable>%(OutBase)%(FileName).immutable.pb.cs</OutCSImmutable>
|
||||
<CompileOptions></CompileOptions>
|
||||
</Proto3>
|
||||
<Proto3Excel>
|
||||
<OutBase>$(IntermediateOutputPath)\gen\%(relativedir)</OutBase>
|
||||
<OutCSExcel>%(OutBase)%(FileName).excel.pb.cs</OutCSExcel>
|
||||
<CompileOptions></CompileOptions>
|
||||
</Proto3Excel>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
</Project>
|
||||
@@ -57,12 +57,38 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileProto3Excel" AfterTargets="GetAssemblyVersion"
|
||||
Condition="'@(Proto3Excel)'!=''"
|
||||
Inputs="$(protoc);
|
||||
%(Proto3Excel.Identity);$(ProtocPluginBase)\plugin.py;
|
||||
$(ProtocPluginBase)\templates\excel\excel.pb.cs;"
|
||||
Outputs="%(Proto3Excel.OutCSExcel)">
|
||||
<MakeDir Directories="%(Proto3Excel.OutBase)" />
|
||||
|
||||
<Message Importance="High" Text="Generating excel service %(Proto3Excel.FileName)" />
|
||||
<Exec Command="
|
||||
set PATH=$(spark_build_python_path);%path% &
|
||||
set PYTHONPATH=$(Externals)\python &
|
||||
$(protoc) ^
|
||||
%(Proto3Excel.FullPath) ^
|
||||
--error_format=msvs ^
|
||||
--proto_path=$(protoinc);$(SourceRoot) ^
|
||||
--plugin="protoc-gen-service=$(ProtocPluginBase)\plugin.cmd" ^
|
||||
--service_out=lang=excel,strip_out_path=1:%(Proto3Excel.OutBase) ^
|
||||
"
|
||||
WorkingDirectory="%(relativedir)" />
|
||||
<ItemGroup>
|
||||
<Compile Include="%(Proto3Excel.OutCSExcel)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="AfterClean" AfterTargets="Clean">
|
||||
<Delete Files="%(Proto3.OutCS)" />
|
||||
<Delete Files="%(Proto3.OutCSClient)" />
|
||||
<Delete Files="%(Proto3.OutCSServer)" />
|
||||
<Delete Files="%(Proto3.OutCSImmutable)" />
|
||||
<Delete Files="$(GenDir)\proto2\*" />
|
||||
<Delete Files="%(Proto3Excel.OutCSExcel)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Test" DependsOnTargets="Build">
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace mbts.spark {
|
||||
IEnumerable<BTSAccountImmutable> active_accounts =
|
||||
from admin_data in update.all
|
||||
from bts_account in admin_data.Accounts
|
||||
where bts_account.IsPresent
|
||||
where bts_account.IsPresent && _wrapper_subject.Value.can_view_account(bts_account.Name)
|
||||
join account_id in (
|
||||
from bts_user in active_users
|
||||
from account_id in bts_user.UserData.AccountPermissions.AccountPermissions.Keys
|
||||
|
||||
@@ -16,26 +16,26 @@ namespace mbts.spark
|
||||
{
|
||||
_admin_data = admin_data;
|
||||
_user_name = user_name ?? string.Empty;
|
||||
_allowed_accounts = search_admin(u => u.Name == _user_name).Select(x => x.Item1).ToList();
|
||||
foreach(var val in _allowed_accounts)
|
||||
_allowed_accounts = search_admin(u => u.Name == _user_name).Where(x =>
|
||||
{
|
||||
return x.Item2.HasPermissions || x.Item3.HasPermissions;
|
||||
}).Select(xx => xx.Item1).ToList();
|
||||
foreach (var val in _allowed_accounts)
|
||||
_allowed_account_dict[val.Name] = val.Name;
|
||||
}
|
||||
public IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> get_trading_permissions(UInt32 user_id)
|
||||
{
|
||||
return search_admin(u => u.Id == user_id);
|
||||
}
|
||||
public IEnumerable<BTSAccountImmutable> viewable_accounts()
|
||||
{
|
||||
return _allowed_accounts;
|
||||
}
|
||||
public bool can_view_account(string account)
|
||||
{
|
||||
return _allowed_account_dict.ContainsKey(account) || _user_name.ToLower() == "bts";
|
||||
return _allowed_account_dict.ContainsKey(account) || _user_name.ToLower().Equals("bts");
|
||||
}
|
||||
|
||||
IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> search_admin(Func<BTSUserImmutable, bool> predicate)
|
||||
IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnit, AdminPermissionUnit>>
|
||||
search_admin(Func<BTSUserImmutable, bool> predicate)
|
||||
{
|
||||
var rv = new List<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>>();
|
||||
var rv = new List<Tuple<BTSAccountImmutable, TradingPermissionUnit, AdminPermissionUnit>>();
|
||||
if (_admin_data.HasValue)
|
||||
{
|
||||
var users = _admin_data.Value.Users.Where(predicate);
|
||||
@@ -51,9 +51,7 @@ namespace mbts.spark
|
||||
if (accounts != null && accounts.Count() == 1)
|
||||
{
|
||||
var account = accounts.ElementAt(0);
|
||||
var tp = kv.Value.TradingPermission;
|
||||
var rv_val = new TradingPermissionUnitImmutable(tp);
|
||||
rv.Add(Tuple.Create(account, rv_val));
|
||||
rv.Add(Tuple.Create(account, kv.Value.TradingPermission, kv.Value.AdminPermission));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ namespace bts {
|
||||
[Export, Export(typeof(IShellDaemon))]
|
||||
class DeleteSyntheticDaemon : IShellDaemon, IDisposable {
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model) {
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model, ErrorReporter error_reporter) {
|
||||
_orders_model = orders_model;
|
||||
_error_reporter = error_reporter;
|
||||
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
|
||||
.OnDispatcher()
|
||||
.Subscribe(_delete_synthetic);
|
||||
@@ -28,6 +30,7 @@ namespace bts {
|
||||
async void _delete_synthetic(uint id) {
|
||||
var (orders, orders_success) = await _orders_model.on_orders.FirstAsyncTask(SparkUtils.DefaultTimeOut);
|
||||
if (!orders_success) {
|
||||
_error_reporter.report_error($"Unable to delete synthetic security ({id}): unable to determine whether or not security ({id}) has working orders (deletion is not permitted while there are working orders).");
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because the orders lookup timed out (must confirm no working orders).");
|
||||
} else if (orders.state.Values.Any(o => o.Def.SecurityKey == id && o.is_non_terminal())) {
|
||||
Log.Ui.warn($"Ignoring request to delete synthetic security ({id}) because non-terminal-orders were detected.");
|
||||
|
||||
@@ -13,11 +13,13 @@ using System.Linq;
|
||||
using SecurityKey = System.UInt32;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using bts.api.user;
|
||||
|
||||
namespace bts {
|
||||
[Export(typeof(IInitializable))]
|
||||
class MDSource : IObserver<List<SecurityAndHandle>>, IInitializable, IDisposable {
|
||||
readonly Lazy<IBus> _bus;
|
||||
readonly Lazy<AuthToken> _token;
|
||||
readonly Lazy<SyntheticSecurityService> _ss;
|
||||
readonly MDConsumer _consumer = new MDConsumer();
|
||||
readonly Co2RelayControl _relay;
|
||||
@@ -27,21 +29,27 @@ namespace bts {
|
||||
List<IDisposable> _dispose = new List<IDisposable>();
|
||||
|
||||
[ImportingConstructor]
|
||||
public MDSource(Lazy<IBus> bus, Lazy<SyntheticSecurityService> ss) {
|
||||
public MDSource(
|
||||
Lazy<IBus> bus,
|
||||
Lazy<AuthToken> token,
|
||||
Lazy<SyntheticSecurityService> ss) {
|
||||
_bus = bus;
|
||||
_token = token;
|
||||
_ss = ss;
|
||||
_relay = new Co2RelayControl(_consumer);
|
||||
_relay.preload_securities((co2.MDRequest.Types.Flags)0xff).ignore();
|
||||
_dispose.Add(_consumer.on_security.Subscribe(this));
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
_relay.set_token(_token.Value);
|
||||
_dispose.Add(
|
||||
_ss.Value.subscribei_metadata()
|
||||
.Select(upd => upd.all.ToDictionary(i => i.SecurityKey, i => i.Strategy))
|
||||
.Subscribe(t => {
|
||||
var deleted_synthetic_keys = new HashSet<uint>(t.Where(kv => kv.Value.Deleted).Select(kv => kv.Key));
|
||||
// add back 'undeleted' synthetics
|
||||
foreach (var kvp in _deleted_synthetics) {
|
||||
foreach (var kvp in _deleted_synthetics.ToList()) {
|
||||
if (!deleted_synthetic_keys.Contains(kvp.Key)) {
|
||||
_exchange_sec_map.TryAdd(kvp.Key, kvp.Value);
|
||||
_deleted_synthetics.Remove(kvp.Key);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using bts.api.core;
|
||||
using bts.metrics;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -18,6 +20,7 @@ namespace bts.perf {
|
||||
readonly string _version;
|
||||
readonly string _username;
|
||||
IDisposable _subs;
|
||||
Ping _pinger = new Ping();
|
||||
|
||||
[ImportingConstructor]
|
||||
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
|
||||
@@ -25,7 +28,12 @@ namespace bts.perf {
|
||||
_version = Configuration.get<bts.config.System>().version;
|
||||
_username = user;
|
||||
_hostname = Environment.MachineName;
|
||||
_subs = _service.subscribei_clock().Subscribe(on_tick);
|
||||
|
||||
_forever_ping_host(_pinger);
|
||||
_subs = Disposable.Create(
|
||||
_pinger,
|
||||
_service.subscribei_clock().Subscribe(on_tick)
|
||||
);
|
||||
}
|
||||
|
||||
void on_tick(Update<TickImmutable, int> update) {
|
||||
@@ -40,8 +48,60 @@ namespace bts.perf {
|
||||
}
|
||||
}
|
||||
|
||||
async void _forever_ping_host(Ping pinger) {
|
||||
var config = Configuration.get<bts.config.ShellPingOptions>();
|
||||
string server_ip = Configuration.get<bts.config.System>().config_ip;
|
||||
int delay = config.workloop_milliseconds_interval;
|
||||
int _current_missed_ping_count = 0;
|
||||
|
||||
if (config.shell_ping_is_enabled && pinger != null) {
|
||||
while (true) {
|
||||
try {
|
||||
await Task.Delay(delay);
|
||||
var response = await pinger.SendPingAsync(server_ip, config.timeout_milliseconds_threshold);
|
||||
if (response.Status == IPStatus.Success) {
|
||||
_current_missed_ping_count = 0;
|
||||
if (response.RoundtripTime > config.logging_milliseconds_threshold) {
|
||||
Log.Ui.warn($"Ping response not received in a timely manner:\n" +
|
||||
$"\tAddress:\t{response.Address}\n" +
|
||||
$"\tRoundTrip:\t{response.RoundtripTime} ms");
|
||||
}
|
||||
// potentially reset delay to initial work period (no-op otherwise)
|
||||
delay = config.workloop_milliseconds_interval;
|
||||
} else {
|
||||
_current_missed_ping_count++;
|
||||
delay = _get_exp_delay(_current_missed_ping_count, config.workloop_milliseconds_interval, config.maximum_backoff_milliseconds);
|
||||
Log.Ui.warn($"Ping was not successful. Ping response: {response.Status}. Missed ping count: {_current_missed_ping_count}. Backing off for {delay} milliseconds.");
|
||||
}
|
||||
} catch (PingException e) {
|
||||
_current_missed_ping_count++;
|
||||
delay = _get_exp_delay(_current_missed_ping_count, config.workloop_milliseconds_interval, config.maximum_backoff_milliseconds);
|
||||
Log.Ui.error($"Ping exception. {e}\nBacking off for {delay} milliseconds.");
|
||||
}
|
||||
} // end while
|
||||
}
|
||||
|
||||
// since we're returning milliseconds here up to a 1 minute delay
|
||||
// we don't need to go crazy with the power, else we will get
|
||||
// an ArgumentOutOfRangeExecption.
|
||||
// Int.MaxValue = 2^31, so we need to be below that
|
||||
// Also, even if we're under the 2^31 int.maxvalue, we don't want to be multed over it
|
||||
// so that is why we have checked() here.
|
||||
int _get_exp_delay(int retry_num, int min_interval, int max_delay) {
|
||||
// add some jitter up to half the max delay so we don't
|
||||
// have a ping spike from all clients at once
|
||||
var jitter_amount = new Random().Next(max_delay / 2);
|
||||
int power_factor = (int)Math.Pow(2, Math.Min(retry_num, 32) - 1);
|
||||
try {
|
||||
return Math.Min(checked(min_interval * power_factor + jitter_amount), max_delay + jitter_amount);
|
||||
} catch (OverflowException) {
|
||||
return max_delay + jitter_amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
throw new NotImplementedException();
|
||||
Disposable.dispose(ref _subs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,29 +16,46 @@
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<c:DIYGrid
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
|
||||
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:MarketViewRowView usp:GridAnimation.RowAnimation="{Binding animations}"/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:MarketViewRowView usp:GridAnimation.RowAnimation="{Binding animations}"/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Securities:"/>
|
||||
<Run Text="{Binding ElementName=_grid, Path=Rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows_view.SourceCollection.Count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -58,14 +58,14 @@ namespace mbts.spark {
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
_commands = new List<SparkCommand> {
|
||||
SparkCommands.ShowLadders.complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(_vm.current_security_md, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(_vm.selected_securities_observable(), _ => true,
|
||||
secs => new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
|
||||
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
|
||||
})
|
||||
),
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
|
||||
@@ -167,9 +167,6 @@ namespace mbts.spark {
|
||||
public AggressorSide aggressor_side { get; }
|
||||
public ICommand show_ladder { get; }
|
||||
|
||||
ObservableCollection<RowAnimation> DIYGrid.IRowVM.row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
IReadOnlyList<DIYGrid.IGridCellStore> DIYGrid.IRowVM.cell_stores => new DIYGrid.IGridCellStore[0];
|
||||
|
||||
public string this[TimeAndSalesView.Columns col] {
|
||||
get {
|
||||
switch (col) {
|
||||
|
||||
@@ -18,10 +18,18 @@
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<c:DIYGrid Columns="{Binding columns}"
|
||||
Rows="{Binding rows_view}" CurrentRow="{Binding current_row}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
Rows="{Binding rows_view}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
@@ -39,6 +47,11 @@
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock Text=" "/>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -62,8 +62,8 @@ namespace mbts.spark {
|
||||
var current_smd_source = connectable_builder.create(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = connectable_builder.create(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowOrderTicketFromSecurity.complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
|
||||
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
UIntTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
|
||||
>
|
||||
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
|
||||
<DataTemplate>
|
||||
@@ -122,6 +123,11 @@
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.LegMarketDataCalculation}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.MarketDataCalculationTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.ConditionalPriceTypeTemplate>
|
||||
<DataTemplate>
|
||||
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:ConditionalQuoting.Types.PriceType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
|
||||
</DataTemplate>
|
||||
</usp:DataGridParameterCellTemplateConverter.ConditionalPriceTypeTemplate>
|
||||
<usp:DataGridParameterCellTemplateConverter.QuantityMeasureTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding enabled}">
|
||||
|
||||
@@ -304,6 +304,7 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate QuantityMeasureTemplate { get; set; }
|
||||
public DataTemplate MarketDataTypeTemplate { get; set; }
|
||||
public DataTemplate MarketDataCalculationTemplate { get; set; }
|
||||
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
Type t = (Type)value;
|
||||
@@ -314,6 +315,7 @@ namespace mbts.ui.spark {
|
||||
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
|
||||
else if (t == typeof(LegSpreaderParams.Types.LegMarketDataCalculation)) return MarketDataCalculationTemplate;
|
||||
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.ui;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
@@ -43,8 +44,23 @@ namespace bts.shell {
|
||||
name + "(1)";
|
||||
}
|
||||
|
||||
async void _show_synthetic_create(SecurityCriteria security_criteria) =>
|
||||
_show_synthetic_create((await security_criteria.securities(_bus)).ToList());
|
||||
async void _show_synthetic_create(SecurityCriteria security_criteria) {
|
||||
var found_securities = await security_criteria.securities(_bus);
|
||||
var respect_clause_security_ordering_is_possible = security_criteria.clauses.Any(clause => clause.security_keys != null && clause.security_keys.Count > 0);
|
||||
if (!respect_clause_security_ordering_is_possible) {
|
||||
_show_synthetic_create(found_securities.Values.ToList());
|
||||
} else {
|
||||
List<SecurityImmutable> ordered_security_list = new List<SecurityImmutable>();
|
||||
foreach (var clause in security_criteria.clauses) {
|
||||
foreach (var sec_key in clause.security_keys) {
|
||||
if (found_securities.TryGetValue(sec_key, out SecurityImmutable found_result)) {
|
||||
ordered_security_list.Add(found_result);
|
||||
}
|
||||
}
|
||||
_show_synthetic_create(ordered_security_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _show_synthetic_create(IReadOnlyCollection<SecurityImmutable> securities) {
|
||||
if (securities.Count == 1) {
|
||||
|
||||
@@ -103,6 +103,11 @@ namespace bts.shell {
|
||||
OutsideTicks = 3,
|
||||
Priority = RulePriority
|
||||
};
|
||||
public static ConditionalQuoting ConditionalQuoting => new ConditionalQuoting
|
||||
{
|
||||
NTicks = 2,
|
||||
PriceType = ConditionalQuoting.Types.PriceType.Leg
|
||||
};
|
||||
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
|
||||
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
|
||||
#endregion
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
@@ -39,6 +44,16 @@
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Synthetics:"/>
|
||||
<Run Text="{Binding ElementName=_grid, Path=Rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows_view.SourceCollection.Count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -55,7 +55,7 @@ namespace mbts.spark {
|
||||
current_row_connectable_builder.create(observe_row_strategy),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder.complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
|
||||
filters = new OrderBookWindowFactory.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class EnableHedgeModel : RowModelBase<bool> {
|
||||
public override string display_name { get; } = "Enable Hedging";
|
||||
public override string help_text { get; } = "Send a hedge for this leg?";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.EnableHedging;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
|
||||
leg_tup.leg_params.EnableHedging = value;
|
||||
|
||||
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
|
||||
!leg_tups.Any(lt => lt.leg_params.EnableHedging);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using mbts.ui.spark;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class MinLeanQtyModel : RowModelBase<System.UInt32> {
|
||||
public override string display_name { get; } = "Min Lean Qty";
|
||||
public override string help_text { get; } = "Minimum Quantity required at this leg to quote";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override System.UInt32 get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.MinLeanQty;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, System.UInt32 value) =>
|
||||
leg_tup.leg_params.QuoteParams.MinLeanQty = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using bts.shell;
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class ConditionalQuoteEnabledModel : RowModelBase<bool> {
|
||||
readonly ConditionalQuotingTicksModel _ticks;
|
||||
readonly ConditionalQuotingPriceTypeModel _price_type;
|
||||
|
||||
public ConditionalQuoteEnabledModel(ConditionalQuotingTicksModel ticks, ConditionalQuotingPriceTypeModel price_type)
|
||||
{
|
||||
_ticks = ticks;
|
||||
_price_type = price_type;
|
||||
}
|
||||
|
||||
public override string display_name { get; } = string.Empty;
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.Conditional };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting != null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.QuoteParams.ConditionalQuoting == null) {
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting = SyntheticDataHelper.Defaults.ConditionalQuoting;
|
||||
_ticks.apply_value(leg_tup);
|
||||
_price_type.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting = null;
|
||||
}
|
||||
_ticks.set_enabled(leg_tup, value);
|
||||
_price_type.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
using mbts.ui.spark;
|
||||
using bts.spark;
|
||||
using PriceType = bts.spark.ConditionalQuoting.Types.PriceType;
|
||||
using mbts.collections.extensions;
|
||||
using System.Collections.Generic;
|
||||
using bts.shell;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
partial class SyntheticEditorVM
|
||||
{
|
||||
class ConditionalQuotingPriceTypeModel : RowModelBase<PriceType>
|
||||
{
|
||||
IDictionary<uint, PriceType> _value_store = new Dictionary<uint, PriceType>();
|
||||
|
||||
public override string display_name { get; } = "Price Type";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.Conditional };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup).PriceType = _value_from_store(leg_tup);
|
||||
|
||||
public override PriceType get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) is ConditionalQuoting cond ?
|
||||
cond.PriceType :
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, PriceType value)
|
||||
{
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
if (_get_value(leg_tup) is ConditionalQuoting cond)
|
||||
cond.PriceType = value;
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) => _get_value(leg_tup) != null; //TAHA_SPREADER
|
||||
|
||||
ConditionalQuoting _get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting;
|
||||
|
||||
PriceType _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ConditionalQuoting.PriceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.collections.extensions;
|
||||
using mbts.ui.spark;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class ConditionalQuotingTicksModel : RowModelBase<int> {
|
||||
IDictionary<uint, int> _value_store = new Dictionary<uint, int>();
|
||||
|
||||
public override string display_name { get; } = "Ticks";
|
||||
public override string help_text { get; } = string.Empty;
|
||||
public override string man_page { get; } = string.Empty;
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.Conditional };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup).NTicks = _value_from_store(leg_tup);
|
||||
|
||||
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) is ConditionalQuoting cond ?
|
||||
cond.NTicks :
|
||||
_value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
_value_store[leg_tup.SecurityKey] = value;
|
||||
if (_get_value(leg_tup) is ConditionalQuoting cond)
|
||||
cond.NTicks = value;
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_value(leg_tup) != null;
|
||||
|
||||
ConditionalQuoting _get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.QuoteParams.ConditionalQuoting;
|
||||
|
||||
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ConditionalQuoting.NTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,13 @@ namespace mbts.spark {
|
||||
"Reduces re-quoting updates by defining a region on either side " +
|
||||
"of the working quoting order, inside which quote updates will not be sent.");
|
||||
|
||||
public static ParameterSection Conditional =>
|
||||
new ParameterSection("Conditional", true,
|
||||
"Reduces re-quoting updates by only quoting if N ticks away from the opposite " +
|
||||
"inside market. Quotes are cancelled when it gets further away. 'Price Type' " +
|
||||
"dictates if this logic applies to the quoting market or to the synthetic spread " +
|
||||
"market");
|
||||
|
||||
public static ParameterSection InsideMarketThrottleThresholds =>
|
||||
new ParameterSection("Inside Market Throttle Thresholds", true,
|
||||
"Reduces re-quoting for quotes that are moving toward the inside market. " +
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.spark;
|
||||
@@ -110,7 +109,7 @@ namespace mbts.spark {
|
||||
_validate_leg_count();
|
||||
};
|
||||
|
||||
async Task commit_async(){
|
||||
async Task commit_async() {
|
||||
RequestUpdate?.Invoke();
|
||||
bool valid = true;
|
||||
if (string.IsNullOrWhiteSpace(name)) {
|
||||
@@ -125,8 +124,7 @@ namespace mbts.spark {
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
_synthetic.Synthetic.Legs.Clear();
|
||||
_synthetic.Synthetic.Legs.AddRange(leg_tuples.Select(l => l.leg).OrderBy(l => l.Security, SecurityHelper.Comparer.Instance));
|
||||
_synthetic.Synthetic.Legs.SetItems(leg_tuples.Select(l => l.leg));
|
||||
_synthetic.Symbol = _deduce_symbol(_synthetic);
|
||||
double tick_frac = _tick_factors.tick_unit / _tick_factors.tick_base;
|
||||
_synthetic.ContractSpec.LowTick = tick_frac;
|
||||
@@ -180,6 +178,9 @@ namespace mbts.spark {
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
|
||||
|
||||
var conditional_quoting_ticks = new ConditionalQuotingTicksModel();
|
||||
var conditional_quoting_price_type = new ConditionalQuotingPriceTypeModel();
|
||||
|
||||
var quote_inside_market_throttle_ticks = new QuoteInsideMarketThrottleTicksModel();
|
||||
var quote_inside_market_throttle_min_move = new QuoteInsideMarketThrottleMinMoveModel();
|
||||
|
||||
@@ -201,13 +202,18 @@ namespace mbts.spark {
|
||||
_quantity_model,
|
||||
_price_mult_model,
|
||||
new IsQuoteModel(),
|
||||
//new EnableHedgeModel(), //TAHA_SPREADER
|
||||
new HedgeRatioModel(),
|
||||
//new MinLeanQtyModel(), //TAHA_SPREADER
|
||||
new QuoteHiddenModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new MarketDataCalculationModel(),
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
new ConditionalQuoteEnabledModel(conditional_quoting_ticks, conditional_quoting_price_type),
|
||||
conditional_quoting_ticks,
|
||||
conditional_quoting_price_type,
|
||||
//new QuoteInsideMarketThrottleEnabledModel(quote_inside_market_throttle_ticks, quote_inside_market_throttle_min_move),
|
||||
//quote_inside_market_throttle_ticks,
|
||||
//quote_inside_market_throttle_min_move,
|
||||
@@ -296,7 +302,6 @@ namespace mbts.spark {
|
||||
|
||||
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
|
||||
synthetic.Synthetic.Legs
|
||||
.OrderBy(l => l.Security, SecurityHelper.Comparer.Instance)
|
||||
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
|
||||
|
||||
void _close_new_leg_popup() {
|
||||
@@ -339,4 +344,4 @@ namespace mbts.spark {
|
||||
else clear_error(nameof(leg_count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ namespace bts.shell {
|
||||
Configuration.get<bts.config.System>().config_port);
|
||||
string temp = uristr;
|
||||
if (uristr.StartsWith("bts://m/")) {
|
||||
temp = uristr.Replace("bts://m/", http_base + "doc/manual/");
|
||||
temp = uristr.Replace("bts://m/", http_base + "docs/");
|
||||
}
|
||||
else {
|
||||
temp = uristr.Replace("bts://", http_base);
|
||||
|
||||
@@ -38,10 +38,10 @@ namespace bts.shell {
|
||||
//SparkCommands.ShowAuditLogs,
|
||||
//SparkCommands.ShowDiagnostics,
|
||||
SparkCommands.ShowReportIssue.complete("Created from launcher"),
|
||||
SparkCommands.ShowAboutSpark,
|
||||
// SparkCommands.ShowReleaaseNotes,
|
||||
SparkCommands.ShowActivityLog,
|
||||
SparkCommands.ShowManual,
|
||||
SparkCommands.ShowAboutSpark,
|
||||
};
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
|
||||
@@ -207,9 +207,6 @@ namespace mbts.spark {
|
||||
public string fill_key => _fill.FillKey;
|
||||
public string order_key => _order.Key.ToString();
|
||||
|
||||
ObservableCollection<RowAnimation> IRowVM.row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
IReadOnlyList<IGridCellStore> IRowVM.cell_stores => new IGridCellStore[0];
|
||||
|
||||
public bool Equals(Row other) => other != null && other._fill.Equals(_fill) && other._order.Equals(_order);
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
@@ -19,8 +19,13 @@
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row}"
|
||||
Rows="{Binding rows_view}"
|
||||
@@ -42,6 +47,16 @@
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Fills:"/>
|
||||
<Run Text="{Binding ElementName=_grid, Path=Rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows_view.SourceCollection.Count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -1,168 +0,0 @@
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM {
|
||||
public interface IOrderBookCell : DIYGrid.IGridCell, IComparable { }
|
||||
|
||||
public interface IOrderBookCell<T> : IOrderBookCell {
|
||||
T value { get; }
|
||||
IOrderBookCellStore<T> store { get; }
|
||||
}
|
||||
|
||||
public interface IOrderBookCellStore : DIYGrid.IGridCellStore {
|
||||
OrderBookRowVM row { get; }
|
||||
new ICVOrderBookColumn column { get; }
|
||||
string prop_name { get; }
|
||||
new IOrderBookCell current { get; }
|
||||
//
|
||||
IOrderBookCellStore select_other(OrderBookRowVM row);
|
||||
}
|
||||
|
||||
public interface IOrderBookCellStore<T> : IOrderBookCellStore, IComparer<T>, IEqualityComparer<T> {
|
||||
new IOrderBookCell<T> current { get; }
|
||||
new IOrderBookCellStore<T> select_other(OrderBookRowVM row);
|
||||
//
|
||||
T get_value(OrderBookRowVM row);
|
||||
string format(T value);
|
||||
}
|
||||
|
||||
public static class CellFactory {
|
||||
public static IOrderBookCellStore<T> create<T>(OrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<OrderBookRowVM, T> get_value, Func<OrderBookRowVM, IOrderBookCellStore<T>> select_other, Func<T, string> format = null) =>
|
||||
new OrderBookCellStore<T>(row, column, prop_name, get_value, select_other, format, new Comparison<T>(Comparer<T>.Default.Compare), EqualityComparer<T>.Default);
|
||||
|
||||
public static IOrderBookCellStore<T> create<T>(OrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<OrderBookRowVM, T> get_value, Func<OrderBookRowVM, IOrderBookCellStore<T>> select_other, Comparison<T> comparison, Func<T, string> format = null) =>
|
||||
new OrderBookCellStore<T>(row, column, prop_name, get_value, select_other, format, comparison, EqualityComparer<T>.Default);
|
||||
|
||||
public static IOrderBookCellStore<T?> create_nullable<T>(OrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<OrderBookRowVM, T?> get_value, Func<OrderBookRowVM, IOrderBookCellStore<T?>> select_other, Func<T?, string> format = null)
|
||||
where T : struct => new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison<T>(Comparer<T>.Default.Compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
public static IOrderBookCellStore<T?> create_nullable<T>(OrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<OrderBookRowVM, T?> get_value, Func<OrderBookRowVM, IOrderBookCellStore<T?>> select_other, Func<T, T, int> compare, Func<T?, string> format = null)
|
||||
where T : struct =>
|
||||
new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T>, INotifyPropertyChanged {
|
||||
readonly OrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly string _prop_name;
|
||||
readonly Func<OrderBookRowVM, T> _get_value;
|
||||
readonly Func<OrderBookRowVM, IOrderBookCellStore<T>> _select_other;
|
||||
readonly Func<T, string> _format;
|
||||
readonly Comparison<T> _comparison;
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
OrderBookCell<T> _current;
|
||||
|
||||
// Property changed handler to avoid WPF binding leak
|
||||
// https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
|
||||
public event PropertyChangedEventHandler PropertyChanged = (_,__) => { };
|
||||
|
||||
public OrderBookCellStore(
|
||||
OrderBookRowVM row,
|
||||
ICVOrderBookColumn column,
|
||||
string prop_name,
|
||||
Func<OrderBookRowVM, T> get_value,
|
||||
Func<OrderBookRowVM, IOrderBookCellStore<T>> select_other,
|
||||
Func<T, string> format,
|
||||
Comparison<T> comparison,
|
||||
IEqualityComparer<T> eq_comparer) {
|
||||
//
|
||||
_row = row;
|
||||
_column = column;
|
||||
_prop_name = prop_name;
|
||||
_get_value = get_value;
|
||||
_select_other = select_other;
|
||||
_format = format ?? new Func<T, string>(v => v?.ToString() ?? string.Empty);
|
||||
_comparison = comparison;
|
||||
_eq_comparer = eq_comparer;
|
||||
_current = new OrderBookCell<T>(this, _get_value(_row));
|
||||
}
|
||||
|
||||
public OrderBookRowVM row => _row;
|
||||
public ICVOrderBookColumn column => _column;
|
||||
public string prop_name => _prop_name;
|
||||
public IOrderBookCell<T> current => _current;
|
||||
|
||||
IOrderBookCell IOrderBookCellStore.current => _current;
|
||||
DIYGrid.IGridColumn DIYGrid.IGridCellStore.column => _column;
|
||||
DIYGrid.IGridCell DIYGrid.IGridCellStore.current => _current;
|
||||
|
||||
public ObservableCollection<CellAnimation> animations { get; } = new ObservableCollection<CellAnimation>();
|
||||
|
||||
public int Compare(T x, T y) => _comparison(x, y);
|
||||
public bool Equals(T x, T y) => _eq_comparer.Equals(x, y);
|
||||
public string format(T value) => _format(value);
|
||||
public int GetHashCode(T obj) => _eq_comparer.GetHashCode(obj);
|
||||
public T get_value(OrderBookRowVM row) => _get_value(row);
|
||||
public IOrderBookCellStore<T> select_other(OrderBookRowVM row) => _select_other(row);
|
||||
IOrderBookCellStore IOrderBookCellStore.select_other(OrderBookRowVM row) => select_other(row);
|
||||
|
||||
public bool refresh() {
|
||||
T v = _get_value(_row);
|
||||
if (!_eq_comparer.Equals(v, _current.value)) {
|
||||
_current = new OrderBookCell<T>(this, v);
|
||||
_row.RaisePropertyChanged(_prop_name);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public void regenerate_cell() {
|
||||
_current = new OrderBookCell<T>(this, _current.value);
|
||||
_row.RaisePropertyChanged(_prop_name);
|
||||
}
|
||||
|
||||
public void invalidate_text() => regenerate_cell();
|
||||
}
|
||||
|
||||
class OrderBookCell<T> : IOrderBookCell<T> {
|
||||
string _text;
|
||||
|
||||
public OrderBookCell(OrderBookCellStore<T> store, T value) {
|
||||
this.store = store;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public string text => _text ?? (_text = store.format(value));
|
||||
public T value { get; }
|
||||
public IOrderBookCellStore<T> store { get; }
|
||||
|
||||
public int CompareTo(OrderBookCell<T> other) {
|
||||
if (other == null)
|
||||
return 1;
|
||||
var lin = lineage(store.row);
|
||||
var olin = lineage(other.store.row);
|
||||
int c = 0;
|
||||
using (var en = lin.GetEnumerator())
|
||||
using (var oen = olin.GetEnumerator()) {
|
||||
while (en.MoveNext() && oen.MoveNext()) {
|
||||
if (en.Current.order_key.value != oen.Current.order_key.value) {
|
||||
c = store.Compare(
|
||||
store.select_other(en.Current).current.value,
|
||||
store.select_other(oen.Current).current.value);
|
||||
if (c != 0)
|
||||
return c;
|
||||
else {
|
||||
c = en.Current.order_key.value.CompareTo(
|
||||
oen.Current.order_key.value);
|
||||
return store.column.SortDirection.HasValue && store.column.SortDirection.Value == ListSortDirection.Ascending ? c : -c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return store.column.SortDirection.HasValue && store.column.SortDirection.Value == ListSortDirection.Ascending ?
|
||||
lin.Count.CompareTo(olin.Count) :
|
||||
olin.Count.CompareTo(lin.Count);
|
||||
}
|
||||
|
||||
public int CompareTo(object obj) => CompareTo(obj as OrderBookCell<T>);
|
||||
|
||||
public override string ToString() => text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
@@ -7,6 +8,7 @@ using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -19,37 +21,53 @@ using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookRowVM;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
// In OrderBookRowVM, each "cell(property)" has a corresponding TreeComparable property that the column uses for sorting
|
||||
// - each columns' sort_member_path points to the TreeComparable.
|
||||
// The TreeComparable class needs to know which direction it is sorting so it can force child-orders to always be beneath their parents.
|
||||
// - when comparing with their parent, it must return 1 if Ascending, -1 if Descending.
|
||||
// Since the TreeComparables in each row must access their respective column SortDirections in their CompareTo methods, those DP lookups could add significant overhead in large collections.
|
||||
// I considered adding ListSortDirection members directly to OrderBookVM, and letting the TreeComparables refer to that property,
|
||||
// but decided it would be better to let the Columns own it so we wouldn't have to worry about keeping the 2 props in sync.
|
||||
// TODO: Maybe binding to the SortDirection would be less smelly?
|
||||
// - initially, I tried to bind to the SortDirection of each column, unfortunately, the binding target was not updated before the ICollectionView sorting took place.
|
||||
// I decided it was not worth changing the DIYGrid.Sorting function to force the SortDirection binding targets to update immediately, before the ICollectionView sorting.
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn<OrderBookRowVM> {
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
|
||||
string sort_member_path { get; }
|
||||
ListSortDirection? SortDirection { get; }
|
||||
IDataGridColumnFilter<OrderBookRowVM> filter { get; }
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderRecordTuple> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
}
|
||||
|
||||
class OrderBookSortingData : IEquatable<OrderBookSortingData> {
|
||||
public readonly IReadOnlyList<SortingCriteria> sorting_criterion;
|
||||
public readonly IComparer<OrderBookRowVM> comparer;
|
||||
public readonly string desc;
|
||||
|
||||
public OrderBookSortingData(IEnumerable<ICVOrderBookColumn> column_sorting_hierarchy) {
|
||||
var comp_builder = new ComparerBuilder<OrderBookRowVM>();
|
||||
foreach (var col in column_sorting_hierarchy)
|
||||
comp_builder.add(col.SortDirection.Value == ListSortDirection.Ascending ? col.comparer : col.comparer.reverse());
|
||||
if (!column_sorting_hierarchy.Any(col => col.key == Columns.OrderKey)) {
|
||||
// the resultant comparer must to be 100% deterministic, as SortedList (the building block of the SortedTree) uses a BinarySearch to find/remove items and does NOT check for item-equality.
|
||||
comp_builder.add(Comparer<OrderBookRowVM>.Create((l, r) => l.order_key.CompareTo(r.order_key)));
|
||||
}
|
||||
comparer = comp_builder.to_comparer();
|
||||
sorting_criterion = column_sorting_hierarchy.Select(col => new SortingCriteria(col.id, col.SortDirection.GetValueOrDefault() == ListSortDirection.Ascending)).ToArray();
|
||||
desc = string.Join(", ", sorting_criterion);
|
||||
}
|
||||
|
||||
public bool Equals(OrderBookSortingData other) => other != null && EqualityComparer.safe_sequence_equals(sorting_criterion, other.sorting_criterion);
|
||||
public override bool Equals(object obj) => base.Equals(obj);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(sorting_criterion);
|
||||
public override string ToString() => desc;
|
||||
}
|
||||
|
||||
partial class OrderBookVM {
|
||||
public class OrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<FilterStates>, IDisposable {
|
||||
public class OrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<(FilterStates filters, IReadOnlyList<SortingCriteria> sorting)>, IDisposable {
|
||||
readonly List<ICVOrderBookColumn> _active_comparers = new List<ICVOrderBookColumn>();
|
||||
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
Add(security_column = new SecurityColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, SecurityImmutable, SecurityCriteria>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, SecurityImmutable, SecurityCriteria>(
|
||||
order_securities,
|
||||
r => r.security.value.GetValueOrDefault(SecurityDataHelper.Defaults.SecurityImmutable),
|
||||
ort => ort.security_md.HasValue ? ort.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
nameof(OrderBookRowVM.security),
|
||||
@@ -61,172 +79,222 @@ namespace mbts.spark {
|
||||
Columns.Symbol,
|
||||
DefaultColumnHeaders.Symbol,
|
||||
nameof(OrderBookRowVM.symbol),
|
||||
r => r.symbol.store,
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, string>(
|
||||
mbts.co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
|
||||
r => r.symbol.value,
|
||||
value_path(nameof(OrderBookRowVM.symbol))
|
||||
ort => ort.security_md.HasValue ? ort.security_md.Value.security.Symbol : string.Empty,
|
||||
nameof(OrderBookRowVM.symbol)
|
||||
),
|
||||
DataGridFilterKnownTemplates.DiscreteValues));
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
_wrap_comparer(r => r.symbol)));
|
||||
// time_created
|
||||
Add(time_created_column = new TimeCreatedColumn(
|
||||
new TimeFilterVM<OrderBookRowVM>(r => r.time_created.value, value_path(nameof(OrderBookRowVM.time_created)))));
|
||||
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.Time,
|
||||
"Time",
|
||||
nameof(OrderBookRowVM.time_created),
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderRecordTuple>(
|
||||
ort => ort.order.created(),
|
||||
nameof(OrderBookRowVM.time_created)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.time_created)));
|
||||
// last_change
|
||||
Add(last_change_column = new LastChangeTimeColumn(
|
||||
new TimeFilterVM<OrderBookRowVM>(r => r.last_change.value, value_path(nameof(OrderBookRowVM.last_change)))));
|
||||
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.LastChange,
|
||||
"Last Change",
|
||||
nameof(OrderBookRowVM.last_change),
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderRecordTuple>(
|
||||
ort => ort.order.last_change(),
|
||||
nameof(OrderBookRowVM.last_change)),
|
||||
DataGridFilterKnownTemplates.TimeFilter,
|
||||
_wrap_comparer(r => r.last_change)));
|
||||
// user
|
||||
Add(user_column = new SimpleTextColumn<string[]>(
|
||||
Columns.User,
|
||||
DefaultColumnHeaders.User,
|
||||
nameof(OrderBookRowVM.user),
|
||||
r => r.user.store,
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, string>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, string>(
|
||||
model.factory.subscribe_users().Select(d => d.Values),
|
||||
r => r.user.value,
|
||||
value_path(nameof(OrderBookRowVM.user))),
|
||||
DataGridFilterKnownTemplates.DiscreteValues));
|
||||
ort => ort.order.user(),
|
||||
nameof(OrderBookRowVM.user)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
_wrap_comparer(r => r.user)));
|
||||
// account
|
||||
Add(account_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Account,
|
||||
DefaultColumnHeaders.Account,
|
||||
nameof(OrderBookRowVM.account),
|
||||
r => r.account.store,
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, string, string[]>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, string, string[]>(
|
||||
model.factory.subscribe_accounts().Select(d => d.Values),
|
||||
r => r.account.value,
|
||||
ort => ort.order.account(),
|
||||
vals => vals.ToArray(),
|
||||
vals => Observable.Return(vals),
|
||||
value_path(nameof(OrderBookRowVM.account))),
|
||||
DataGridFilterKnownTemplates.DiscreteValues));
|
||||
nameof(OrderBookRowVM.account)),
|
||||
DataGridFilterKnownTemplates.DiscreteValues,
|
||||
_wrap_comparer(r => r.account)));
|
||||
// exchange
|
||||
Add(exchange_column = new SimpleTextColumn<Venue[]>(
|
||||
Columns.Exchange,
|
||||
DefaultColumnHeaders.Exchange,
|
||||
nameof(OrderBookRowVM.exchange),
|
||||
r => r.exchange.store,
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, Venue>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, Venue>(
|
||||
EnumHelper.get_values_except(Venue.Unset).ToList(),
|
||||
r => r.exchange.value,
|
||||
value_path(nameof(OrderBookRowVM.exchange))),
|
||||
DataGridFilterKnownTemplates.EnumValues));
|
||||
ort => ort.security_md.HasValue ? ort.security_md.Value.security.Exchange : Venue.Unset,
|
||||
nameof(OrderBookRowVM.exchange)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.exchange)));
|
||||
// time in force
|
||||
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
|
||||
Columns.TimeInForce,
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
nameof(OrderBookRowVM.time_in_force),
|
||||
r => r.time_in_force.store,
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, TimeInForce>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, TimeInForce>(
|
||||
EnumHelper.get_values<TimeInForce>().ToList(),
|
||||
r => r.time_in_force.value,
|
||||
value_path(nameof(OrderBookRowVM.time_in_force))),
|
||||
DataGridFilterKnownTemplates.EnumValues));
|
||||
ort => ort.order.time_in_force(),
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
// order type
|
||||
Add(order_type_column = new OrderBookOrderTypeColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, OrderType>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, OrderType>(
|
||||
utilities.OrderFactory.all_order_types,
|
||||
r => r.order_type.value,
|
||||
value_path(nameof(OrderBookRowVM.order_type)))));
|
||||
ort => ort.order.order_type(),
|
||||
nameof(OrderBookRowVM.order_type))));
|
||||
// order sub type
|
||||
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
|
||||
Columns.SubType,
|
||||
"Sub Type",
|
||||
nameof(OrderBookRowVM.order_sub_type),
|
||||
r => r.order_sub_type.store,
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, OrderSubType>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
r => r.order_sub_type.value,
|
||||
value_path(nameof(OrderBookRowVM.order_sub_type))),
|
||||
SubTypeFilterTemplateKey));
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, Side>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, Side>(
|
||||
new[] { Side.Buy, Side.Sell },
|
||||
r => r.side.value,
|
||||
value_path(nameof(OrderBookRowVM.side)))));
|
||||
ort=> ort.order.side(),
|
||||
nameof(OrderBookRowVM.side))));
|
||||
// quantity
|
||||
Add(qty_column = new SizeColumn(
|
||||
new UIntMinMax<OrderBookRowVM>(r => r.quantity.value, value_path(nameof(OrderBookRowVM.quantity)))));
|
||||
// price
|
||||
Add(price_column = new PriceColumn(
|
||||
new DoubleMinMax<OrderBookRowVM>(r => r.price.value, value_path(nameof(OrderBookRowVM.price)))));
|
||||
new UIntMinMax<OrderRecordTuple>(ort => ort.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// filled quantity
|
||||
Add(filled_qty_column = new FilledColumn(
|
||||
new UIntMinMax<OrderBookRowVM>(r => r.filled_quantity.value, value_path(nameof(OrderBookRowVM.filled_quantity)))));
|
||||
new UIntMinMax<OrderRecordTuple>(ort => ort.order.filled(), nameof(OrderBookRowVM.filled_quantity))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
nameof(OrderBookRowVM.price),
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderRecordTuple>(
|
||||
ort => ort.order.price(),
|
||||
nameof(OrderBookRowVM.price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.price)));
|
||||
// avg filled price
|
||||
Add(avg_price_column = new AvgFilledColumn(
|
||||
new DoubleMinMax<OrderBookRowVM>(r => r.avg_price.value, value_path(nameof(OrderBookRowVM.avg_price)))));
|
||||
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.AvgFilled,
|
||||
"Avg Price",
|
||||
nameof(OrderBookRowVM.avg_price),
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderRecordTuple>(
|
||||
ort => ort.order.avg_price(),
|
||||
nameof(OrderBookRowVM.avg_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.avg_price)));
|
||||
// last filled price
|
||||
Add(last_price_column = new LastFilledColumn(
|
||||
new DoubleMinMax<OrderBookRowVM>(r => r.last_price.value, value_path(nameof(OrderBookRowVM.last_price)))));
|
||||
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.LastPrice,
|
||||
"Last Filled",
|
||||
nameof(OrderBookRowVM.last_price),
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderRecordTuple>(
|
||||
ort => ort.order.last_price(),
|
||||
nameof(OrderBookRowVM.last_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.last_price)));
|
||||
// status
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
nameof(OrderBookRowVM.status),
|
||||
r => r.status.store,
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderBookRowVM, OrderStatus>(
|
||||
DiscreteValuesFilterVM.Create<OrderRecordTuple, OrderStatus>(
|
||||
EnumHelper.get_values<OrderStatus>().ToList(),
|
||||
r => r.status.value,
|
||||
value_path(nameof(OrderBookRowVM.status))),
|
||||
DataGridFilterKnownTemplates.EnumValues));
|
||||
ort => ort.order.status(),
|
||||
nameof(OrderBookRowVM.status)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.status)));
|
||||
// last message
|
||||
Add(last_message_column = new OrderBookLastMessageColumn(
|
||||
new RegexFilterVM<OrderBookRowVM>(
|
||||
r => r.last_msg.value,
|
||||
value_path(nameof(OrderBookRowVM.last_msg)))));
|
||||
new RegexFilterVM<OrderRecordTuple>(
|
||||
ort => ort.order.last_msg(),
|
||||
nameof(OrderBookRowVM.last_msg))));
|
||||
// security key
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
Columns.SecurityKey,
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
nameof(OrderBookRowVM.security_key),
|
||||
r => r.security_key.store,
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderBookRowVM>(
|
||||
r => r.security_key.text,
|
||||
value_path(nameof(OrderBookRowVM.security_key))),
|
||||
DataGridFilterKnownTemplates.Regex));
|
||||
new RegexFilterVM<OrderRecordTuple>(
|
||||
ort => ort.order.security_key().ToString(),
|
||||
nameof(OrderBookRowVM.security_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.security_key)));
|
||||
// order key
|
||||
Add(order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.OrderKey,
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
nameof(OrderBookRowVM.order_key),
|
||||
r => r.order_key.store,
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderBookRowVM>(
|
||||
r => r.order_key.text,
|
||||
value_path(nameof(OrderBookRowVM.order_key))),
|
||||
DataGridFilterKnownTemplates.Regex));
|
||||
new RegexFilterVM<OrderRecordTuple>(
|
||||
ort => ort.order.Key.ToString(),
|
||||
nameof(OrderBookRowVM.order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order_key)));
|
||||
// parent key (this is for diagnostic purposes, remove this column once OrderBook seems stable again)
|
||||
Add(parent_key_column = new SimpleTextColumn<string>(
|
||||
Columns.ParentKey,
|
||||
"Parent Key",
|
||||
nameof(OrderBookRowVM.parent_order_key),
|
||||
r => r.parent_order_key.store,
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderBookRowVM>(
|
||||
r => r.parent_order_key.text,
|
||||
value_path(nameof(OrderBookRowVM.parent_order_key))),
|
||||
DataGridFilterKnownTemplates.Regex));
|
||||
|
||||
string value_path(string cell_name) =>
|
||||
string.Join(".", cell_name, nameof(OrderBookRowVM.IOrderBookCell<uint>.value)); //uint is an arbitrary 'T', just want to get compile check on 'value' path.
|
||||
new RegexFilterVM<OrderRecordTuple>(
|
||||
ort => ort.order.ParentKey.ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
}
|
||||
|
||||
List<IDataGridColumnFilter<OrderBookRowVM>> _active_filters = new List<IDataGridColumnFilter<OrderBookRowVM>>(0);
|
||||
|
||||
public event Action filter_changed;
|
||||
|
||||
public SecurityColumn security_column { get; }
|
||||
public SimpleTextColumn<string[]> symbol_column { get; }
|
||||
public TimeCreatedColumn time_created_column { get; }
|
||||
public LastChangeTimeColumn last_change_column { get; }
|
||||
public SimpleTextColumn<TimeFilterTuple> time_created_column { get; }
|
||||
public SimpleTextColumn<TimeFilterTuple> last_change_column { get; }
|
||||
public SimpleTextColumn<string[]> user_column { get; }
|
||||
public SimpleTextColumn<string[]> account_column { get; }
|
||||
public SimpleTextColumn<Venue[]> exchange_column { get; }
|
||||
@@ -235,19 +303,17 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
|
||||
public SideColumn side_column { get; }
|
||||
public SizeColumn qty_column { get; }
|
||||
public PriceColumn price_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
|
||||
public FilledColumn filled_qty_column { get; }
|
||||
public LastFilledColumn last_price_column { get; }
|
||||
public AvgFilledColumn avg_price_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> last_price_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> avg_price_column { get; }
|
||||
public SimpleTextColumn<OrderStatus[]> status_column { get; }
|
||||
public OrderBookLastMessageColumn last_message_column { get; }
|
||||
public SimpleTextColumn<string> security_key_column { get; }
|
||||
public SimpleTextColumn<string> order_key_column { get; }
|
||||
public SimpleTextColumn<string> parent_key_column { get; }
|
||||
|
||||
public bool filter(OrderBookRowVM row) => _active_filters.All(f => f.filter(row));
|
||||
|
||||
public FilterStates create() =>
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
|
||||
new FilterStates {
|
||||
order_key_regex = order_key_column.filter.get_state(),
|
||||
time_created = time_created_column.filter.get_state(),
|
||||
@@ -268,13 +334,11 @@ namespace mbts.spark {
|
||||
last_change = last_change_column.filter.get_state(),
|
||||
security_criteria = security_column.filter.get_state(),
|
||||
symbols = symbol_column.filter.get_state()
|
||||
};
|
||||
},
|
||||
get_sorting_data().sorting_criterion);
|
||||
|
||||
public void load_from(FilterStates fs) {
|
||||
if (fs != null) {
|
||||
foreach (var c in this.Cast<ICVOrderBookColumn>())
|
||||
c.filter.filter_changed -= _set_active_filters;
|
||||
//
|
||||
public void load_from((FilterStates filters, IReadOnlyList<SortingCriteria> sorting) state) {
|
||||
if (state.filters is FilterStates fs) {
|
||||
security_column.filter.load_state(fs.security_criteria);
|
||||
symbol_column.filter.load_state(fs.symbols);
|
||||
time_created_column.filter.load_state(fs.time_created);
|
||||
@@ -294,45 +358,128 @@ namespace mbts.spark {
|
||||
status_column.filter.load_state(fs.statuses);
|
||||
last_message_column.filter.load_state(fs.exchange_message_regex);
|
||||
last_change_column.filter.load_state(fs.last_change);
|
||||
//
|
||||
_set_active_filters();
|
||||
foreach (var c in this.Cast<ICVOrderBookColumn>())
|
||||
c.filter.filter_changed += _set_active_filters;
|
||||
}
|
||||
_active_comparers.Clear();
|
||||
foreach (var col in this)
|
||||
col.SortDirection = null;
|
||||
if (state.sorting is IReadOnlyList<SortingCriteria> sorting) {
|
||||
foreach (var sc in sorting) {
|
||||
this.OfType<ICVOrderBookColumn>().ApplyToFirst(col => col.id.iequals(sc.col_name),
|
||||
col => {
|
||||
col.SortDirection = sc.is_ascending ? ListSortDirection.Ascending : ListSortDirection.Descending;
|
||||
_active_comparers.Add(col);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (_active_comparers.Count == 0) {
|
||||
// safeguard against empty sort, fallback to time-created
|
||||
time_created_column.SortDirection = ListSortDirection.Descending;
|
||||
_active_comparers.Add(time_created_column);
|
||||
}
|
||||
}
|
||||
|
||||
public Func<OrderRecordTuple, bool> get_filter() {
|
||||
var active_filters = this.OfType<ICVOrderBookColumn>().Where(c => c.filter.active.GetValueOrDefault()).Select(c => c.filter).ToArray();
|
||||
return active_filters.Length == 0 ? null : new Func<OrderRecordTuple, bool>(r => active_filters.All(f => f.filter(r)));
|
||||
}
|
||||
|
||||
public OrderBookSortingData get_sorting_data() {
|
||||
var desc_parts = new List<string>();
|
||||
var comp_builder = new ComparerBuilder<OrderBookRowVM>();
|
||||
// this should be unneccesary, safe_active_comparers is just an extra guard against unexpected results from future code changes
|
||||
// (get_comparer_data() should NEVER return a null/non-deterministic IComparer, fallback to sorting by Time Created in descending order if necessary)
|
||||
var safe_active_comparers = _active_comparers.Where(c => c.SortDirection.HasValue).ToList();
|
||||
if (safe_active_comparers.Count == 0) {
|
||||
time_created_column.SortDirection = ListSortDirection.Descending;
|
||||
_active_comparers.Clear();
|
||||
_active_comparers.Add(time_created_column);
|
||||
safe_active_comparers = _active_comparers;
|
||||
}
|
||||
return new OrderBookSortingData(safe_active_comparers);
|
||||
}
|
||||
|
||||
public void on_sorting(ICVOrderBookColumn col, bool clear_current_sort) {
|
||||
col.SortDirection = col.SortDirection.HasValue && col.SortDirection.Value == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
|
||||
if (_active_comparers.Contains(col, EqualityComparer.Create<ICVOrderBookColumn, Columns>(r => r.key))) {
|
||||
if (clear_current_sort) _reset_active_comparer(col);
|
||||
} else {
|
||||
if (clear_current_sort) _reset_active_comparer(col);
|
||||
else _active_comparers.Add(col);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
foreach (var c in this.Cast<ICVOrderBookColumn>()) {
|
||||
c.filter.filter_changed -= _set_active_filters;
|
||||
(c.filter as IDisposable)?.Dispose();
|
||||
foreach (var col in this.OfType<ICVOrderBookColumn>())
|
||||
(col.filter as IDisposable)?.Dispose();
|
||||
Clear();
|
||||
}
|
||||
|
||||
/// <see cref="OrderBookColumnCollection"/> ONLY supports sorting/filtering behavior for columns that implement <see cref="ICVOrderBookColumn"/>.
|
||||
/// This is because OrderBook, unlike the other windows (as of 10/30/2018), does not use <see cref="ICollectionView"/> as the sorting/filtering model.
|
||||
protected override void InsertItem(int index, ColumnBase item) {
|
||||
if (item is ICVOrderBookColumn col) {
|
||||
try {
|
||||
_hook(col);
|
||||
base.InsertItem(index, item);
|
||||
} catch {
|
||||
_unhook(col);
|
||||
throw;
|
||||
}
|
||||
} else {
|
||||
Log.Ui.warn($"{nameof(OrderBookColumnCollection)} added a column that is not of type {nameof(ICVOrderBookColumn)}");
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
void _set_active_filters() {
|
||||
_active_filters = this
|
||||
.Cast<ICVOrderBookColumn>()
|
||||
.Select(c => c.filter)
|
||||
.Where(f => f.active.GetValueOrDefault())
|
||||
.ToList();
|
||||
filter_changed?.Invoke();
|
||||
protected override void RemoveItem(int index) {
|
||||
if (Count > index && this[index] is ICVOrderBookColumn col)
|
||||
_unhook(col);
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
protected override void ClearItems() {
|
||||
var ob_columns = this.OfType<ICVOrderBookColumn>().ToList();
|
||||
Log.Ui.info($"{nameof(OrderBookColumnCollection)} is unhooking all columns of type {nameof(ICVOrderBookColumn)} ({ob_columns.Count}/{Count}).");
|
||||
foreach (var col in ob_columns)
|
||||
_unhook(col);
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
void _hook(ICVOrderBookColumn new_item) => new_item.filter.filter_changed += _on_column_filter_changed;
|
||||
void _unhook(ICVOrderBookColumn old_item) => old_item.filter.filter_changed -= _on_column_filter_changed;
|
||||
void _on_column_filter_changed() => filter_changed?.Invoke();
|
||||
|
||||
void _reset_active_comparer(ICVOrderBookColumn column) {
|
||||
foreach (var col in _active_comparers) {
|
||||
if (!col.Equals(column)) col.SortDirection = null;
|
||||
}
|
||||
_active_comparers.Clear();
|
||||
_active_comparers.Add(column);
|
||||
}
|
||||
}
|
||||
|
||||
static IComparer<OrderBookRowVM> _wrap_comparer<T>(Func<OrderBookRowVM, T> get) =>
|
||||
Comparer<T>.Default.wrap(get);
|
||||
|
||||
static IComparer<OrderBookRowVM> _wrap_comparer<T>(Func<OrderBookRowVM, T> get, IComparer<T> comparer) =>
|
||||
comparer.wrap(get);
|
||||
|
||||
public abstract class OrderbookColumnBase<T> : ColumnBase<Columns>, IKeyedColumn<Columns>, IFilterableColumn, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
readonly string _filter_template;
|
||||
protected readonly Func<OrderBookRowVM, IOrderBookCellStore> _get_cell_store;
|
||||
protected readonly Func<OrderBookRowVM, string> _get_text;
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, IOrderBookCellStore> get_cell_store, string sort_prop, double width, IDataGridColumnPersistentFilter<OrderBookRowVM, T> filter, string filter_template) : base(col_key) {
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
|
||||
IDataGridColumnPersistentFilter<OrderRecordTuple, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
_get_cell_store = get_cell_store;
|
||||
_get_text = get_text;
|
||||
sort_member_path = sort_prop;
|
||||
this.filter = filter;
|
||||
_filter_template = filter_template;
|
||||
_cached_sort_direction = SortDirection;
|
||||
this.comparer = comparer;
|
||||
font_params_changed += refresh_glyph_contexts;
|
||||
refresh_glyph_contexts();
|
||||
void refresh_glyph_contexts() {
|
||||
@@ -341,14 +488,13 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderBookRowVM, T> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderRecordTuple, T> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderBookRowVM> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderRecordTuple> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
public IGridCellStore get_cell_store(OrderBookRowVM row) => _get_cell_store(row);
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
@@ -358,99 +504,51 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, IOrderBookCellStore> get_cell_store, double width, IDataGridColumnPersistentFilter<OrderBookRowVM, TFilterState> filter, string filter_template)
|
||||
: base(key, header, get_cell_store, sort_prop, width, filter, filter_template) { }
|
||||
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderRecordTuple, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
var cell = _get_cell_store(row);
|
||||
if (!string.IsNullOrEmpty(cell.current.text))
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, cell.current.text, position, RowHeight,
|
||||
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, cell.animations);
|
||||
}
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, RowHeight,
|
||||
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.store, nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues) { }
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderRecordTuple, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.text, position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.side.store.animations);
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.store, nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderRecordTuple, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.quantity);
|
||||
}
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.text, position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.quantity.store.animations);
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.store, nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Filled, filter, DataGridFilterKnownTemplates.UIntMinMax) {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderRecordTuple, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
|
||||
}
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.text, position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.filled_quantity.store.animations);
|
||||
}
|
||||
|
||||
public class PriceColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public PriceColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
|
||||
: base(Columns.Price, "Price", r => r.price.store, nameof(OrderBookRowVM.price), DefaultColumnWidths.Price, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.price.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.price.store.animations);
|
||||
}
|
||||
|
||||
public class AvgFilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public AvgFilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
|
||||
: base(Columns.AvgFilled, "Avg Filled", r => r.avg_price.store, nameof(OrderBookRowVM.avg_price), DefaultColumnWidths.AveragePrice, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) {
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.avg_price.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.avg_price.store.animations);
|
||||
}
|
||||
}
|
||||
|
||||
public class LastFilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public LastFilledColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, DoubleMinMaxState> filter)
|
||||
: base(Columns.LastPrice, "Last Filled", r => r.last_price.store, nameof(OrderBookRowVM.last_price), DefaultColumnWidths.LastPrice, filter, DataGridFilterKnownTemplates.DoubleMinMax) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.last_price.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.last_price.store.animations);
|
||||
}
|
||||
|
||||
public class TimeCreatedColumn : OrderbookColumnBase<TimeFilterTuple>, IRenderColumn<OrderBookRowVM> {
|
||||
public TimeCreatedColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, TimeFilterTuple> filter)
|
||||
: base(Columns.Time, "Time", r => r.time_created.store, nameof(OrderBookRowVM.time_created), DefaultColumnWidths.Time, filter, DataGridFilterKnownTemplates.TimeFilter) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.time_created.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.time_created.store.animations);
|
||||
}
|
||||
|
||||
public class LastChangeTimeColumn : OrderbookColumnBase<TimeFilterTuple>, IRenderColumn<OrderBookRowVM> {
|
||||
public LastChangeTimeColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, TimeFilterTuple> filter)
|
||||
: base(Columns.LastChange, "Last Change", r => r.last_change.store, nameof(OrderBookRowVM.last_change), DefaultColumnWidths.Time, filter, DataGridFilterKnownTemplates.TimeFilter) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.last_change.text,
|
||||
position, RowHeight, font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding, row.last_change.store.animations);
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, RowHeight,
|
||||
RenderUtils.get_side_brush(row), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
public OrderBookOrderTypeColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, OrderType[]> filter)
|
||||
: base(Columns.OrderType, new Binding($"{nameof(OrderBookRowVM.order_type)}.{nameof(IOrderBookCell<OrderType>.value)}"), new Binding(nameof(OrderBookRowVM.order_metadata)), filter) {
|
||||
public OrderBookOrderTypeColumn(IDataGridColumnPersistentFilter<OrderRecordTuple, OrderType[]> filter)
|
||||
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.order_type)), new Binding(nameof(OrderBookRowVM.order_metadata)), filter) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
sort_member_path = nameof(OrderBookRowVM.order_type);
|
||||
@@ -458,12 +556,11 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderBookRowVM, OrderType[]> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderRecordTuple, OrderType[]> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.order_type);
|
||||
|
||||
IDataGridColumnFilter<OrderBookRowVM> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
public IGridCellStore get_cell_store(OrderBookRowVM row) => row.order_type.store;
|
||||
IDataGridColumnFilter<OrderRecordTuple> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
@@ -474,8 +571,8 @@ namespace mbts.spark {
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, string> filter)
|
||||
: base(Columns.LastMessage, new Binding($"{nameof(OrderBookRowVM.last_msg)}.{nameof(OrderBookRowVM.IOrderBookCell<string>.value)}"), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<OrderRecordTuple, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(OrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
@@ -484,12 +581,11 @@ namespace mbts.spark {
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderBookRowVM, string> filter { get; }
|
||||
public new IDataGridColumnPersistentFilter<OrderRecordTuple, string> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.last_msg);
|
||||
|
||||
IDataGridColumnFilter<OrderBookRowVM> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
public IGridCellStore get_cell_store(OrderBookRowVM row) => row.last_msg.store;
|
||||
IDataGridColumnFilter<OrderRecordTuple> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
@@ -504,7 +600,7 @@ namespace mbts.spark {
|
||||
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderBookRowVM, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderRecordTuple, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = DefaultColumnHeaders.Security;
|
||||
@@ -513,14 +609,13 @@ namespace mbts.spark {
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
public IDataGridColumnPersistentFilter<OrderBookRowVM, SecurityCriteria> filter { get; }
|
||||
public IDataGridColumnPersistentFilter<OrderRecordTuple, SecurityCriteria> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.security, SecurityHelper.Comparer.InstanceImmutable.to_nullable());
|
||||
|
||||
IDataGridColumnFilter<OrderBookRowVM> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter<OrderRecordTuple> ICVOrderBookColumn.filter => filter;
|
||||
IDataGridColumnFilter IFilterableColumn.filter => filter;
|
||||
string IFilterableColumn.filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
public IGridCellStore get_cell_store(OrderBookRowVM row) => row.security.store;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
// expander
|
||||
@@ -533,7 +628,7 @@ namespace mbts.spark {
|
||||
var security_view = new TextBlock { Foreground = font_brush };
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.TextProperty, $"{nameof(OrderBookRowVM.security)}.{nameof(IGridCell.text)}");
|
||||
security_view.SetBinding(TextBlock.TextProperty, nameof(OrderBookRowVM.security_text));
|
||||
security_view.SetBinding(TextBlock.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
@@ -547,7 +642,7 @@ namespace mbts.spark {
|
||||
grid.Children.Add(security_view);
|
||||
//
|
||||
var border = new Border { Child = grid };
|
||||
CellRender.set_background_animation_bindings(border, $"{nameof(OrderBookRowVM.security)}.{nameof(OrderBookRowVM.security.store)}");
|
||||
CellRender.set_background_animation_bindings(border, nameof(OrderBookRowVM.security));
|
||||
return border;
|
||||
}
|
||||
|
||||
@@ -581,7 +676,7 @@ namespace mbts.spark {
|
||||
return mb;
|
||||
}
|
||||
|
||||
public static Brush get_side_brush(OrderBookRowVM r) => r.side.value == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
|
||||
public static Brush get_side_brush(OrderBookRowVM r) => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
|
||||
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
class OrderBookRowCollection : ObservableCollection<OrderBookRowVM> {
|
||||
static readonly IEqualityComparer<INode<OrderBookRowVM>> _node_equality_comparer = EqualityComparer.Create<INode<OrderBookRowVM>, uint>(n => n.value.order_key);
|
||||
readonly Func<OrderRecordTuple, OrderBookRowVM> _row_factory;
|
||||
SortedTree<OrderBookRowVM, uint> _tree;
|
||||
IComparer<OrderBookRowVM> _comparer;
|
||||
Func<OrderRecordTuple, bool> _filter;
|
||||
bool _is_refresh_pending;
|
||||
int _deferment_count = 0;
|
||||
/// When processing changed-items, the <see cref="SortedTree{T, TKey}.TryReplace(TKey, Func{T, T})"/> method is used to update the changed row. It is not expected to fail (return false),
|
||||
/// but if it does, we don't want to overwhelm the logs with a bunch of useless noise in case it starts happening repeatedly, hence the int fields below.
|
||||
/// <see cref="_max_replace_misses_logged"/>, <see cref="_n_replace_misses_logged"/>, <see cref="update(IIncrementalTableUpdateTransform{OrderRecordTuple, uint})"/>.
|
||||
const int _max_replace_misses_logged = 10;
|
||||
int _n_replace_misses_logged;
|
||||
|
||||
public OrderBookRowCollection(Func<OrderRecordTuple, OrderBookRowVM> row_factory, IComparer<OrderBookRowVM> comparer) {
|
||||
_row_factory = row_factory;
|
||||
_tree = _mk_tree(_comparer = comparer);
|
||||
}
|
||||
|
||||
public Func<OrderRecordTuple, bool> filter {
|
||||
get => _filter;
|
||||
set {
|
||||
_filter = value;
|
||||
_refresh_branches(_tree.RootNodes);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
public IComparer<OrderBookRowVM> comparer {
|
||||
get => _comparer;
|
||||
set {
|
||||
_tree = _tree.Resort(_comparer = value);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
public int total_row_count { get; private set; }
|
||||
IEnumerable<OrderBookRowVM> _visible_rows => _tree.Where(r => r.is_visible);
|
||||
|
||||
bool _is_refresh_deferred => _deferment_count > 0;
|
||||
|
||||
public IEnumerable<INode<OrderBookRowVM>> get_inner_nodes(OrderBookRowVM row) =>
|
||||
_tree.EnumerateInnerNodes(row);
|
||||
|
||||
public IEnumerable<OrderBookRowVM> get_descendent_rows(OrderBookRowVM row) =>
|
||||
_tree.EnumerateInnerValues(row);
|
||||
|
||||
public void collapse_all() {
|
||||
foreach (var n in _tree.RootNodes) {
|
||||
collapse_node(n);
|
||||
}
|
||||
if (_is_refresh_deferred) {
|
||||
_is_refresh_pending = true;
|
||||
} else {
|
||||
Clear();
|
||||
foreach (var n in _tree.RootNodes) {
|
||||
if (n.value.is_visible)
|
||||
Add(n.value);
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void collapse_node(INode<OrderBookRowVM> node) {
|
||||
node.value.collapse_internal();
|
||||
foreach (var cn in node.inner) {
|
||||
cn.value.is_visible = false;
|
||||
collapse_node(cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void expand_all() {
|
||||
foreach (var n in _tree.RootNodes) {
|
||||
expand_node(n);
|
||||
}
|
||||
if (_is_refresh_deferred) {
|
||||
_is_refresh_pending = true;
|
||||
} else {
|
||||
Clear();
|
||||
foreach (var n in _tree.RootNodes) {
|
||||
if (n.value.is_visible) {
|
||||
Add(n.value);
|
||||
foreach (var r in n.inner_values())
|
||||
Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void expand_node(INode<OrderBookRowVM> node) {
|
||||
node.value.expand_internal();
|
||||
foreach (var cn in node.inner) {
|
||||
cn.value.is_visible = node.value.is_visible;
|
||||
expand_node(cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the collection by calling <see cref="GlobalExtensions.sync_with{T}(IList{T}, IEnumerable{T})"/> with all visible rows.
|
||||
/// </summary>
|
||||
public void refresh() {
|
||||
if (_is_refresh_deferred) {
|
||||
_is_refresh_pending = true;
|
||||
} else {
|
||||
_is_refresh_pending = false;
|
||||
this.sync_with(_visible_rows);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the collection by calling <see cref="Collection{T}.Clear"/>, then adding all visible rows.
|
||||
/// </summary>
|
||||
public void reset(bool force = false) {
|
||||
if (_is_refresh_deferred && !force) {
|
||||
_is_refresh_pending = true;
|
||||
} else {
|
||||
Clear();
|
||||
this.AddRange(_visible_rows);
|
||||
_is_refresh_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable defer_refresh(bool force_refresh_after = false) {
|
||||
// use a counter instead of a simple bool flag to gracefully handle recursive calls to defer_refresh(),
|
||||
// by only calling refresh() after all IDisposables returned by defer_refresh() have been disposed.
|
||||
_deferment_count++;
|
||||
_is_refresh_pending |= force_refresh_after;
|
||||
return Disposable.Create(() => {
|
||||
_deferment_count--;
|
||||
if (_is_refresh_pending)
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public void update(IIncrementalTableUpdateTransform<OrderRecordTuple, uint> table) {
|
||||
IEnumerable<INode<OrderBookRowVM>> impacted_branch_roots;
|
||||
int n_removed = table.removed.Count();
|
||||
if (n_removed == total_row_count) {
|
||||
// full refresh (most likely this is the first update or a server restart)
|
||||
_tree = _mk_tree(_comparer, table.added.Select(_row_factory));
|
||||
impacted_branch_roots = _tree.RootNodes;
|
||||
} else {
|
||||
foreach (OrderRecordTuple ort in table.removed)
|
||||
_tree.Remove(ort.order.Key);
|
||||
foreach (Change<OrderRecordTuple> change in table.changes) {
|
||||
if (!_tree.TryReplace(change.New.order.Key, r => { r.update(change.New); return r; })) {
|
||||
// uh oh, replace failed! Let's log this, but not a million times (this seems like the kind of error that, if it happens once, could happen a lot).
|
||||
if (_n_replace_misses_logged++ < _max_replace_misses_logged)
|
||||
Log.Ui.error($"{nameof(OrderBookRowCollection)} failed to process Changed {nameof(OrderRecordTuple)} in update (old:{change.Old.key}, new:{change.New.key}). This is the '{_n_replace_misses_logged}' time this has happened in this OrderBook.");
|
||||
}
|
||||
}
|
||||
foreach (OrderRecordTuple ort in table.added)
|
||||
_tree.Insert(_row_factory(ort));
|
||||
impacted_branch_roots =
|
||||
table.added.Concat(table.changed).Select(ort => ort.order.Key).Select(_tree.GetRootNode)
|
||||
.Concat(
|
||||
table.removed
|
||||
.Where(ort => ort.order.has_parent())
|
||||
.Select(ort => ort.order.ParentKey)
|
||||
.Distinct() // since these are ParentKeys, there could be duplicates.
|
||||
.Select(_tree.GetRootNode)
|
||||
.Where(n => n != null)
|
||||
).Distinct(_node_equality_comparer);
|
||||
}
|
||||
_refresh_branches(impacted_branch_roots);
|
||||
refresh();
|
||||
int old_row_count = total_row_count;
|
||||
total_row_count += table.added.Count() - n_removed;
|
||||
if (old_row_count != total_row_count)
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(total_row_count)));
|
||||
}
|
||||
|
||||
static SortedTree<OrderBookRowVM, uint> _mk_tree(IComparer<OrderBookRowVM> comp, IEnumerable<OrderBookRowVM> init = null) =>
|
||||
new SortedTree<OrderBookRowVM, uint>(comp, r => r.order_key, r => r.parent_order_key, init);
|
||||
|
||||
void _refresh_branches(IEnumerable<INode<OrderBookRowVM>> roots) {
|
||||
if (filter == null) {
|
||||
foreach (var n in roots)
|
||||
refresh_tree_properties(0, true, n);
|
||||
} else {
|
||||
foreach (var n in roots)
|
||||
refresh_tree_properties(0, get_branch_passes_filter(n), n);
|
||||
}
|
||||
// local methods
|
||||
void refresh_tree_properties(int depth_level, bool is_visible, INode<OrderBookRowVM> node) {
|
||||
node.value.depth_level = depth_level;
|
||||
node.value.is_visible = is_visible;
|
||||
using (var en = node.inner.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
node.value.is_parent = true;
|
||||
depth_level++;
|
||||
do refresh_tree_properties(depth_level, node.value.is_visible && node.value.is_expanded, en.Current);
|
||||
while (en.MoveNext());
|
||||
}
|
||||
}
|
||||
}
|
||||
bool get_branch_passes_filter(INode<OrderBookRowVM> n) =>
|
||||
filter(n.value.order_tuple) || n.inner.Any(get_branch_passes_filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,407 +6,135 @@ using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using OrderStatus = bts.api.orderman.OrderStatus;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM<OrderRecordTuple>, DIYGrid.IActionableRowVM {
|
||||
//public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _book_vm;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<bool> _visible, _expanded, _passes_filter;
|
||||
readonly DelegateProperty<int> _depth_level;
|
||||
readonly HashSet<OrderBookRowVM> _child_rows = new HashSet<OrderBookRowVM>();
|
||||
readonly ObservantCommand _show_order_modify_cmd;
|
||||
OrderRecordImmutable _order;
|
||||
SecurityMD? _security_md;
|
||||
string _symbol;
|
||||
OrderBookRowVM _parent;
|
||||
OrderBookVM.BranchFilterSource _branch_filter_source;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _security_key_store_backing;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store_backing;
|
||||
IOrderBookCellStore<Venue> _exchange_store_backing;
|
||||
IOrderBookCellStore<string> _symbol_store_backing;
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store_backing;
|
||||
IOrderBookCellStore<Side> _side_store_backing;
|
||||
IOrderBookCellStore<string> _user_store_backing;
|
||||
IOrderBookCellStore<string> _account_store_backing;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store_backing;
|
||||
IOrderBookCellStore<DateTime> _time_created_store_backing;
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store_backing;
|
||||
IOrderBookCellStore<OrderStatus> _status_store_backing;
|
||||
IOrderBookCellStore<double> _price_store_backing;
|
||||
IOrderBookCellStore<uint> _quantity_store_backing;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store_backing;
|
||||
IOrderBookCellStore<double> _last_price_store_backing;
|
||||
IOrderBookCellStore<double> _avg_price_store_backing;
|
||||
IOrderBookCellStore<string> _last_msg_store_backing;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store_backing;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store => _order_key_store_backing ?? (_order_key_store_backing = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store));
|
||||
IOrderBookCellStore<uint> _parent_order_key_store => _parent_order_key_store_backing ?? (_parent_order_key_store_backing = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store));
|
||||
IOrderBookCellStore<uint> _security_key_store => _security_key_store_backing ?? (_security_key_store_backing = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store));
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store => _security_store_backing ?? (_security_store_backing = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()));
|
||||
IOrderBookCellStore<Venue> _exchange_store => _exchange_store_backing ?? (_exchange_store_backing = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store));
|
||||
IOrderBookCellStore<string> _symbol_store => _symbol_store_backing ?? (_symbol_store_backing = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"));
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store => _order_type_store_backing ?? (_order_type_store_backing = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store));
|
||||
IOrderBookCellStore<Side> _side_store => _side_store_backing ?? (_side_store_backing = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store));
|
||||
IOrderBookCellStore<string> _user_store => _user_store_backing ?? (_user_store_backing = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store));
|
||||
IOrderBookCellStore<string> _account_store => _account_store_backing ?? (_account_store_backing = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store));
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store => _time_in_force_store_backing ?? (_time_in_force_store_backing = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store));
|
||||
IOrderBookCellStore<DateTime> _time_created_store => _time_created_store_backing ?? (_time_created_store_backing = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date));
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store => _last_change_store_backing ?? (_last_change_store_backing = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date));
|
||||
IOrderBookCellStore<OrderStatus> _status_store => _status_store_backing ?? (_status_store_backing = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store));
|
||||
IOrderBookCellStore<double> _price_store => _price_store_backing ?? (_price_store_backing = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price));
|
||||
IOrderBookCellStore<uint> _quantity_store => _quantity_store_backing ?? (_quantity_store_backing = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store));
|
||||
IOrderBookCellStore<uint> _filled_quantity_store => _filled_quantity_store_backing ?? (_filled_quantity_store_backing = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store));
|
||||
IOrderBookCellStore<double> _last_price_store => _last_price_store_backing ?? (_last_price_store_backing = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price));
|
||||
IOrderBookCellStore<double> _avg_price_store => _avg_price_store_backing ?? (_avg_price_store_backing = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price));
|
||||
IOrderBookCellStore<string> _last_msg_store => _last_msg_store_backing ?? (_last_msg_store_backing = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store));
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store => _order_sub_type_store_backing ?? (_order_sub_type_store_backing = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type));
|
||||
readonly OrderBookVM _owner;
|
||||
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd;
|
||||
bool _is_parent, _is_expanded;
|
||||
int _depth_level;
|
||||
SecurityMD? _security_md => order_tuple.security_md;
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderRecordTuple order_tuple) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
is_visible = !is_child;
|
||||
}
|
||||
|
||||
static string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_price(double v) => _security.HasValue ?
|
||||
_security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
static string format_sub_type(OrderSubType v) {
|
||||
public event Action invalidated;
|
||||
|
||||
// Core
|
||||
public OrderRecordTuple order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order;
|
||||
public OrderMetadata order_metadata => _owner.get_metadata(this);
|
||||
public uint order_key => order.Key;
|
||||
public uint parent_order_key => order.ParentKey;
|
||||
public uint security_key => order.security_key();
|
||||
public SecurityImmutable? security => _security_md.security();
|
||||
public string security_text => security.display_name();
|
||||
public Venue exchange => _security_md.exchange();
|
||||
public string symbol => _security_md.symbol();
|
||||
// Def
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => order.order_type();
|
||||
public OrderSubType order_sub_type => order.sub_type();
|
||||
public string order_sub_type_text => _fmt_order_sub_type(order_sub_type);
|
||||
public Side side => order.side();
|
||||
public string user => order.user();
|
||||
public string account => order.account();
|
||||
public TimeInForce time_in_force => order.time_in_force();
|
||||
public DateTime time_created => order.created();
|
||||
public string time_created_text => _fmt_date(time_created);
|
||||
// State
|
||||
public DateTime last_change => order.last_change();
|
||||
public string last_change_text => _fmt_date(last_change);
|
||||
public OrderStatus status => order.status();
|
||||
public double price => order.price();
|
||||
public double last_price => order.last_price();
|
||||
public double avg_price => order.avg_price();
|
||||
public string price_text => _fmt_price(price);
|
||||
public string last_price_text => _fmt_price(last_price);
|
||||
public string avg_price_text => _fmt_price(avg_price);
|
||||
public uint quantity => order.quantity();
|
||||
public uint filled_quantity => order.filled();
|
||||
public string last_msg => order.last_msg();
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
public bool is_child => order.has_parent();
|
||||
public bool is_visible { get; set; }
|
||||
public bool is_expanded {
|
||||
get => _is_expanded;
|
||||
set {
|
||||
if (Set(ref _is_expanded, value))
|
||||
_owner.on_expanded_changed(this);
|
||||
}
|
||||
}
|
||||
public int depth_level { get => _depth_level; set => Set(ref _depth_level, value); }
|
||||
// Commands
|
||||
public ICommand toggle_command => _toggle_expanded_cmd ?? (
|
||||
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
|
||||
|
||||
ICommand DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd ??
|
||||
(_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
|
||||
// These fns present a way for the orderbook to avoid noisy, intermittent collection changes during "Expand/Collapse All" ops,
|
||||
// when incremental changes are ignored in favor of a full reset, after 'is_expanded' is updated for all rows.
|
||||
internal void expand_internal() => Set(ref _is_expanded, true);
|
||||
internal void collapse_internal() => Set(ref _is_expanded, false);
|
||||
|
||||
public void update(OrderRecordTuple order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
bool had_security = _security_md.HasValue;
|
||||
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
|
||||
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
|
||||
OrderRecordImmutable old_order = order;
|
||||
this.order_tuple = order_tuple;
|
||||
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
|
||||
bool invalidate = !(
|
||||
old_order.last_change() == order.last_change() &&
|
||||
old_order.filled() == order.filled() &&
|
||||
old_order.last_price() == order.last_price() &&
|
||||
old_order.avg_price().Equals(order.avg_price()) &&
|
||||
old_order.price().Equals(order.price()) &&
|
||||
old_order.quantity() == order.quantity() &&
|
||||
old_order.last_msg() == order.last_msg());
|
||||
// however, the order-type and security cells DO use databindings - if these cells have changed, the PropertyChanged event must be raised regardless of whether or not invalidated is raised
|
||||
if (old_order.order_type() != order.order_type())
|
||||
RaisePropertyChanged(nameof(order_type), nameof(order_metadata));
|
||||
if (!had_security && _security_md.HasValue) {
|
||||
RaisePropertyChanged(nameof(security), nameof(security_text));
|
||||
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
|
||||
invalidate = true;
|
||||
}
|
||||
if (old_order.status() != order.status()) {
|
||||
invalidate = true;
|
||||
RaisePropertyChanged(nameof(status));
|
||||
}
|
||||
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
|
||||
_show_order_modify_cmd.RaiseCanExecuteChanged();
|
||||
if (invalidate)
|
||||
raise_invalidated();
|
||||
}
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public bool Equals(OrderBookRowVM other) => other != null && other.order.Key == order.Key;
|
||||
public override bool Equals(object obj) => Equals(obj as OrderBookRowVM);
|
||||
public override int GetHashCode() => (int)order.Key;
|
||||
public override string ToString() => $"{order_key} {last_change} {parent_order_key}";
|
||||
|
||||
static string _fmt_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
static string _fmt_order_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public OrderBookRowVM(IBus bus, OrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityMD? s) {
|
||||
_book_vm = source_vm;
|
||||
_model = model;
|
||||
_order = record;
|
||||
_security_md = s;
|
||||
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
|
||||
//animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
_visible = backing(nameof(is_visible), true);
|
||||
_expanded = backing(nameof(is_expanded), false);
|
||||
_depth_level = backing(nameof(depth_level), () => {
|
||||
int i = 0;
|
||||
var curr = this;
|
||||
while (curr._parent != null) {
|
||||
i++;
|
||||
curr = curr._parent;
|
||||
}
|
||||
return i;
|
||||
});
|
||||
toggle_command = new DelegateCommand(() => is_expanded = !is_expanded);
|
||||
_show_order_modify_cmd = new ObservantCommand(() => bus.post(SparkTags.ShowModifyOrderTag, _order.Key));
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
_passes_filter = backing(nameof(passes_filter), row_passes_filter);
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderRecordTuple old_val, OrderRecordTuple new_val)> updated;
|
||||
|
||||
public OrderRecordImmutable order => _order;
|
||||
public OrderMetadata order_metadata => _model.factory.get_metadata(order_type.value);
|
||||
public OrderBookRowVM parent => _parent;
|
||||
public OrderRecordTuple current_data => new OrderRecordTuple(_order, _security_md);
|
||||
// Core
|
||||
public IOrderBookCell<uint> order_key => _order_key_store.current;
|
||||
public IOrderBookCell<uint> parent_order_key => _parent_order_key_store.current;
|
||||
public IOrderBookCell<uint> security_key => _security_key_store.current;
|
||||
public IOrderBookCell<SecurityImmutable?> security => _security_store.current;
|
||||
public IOrderBookCell<Venue> exchange => _exchange_store.current;
|
||||
public IOrderBookCell<string> symbol => _symbol_store.current;
|
||||
// Def
|
||||
public IOrderBookCell<CustomOrderDefinition.CustomOneofCase> order_type => _order_type_store.current;
|
||||
public IOrderBookCell<Side> side => _side_store.current;
|
||||
public IOrderBookCell<string> user => _user_store.current;
|
||||
public IOrderBookCell<string> account => _account_store.current;
|
||||
public IOrderBookCell<TimeInForce> time_in_force => _time_in_force_store.current;
|
||||
public IOrderBookCell<DateTime> time_created => _time_created_store.current;
|
||||
// State
|
||||
public IOrderBookCell<DateTime> last_change => _last_change_store.current;
|
||||
public IOrderBookCell<OrderStatus> status => _status_store.current;
|
||||
public IOrderBookCell<double> price => _price_store.current;
|
||||
public IOrderBookCell<uint> quantity => _quantity_store.current;
|
||||
public IOrderBookCell<uint> filled_quantity => _filled_quantity_store.current;
|
||||
public IOrderBookCell<double> last_price => _last_price_store.current;
|
||||
public IOrderBookCell<double> avg_price => _avg_price_store.current;
|
||||
public IOrderBookCell<string> last_msg => _last_msg_store.current;
|
||||
public IOrderBookCell<OrderSubType> order_sub_type => _order_sub_type_store.current;
|
||||
// Commands
|
||||
public ICommand toggle_command { get; }
|
||||
ICommand ui.controls.DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd;
|
||||
// Accessories
|
||||
public bool is_parent => _child_rows.Count > 0;
|
||||
public bool is_child => _order.has_parent();
|
||||
public IReadOnlyCollection<OrderBookRowVM> child_orders => _child_rows;
|
||||
public OrderBookVM.BranchFilterSource branch_filter_source => _branch_filter_source;
|
||||
public bool passes_filter => _passes_filter.Value;
|
||||
public bool row_passes_filter { get; private set; }
|
||||
public bool is_visible {
|
||||
get => _visible.Value;
|
||||
set {
|
||||
_visible.set(value);
|
||||
if (is_expanded) {
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.is_visible = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool is_expanded {
|
||||
get => _expanded.Value;
|
||||
set {
|
||||
_expanded.set(value);
|
||||
foreach (var child_row in child_orders)
|
||||
child_row.is_visible = value;
|
||||
}
|
||||
}
|
||||
public int depth_level => _depth_level.Value;
|
||||
|
||||
SecurityImmutable? _security => _security_md?.security;
|
||||
// '_dynamic_render_cells' are presented by 'RenderColumns,' and may change value during the lifetime of this row
|
||||
// Note: by instantiating all these cells inside a CellPropertyGroup, we can update all of them with a single call.
|
||||
// this reduces the risk of accidentally failing to update one of the cells, esp if we make changes to them down the road.
|
||||
IEnumerable<IOrderBookCellStore> _dynamic_render_cells {
|
||||
get {
|
||||
yield return _last_change_store;
|
||||
yield return _price_store;
|
||||
yield return _quantity_store;
|
||||
yield return _filled_quantity_store;
|
||||
yield return _avg_price_store;
|
||||
yield return _last_price_store;
|
||||
yield return _status_store;
|
||||
yield return _last_msg_store;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IOrderBookCellStore> _security_dependent_cells {
|
||||
get {
|
||||
yield return _security_store;
|
||||
yield return _exchange_store;
|
||||
yield return _symbol_store;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IOrderBookCellStore> _price_cells {
|
||||
get {
|
||||
yield return _price_store;
|
||||
yield return _avg_price_store;
|
||||
yield return _last_price_store;
|
||||
yield return _symbol_store;
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
|
||||
DIYGrid.IGridCellStore[] _cell_stores_backing;
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => _cell_stores_backing ?? (_cell_stores_backing = new DIYGrid.IGridCellStore[] {
|
||||
_security_store,
|
||||
_security_key_store,
|
||||
_parent_order_key_store,
|
||||
_order_key_store,
|
||||
_symbol_store,
|
||||
_time_created_store,
|
||||
_user_store,
|
||||
_account_store,
|
||||
_exchange_store,
|
||||
_time_in_force_store,
|
||||
_order_type_store,
|
||||
_order_sub_type_store,
|
||||
_side_store,
|
||||
_last_change_store,
|
||||
_price_store,
|
||||
_quantity_store,
|
||||
_filled_quantity_store,
|
||||
_avg_price_store,
|
||||
_last_price_store,
|
||||
_status_store,
|
||||
_last_msg_store
|
||||
});
|
||||
|
||||
public static Stack<OrderBookRowVM> lineage(OrderBookRowVM node) {
|
||||
var rv = new Stack<OrderBookRowVM>();
|
||||
rv.Push(node);
|
||||
while (node._parent != null)
|
||||
rv.Push((node = node._parent));
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void join_branch_filter_source(OrderBookVM.BranchFilterSource new_branch_filter_source) {
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.passes_filter_changed -= _passes_filter.set;
|
||||
}
|
||||
_branch_filter_source = new_branch_filter_source;
|
||||
_branch_filter_source.set_row_passes_filter(_order.Key, row_passes_filter);
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.join_branch_filter_source(_branch_filter_source);
|
||||
_passes_filter.set(_branch_filter_source.passes_filter);
|
||||
_branch_filter_source.passes_filter_changed += _passes_filter.set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the child-row to this row's branch-filter-source, and recursively refreshes sort/filter properties for the child and it's descendents.
|
||||
/// </summary>
|
||||
public void add_child_order(OrderBookRowVM child_row) {
|
||||
if (_child_rows.Add(child_row) && _child_rows.Count == 1)
|
||||
RaisePropertyChanged(nameof(is_parent));
|
||||
child_row._parent = this;
|
||||
// child orders that arrived in an earlier update use a temporary branch_filter_source
|
||||
// need to tell _book_vm to remove the now-stale branch_filter_source.
|
||||
if (child_row._branch_filter_source != null)
|
||||
_book_vm.remove_branch_filter_root(child_row._order.Key);
|
||||
// recursively updates 'live_sorting_props,' and 'is_visible' for child_row and descendents
|
||||
child_row._on_lineage_changed();
|
||||
}
|
||||
|
||||
public void remove_child_order(OrderBookRowVM child_row) {
|
||||
if (_child_rows.Remove(child_row) && _child_rows.Count == 0)
|
||||
RaisePropertyChanged(nameof(is_parent));
|
||||
}
|
||||
|
||||
public void remove_parent(OrderBookVM.BranchFilterSource new_branch_filter_source) {
|
||||
_parent = null;
|
||||
join_branch_filter_source(new_branch_filter_source);
|
||||
refresh_row_passes_filter();
|
||||
foreach (var p in _book_vm.grid_settings_manager.live_sorting_props)
|
||||
RaisePropertyChanged(p);
|
||||
_depth_level.refresh();
|
||||
foreach (OrderBookRowVM child_row in _child_rows)
|
||||
child_row._on_lineage_changed();
|
||||
}
|
||||
|
||||
public void update_order(OrderRecordImmutable order) {
|
||||
Log.assert(order.ParentKey == _order.ParentKey, "Parent Keys can change?");
|
||||
Log.assert(order.Key == _order.Key, "Keys can change?");
|
||||
var old_data = current_data;
|
||||
_order = order;
|
||||
if (_update_partial(_dynamic_render_cells))
|
||||
raise_invalidated();
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
refresh_row_passes_filter();
|
||||
updated?.Invoke((old_data, current_data));
|
||||
}
|
||||
|
||||
public void update_security(SecurityMD security_md) {
|
||||
var old_data = current_data;
|
||||
var old_spec = _security.spec_or_default();
|
||||
_security_md = security_md;
|
||||
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
|
||||
if (!Equals(old_spec, _security.spec_or_default())) {
|
||||
_update_partial(_security_dependent_cells);
|
||||
foreach (var cell_store in _price_cells)
|
||||
cell_store.regenerate_cell();
|
||||
raise_invalidated();
|
||||
} else if (_update_partial(_security_dependent_cells)) {
|
||||
raise_invalidated();
|
||||
}
|
||||
refresh_row_passes_filter();
|
||||
updated?.Invoke((old_data, current_data));
|
||||
}
|
||||
|
||||
public void refresh_row_passes_filter() {
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.set_row_passes_filter(_order.Key, row_passes_filter);
|
||||
} else {
|
||||
_passes_filter.set(row_passes_filter);
|
||||
}
|
||||
}
|
||||
|
||||
public void raise_invalidated() => invalidated?.Invoke();
|
||||
|
||||
public bool Equals(OrderBookRowVM other) => other != null && other._order.Key == _order.Key;
|
||||
public override bool Equals(object obj) => Equals(obj as OrderBookRowVM);
|
||||
public override int GetHashCode() => (int)_order.Key;
|
||||
public override string ToString() => $"{order_key} {last_change} {parent_order_key}";
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
// idempotent
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.passes_filter_changed -= _passes_filter.set;
|
||||
_branch_filter_source = null;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<OrderBookRowVM> _descendents(OrderBookRowVM root) {
|
||||
foreach (var child in root.child_orders) {
|
||||
yield return child;
|
||||
foreach (var grand_child in _descendents(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
}
|
||||
|
||||
static IOrderBookCellStore _cell_store_from_column(OrderBookRowVM row, Columns column) {
|
||||
switch (column) {
|
||||
case Columns.OrderKey: return row._order_key_store;
|
||||
case Columns.ParentKey: return row._parent_order_key_store;
|
||||
case Columns.SecurityKey: return row._security_key_store;
|
||||
case Columns.Security: return row._security_store;
|
||||
case Columns.Symbol: return row._symbol_store;
|
||||
case Columns.Exchange: return row._exchange_store;
|
||||
case Columns.OrderType: return row._order_type_store;
|
||||
case Columns.Side: return row._side_store;
|
||||
case Columns.User: return row._user_store;
|
||||
case Columns.Account: return row._account_store;
|
||||
case Columns.TimeInForce: return row._time_in_force_store;
|
||||
case Columns.Time: return row._time_created_store;
|
||||
case Columns.LastChange: return row._last_change_store;
|
||||
case Columns.Status: return row._status_store;
|
||||
case Columns.Price: return row._price_store;
|
||||
case Columns.Size: return row._quantity_store;
|
||||
case Columns.Filled: return row._filled_quantity_store;
|
||||
case Columns.LastPrice: return row._last_price_store;
|
||||
case Columns.AvgFilled: return row._avg_price_store;
|
||||
case Columns.LastMessage: return row._last_msg_store;
|
||||
case Columns.SubType: return row._order_sub_type_store;
|
||||
default: throw new ArgumentException(nameof(column));
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this method assumes that lineage exists (i.e. _parent is not null).
|
||||
void _on_lineage_changed() {
|
||||
_depth_level.refresh();
|
||||
// parent 'branch_filter_source' can be null:
|
||||
// if the parent is itself an orphan - meaning this is the middle of an update -
|
||||
// the final branch-filter-root will be determined after all new orders have been added
|
||||
if (_parent._branch_filter_source != null)
|
||||
join_branch_filter_source(_parent._branch_filter_source);
|
||||
_visible.set(_parent.is_visible && _parent.is_expanded);
|
||||
// refresh the sort by calling regenerate_cell() for each live-sorting-columns corresponding cell-store
|
||||
foreach (Columns column in _book_vm.live_sorting_columns)
|
||||
_cell_store_from_column(this, column).regenerate_cell();
|
||||
// finally, repeat for each child-row
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row._on_lineage_changed();
|
||||
}
|
||||
|
||||
bool _update_partial(IEnumerable<IOrderBookCellStore> target_cell_stores) {
|
||||
bool updated = false;
|
||||
foreach (var cell_store in target_cell_stores) {
|
||||
if (cell_store.refresh()) {
|
||||
updated = true;
|
||||
if (_book_vm.live_sorting_columns.Contains(cell_store.column.key)) {
|
||||
// refresh the sort by calling regenerate_cell() for each descendent-row
|
||||
foreach (OrderBookRowVM dr in _descendents(this))
|
||||
cell_store.select_other(dr).regenerate_cell();
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
string _fmt_price(double v) => security.HasValue ?
|
||||
security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using mbts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
@@ -11,219 +11,61 @@ using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using _GridSettingsManager = mbts.ui.GridSettingsManager<mbts.spark.OrderBookRowVM, mbts.spark.OrderRecordTuple, bts.shell.INotificationServiceDataCache, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookVM : DisposableViewModelBase, IHandleRowSelection {
|
||||
readonly LiveSortingColumKeys _live_sorting_columns;
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookVM : ViewModelBase, IDisposable, IHandleRowSelection, IHandleGridSorting, IWillLetYouKnowWhenIDispose {
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<OrderBookRowVM> _current_row;
|
||||
readonly Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> _order_securities_subject =
|
||||
new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
readonly OrderBookRowCollection _row_collection;
|
||||
readonly ObservableCollection<OrderBookRowVM> _selected_rows = new ObservableCollection<OrderBookRowVM>();
|
||||
readonly OrderBookColumnCollection _columns;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
readonly List<IDisposable> _resources;
|
||||
OrderBookStartupData _state;
|
||||
|
||||
Dictionary<uint, OrderBookRowVM> _order_rows_cache = new Dictionary<uint, OrderBookRowVM>();
|
||||
Dictionary<uint, BranchFilterSource> _branch_filter_roots = new Dictionary<uint, BranchFilterSource>();
|
||||
ObservableCollection<OrderBookRowVM> _order_rows = new ObservableCollection<OrderBookRowVM>();
|
||||
ObservableCollection<OrderBookRowVM> _selected_rows = new ObservableCollection<OrderBookRowVM>();
|
||||
SetMap<uint, uint> _parent_to_children = new SetMap<uint, uint>();
|
||||
SetMap<uint, uint> _security_orders = new SetMap<uint, uint>();
|
||||
|
||||
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
var order_securities_subject = new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
columns = new OrderBookColumnCollection(bus, model, order_securities_subject);
|
||||
rows_view = CollectionViewSource.GetDefaultView(_order_rows);
|
||||
rows_view.Filter = obj => obj is OrderBookRowVM vm && vm.is_visible && vm.passes_filter;
|
||||
columns.filter_changed += () => {
|
||||
foreach (var vm in _order_rows)
|
||||
vm.refresh_row_passes_filter();
|
||||
};
|
||||
var cvls = (ICollectionViewLiveShaping)rows_view;
|
||||
cvls.IsLiveFiltering = true;
|
||||
cvls.IsLiveSorting = true;
|
||||
cvls.LiveFilteringProperties.AddRange(nameof(OrderBookRowVM.is_visible), nameof(OrderBookRowVM.passes_filter));
|
||||
_columns = new OrderBookColumnCollection(bus, model, model.on_order_securities.Select(u => (u.added.Values.Select(smd => smd.security), u.removed.Values.Select(smd => smd.security))));
|
||||
_row_collection = new OrderBookRowCollection(ort => new OrderBookRowVM(this, ort), _columns.get_sorting_data().comparer) { filter = _columns.get_filter() };
|
||||
_current_row = backing<OrderBookRowVM>(nameof(current_row));
|
||||
|
||||
collapse_all_rows_command = new DelegateCommand(
|
||||
() => {
|
||||
foreach (var r in _order_rows.Where(r => r.is_visible && r.passes_filter))
|
||||
r.is_expanded = false;
|
||||
});
|
||||
expand_all_rows_command = new DelegateCommand(
|
||||
() => {
|
||||
foreach (var r in _order_rows.Where(r => r.is_visible && r.passes_filter))
|
||||
r.is_expanded = true;
|
||||
});
|
||||
|
||||
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, _order_rows, rows_view, columns);
|
||||
grid_settings_manager.load_from(OrderBookStartupData.DefaultGrid);
|
||||
_live_sorting_columns = new LiveSortingColumKeys(grid_settings_manager.live_sorting_props);
|
||||
|
||||
SetMap<uint, uint> missing_security_orders = new SetMap<uint, uint>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> security_dict = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.OnDispatcher().Subscribe(on_orders),
|
||||
_columns.filter_changed += on_filter_changed;
|
||||
collapse_all_rows_command = new DelegateCommand(_row_collection.collapse_all);
|
||||
expand_all_rows_command = new DelegateCommand(_row_collection.expand_all);
|
||||
grid_settings_manager = _create_grid_settings_manager(owner_title, bus, notification_service, model, rows, columns);
|
||||
_resources = new List<IDisposable> {
|
||||
model.on_order_tuples_table.OnDispatcher().Subscribe(_row_collection.update),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
grid_settings_manager,
|
||||
columns,
|
||||
order_securities_subject);
|
||||
|
||||
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
|
||||
// clear everything (most likely the server restarted)
|
||||
if (table.removed.Count() == _order_rows.Count && !table.added.Any()) {
|
||||
_selected_rows.Clear();
|
||||
_order_rows.DisposeAll();
|
||||
_order_rows.Clear();
|
||||
_order_rows_cache.Clear();
|
||||
_branch_filter_roots.Clear();
|
||||
_parent_to_children.clear();
|
||||
_security_orders.clear();
|
||||
missing_security_orders.clear();
|
||||
Log.Ui.warn("OrderBook received reset message.");
|
||||
return;
|
||||
}
|
||||
var added_securities = new List<SecurityImmutable>(1);
|
||||
var removed_securities = new List<SecurityImmutable>(1);
|
||||
|
||||
foreach (var o in table.removed) {
|
||||
if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
row.Dispose();
|
||||
_order_rows.Remove(row);
|
||||
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
|
||||
if (row.security.value.HasValue)
|
||||
removed_securities.Add(row.security.value.Value);
|
||||
}
|
||||
if (o.has_parent()) {
|
||||
_parent_to_children.remove(o.ParentKey, o.Key);
|
||||
if (row.parent != null) {
|
||||
row.parent.remove_child_order(row);
|
||||
} else {
|
||||
_branch_filter_roots.Remove(o.Key);
|
||||
}
|
||||
} else {
|
||||
_branch_filter_roots.Remove(o.Key);
|
||||
}
|
||||
// the child_rows will most-likely be removed in this update, but we can't be sure,
|
||||
// until that happens they must serve as tempororay branch-filter-roots
|
||||
foreach (var child_row in row.child_orders) {
|
||||
var bfs = new BranchFilterSource();
|
||||
child_row.remove_parent(bfs);
|
||||
_branch_filter_roots.Add(child_row.order.Key, bfs);
|
||||
}
|
||||
missing_security_orders.remove(o.Key);
|
||||
}
|
||||
}
|
||||
foreach (var ch in table.changes)
|
||||
_order_rows_cache[ch.New.Key].update_order(ch.New);
|
||||
var orphans = new Dictionary<uint, OrderBookRowVM>();
|
||||
foreach (var o in table.added) {
|
||||
SecurityMD? security_md;
|
||||
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
|
||||
security_md = smd;
|
||||
if (_security_orders.adds_key(o.Def.SecurityKey, o.Key))
|
||||
added_securities.Add(security_md.Value.security);
|
||||
} else {
|
||||
missing_security_orders.add(o.Def.SecurityKey, o.Key);
|
||||
security_md = null;
|
||||
_security_orders.add(o.Def.SecurityKey, o.Key);
|
||||
}
|
||||
OrderBookRowVM row = new OrderBookRowVM(bus, this, model, o, security_md);
|
||||
if (!o.has_parent()) {
|
||||
// if it doesn't have a parent, it must be a root; create a new BranchFilterSource
|
||||
var bfs = new BranchFilterSource();
|
||||
_branch_filter_roots.Add(o.Key, bfs);
|
||||
row.join_branch_filter_source(bfs);
|
||||
} else {
|
||||
_parent_to_children.add(o.ParentKey, o.Key);
|
||||
if (_order_rows_cache.TryGetValue(o.ParentKey, out OrderBookRowVM pvm)) {
|
||||
pvm.add_child_order(row);
|
||||
} else {
|
||||
// (**) look for orphans' parents again after all new orders have been added to _order_vm_cache
|
||||
// don't create a temporary BranchFilterSource root just yet. (**)
|
||||
orphans.Add(o.Key, row);
|
||||
_order_rows_cache.Add(o.Key, row);
|
||||
try_add_children(row);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_order_rows_cache.Add(o.Key, row);
|
||||
_order_rows.Add(row);
|
||||
try_add_children(row);
|
||||
}
|
||||
// (**) since all new rows have been added by now, any remaining orphans will be assigned their own branch-filter-roots
|
||||
// (usually, rows are tempororily added to orphans bc their parent is just behind them in the list of added orders) (**)
|
||||
foreach (OrderBookRowVM row in orphans.Values) {
|
||||
var bfs = new BranchFilterSource();
|
||||
row.join_branch_filter_source(bfs);
|
||||
_branch_filter_roots.Add(row.order.Key, bfs);
|
||||
_order_rows.Add(row);
|
||||
}
|
||||
if (added_securities.Count > 0 || removed_securities.Count > 0)
|
||||
order_securities_subject.OnNext((added_securities, removed_securities));
|
||||
|
||||
void try_add_children(OrderBookRowVM row) {
|
||||
if (_parent_to_children.try_lookup(row.order.Key, out ISet<uint> children))
|
||||
foreach (uint child_key in children) {
|
||||
OrderBookRowVM child_vm = _order_rows_cache[child_key];
|
||||
// (**) if children arrived in an earlier update, this will notify them that their parent has arrived (**)
|
||||
// - their tempororary branch-filter-roots will be removed, and they will join that of their parent's
|
||||
// - for the live-filter-properties, their corresponding cells will be regenerated and they will be re-inserted beneath their new parent.
|
||||
row.add_child_order(child_vm);
|
||||
if (orphans.Remove(child_key))
|
||||
_order_rows.Add(child_vm);
|
||||
else {
|
||||
_order_rows.Remove(child_vm);
|
||||
_order_rows.Add(child_vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I assume we don't need to worry about orders "losing" or "changing" their security
|
||||
// once the order vm discovers its security (by id), it will never change.
|
||||
void on_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
security_dict = securities;
|
||||
var added_securities = new List<SecurityImmutable>(); // assume securities won't be removed
|
||||
foreach (var missing_sec_orders in missing_security_orders.ToList()) {
|
||||
if (securities.TryGetValue(missing_sec_orders.Key, out SecurityMD smd)) {
|
||||
missing_security_orders.remove(missing_sec_orders.Key);
|
||||
added_securities.Add(smd.security);
|
||||
foreach (var order_key in missing_sec_orders) {
|
||||
var vm = _order_rows_cache[order_key];
|
||||
vm.update_security(smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added_securities.Count > 0)
|
||||
order_securities_subject.OnNext((added_securities, new List<SecurityImmutable>(0)));
|
||||
}
|
||||
Disposable.Create(() => {
|
||||
_columns.filter_changed -= on_filter_changed;
|
||||
Disposable.dispose(_row_collection);
|
||||
}),
|
||||
_columns
|
||||
};
|
||||
void on_filter_changed() => _row_collection.filter = _columns.get_filter();
|
||||
}
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
public event Action<bool> OnDisposed;
|
||||
|
||||
public OrderBookColumnCollection columns { get; }
|
||||
public ISet<Columns> live_sorting_columns => _live_sorting_columns;
|
||||
public ICollectionView rows_view { get; }
|
||||
public ObservableCollection<DIYGrid.ColumnBase> columns => _columns;
|
||||
public ObservableCollection<OrderBookRowVM> rows => _row_collection;
|
||||
public OrderBookRowVM current_row { get => _current_row.Value; set => _current_row.Value = value ?? _selected_rows.FirstOrDefault(); }
|
||||
public OrderRecordImmutable? current_order => current_row?.order;
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
|
||||
|
||||
public _GridSettingsManager grid_settings_manager { get; }
|
||||
public ICommand collapse_all_rows_command { get; }
|
||||
public ICommand expand_all_rows_command { get; }
|
||||
@@ -234,29 +76,93 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public OrderBookStartupData create() {
|
||||
if (_settings_manager.instance_settings == null)
|
||||
return new OrderBookStartupData(columns.create(), use_defaults: true);
|
||||
var (filters, sorting) = _columns.create();
|
||||
var gs = grid_settings_manager.create();
|
||||
return new OrderBookStartupData(columns.create(), gs.general, gs.predicate_animations, use_defaults: false);
|
||||
return new OrderBookStartupData(filters, gs.general.with(sorting_criterion: sorting), gs.predicate_animations, use_defaults: _settings_manager.instance_settings == null);
|
||||
}
|
||||
|
||||
public void load_from(OrderBookStartupData state) {
|
||||
if (state == null) return;
|
||||
_state = state;
|
||||
var gs = state.grid_settings_wrapper();
|
||||
if (gs != null) grid_settings_manager.load_from(gs);
|
||||
columns.load_from(state.filters);
|
||||
if (gs != null) {
|
||||
grid_settings_manager.load_from(gs);
|
||||
}
|
||||
(var old_filter, var old_sort) = _columns.create();
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_columns.load_from((state.filters, (state.grid ?? OrderBookStartupData.Default.grid).sorting_criterion));
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
(var new_filter, var new_sort) = _columns.create();
|
||||
bool reset_filter = !new_filter.Equals(old_filter);
|
||||
bool reset_sorting = !new_sort.Equals(old_sort);
|
||||
if (reset_filter) {
|
||||
if (reset_sorting) {
|
||||
// both filter and sort changed; use defer_refresh to avoid refreshing the collection twice
|
||||
using (_row_collection.defer_refresh()) {
|
||||
_row_collection.filter = _columns.get_filter();
|
||||
_row_collection.comparer = _columns.get_sorting_data().comparer;
|
||||
/// <see cref="OrderBookRowCollection.reset(bool)"/> is an optimization; a way to hedge our bets. Interally it calls Clear(), then AddAll(_visible_rows).
|
||||
/// Otherwise, <see cref="OrderBookRowCollection.refresh(bool)"/> would be called when we exit/dispose the defer_refresh() scope.
|
||||
/// The latter method calls sync_with(_visible_rows) internally, which won't perform as well as the former if _visible_rows has changed significantly.
|
||||
_row_collection.reset(true);
|
||||
}
|
||||
} else {
|
||||
// only filter changed; row_collection will refresh accordingly
|
||||
_row_collection.filter = _columns.get_filter();
|
||||
}
|
||||
} else if (reset_sorting) {
|
||||
// only sorting changed; row_collection will refresh accordingly
|
||||
_row_collection.comparer = _columns.get_sorting_data().comparer;
|
||||
}
|
||||
}
|
||||
|
||||
internal void remove_branch_filter_root(uint key) =>
|
||||
_branch_filter_roots.Remove(key);
|
||||
|
||||
protected override void Dispose(bool disposing) =>
|
||||
Disposable.dispose(_order_rows);
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
if (e.Column is ICVOrderBookColumn col) {
|
||||
e.Handled = true;
|
||||
_columns.on_sorting(col, !KeyboardExtensions.IsShiftDown());
|
||||
var sorting_data = _columns.get_sorting_data();
|
||||
Log.Ui.info($"{nameof(OrderBookRowVM)} begin resort: {sorting_data}");
|
||||
_row_collection.comparer = sorting_data.comparer;
|
||||
} else {
|
||||
Log.Ui.warn($"{nameof(OrderBookVM)} failed to process sorting event: Column was not of type {nameof(ICVOrderBookColumn)}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void show_order_modify(OrderBookRowVM row) =>
|
||||
_bus.post(SparkTags.ShowModifyOrderTag, row.order_key);
|
||||
|
||||
internal void on_expanded_changed(OrderBookRowVM row) {
|
||||
using (_row_collection.defer_refresh(true)) {
|
||||
if (row.is_expanded) {
|
||||
_on_expanded(row);
|
||||
} else {
|
||||
_on_collapsed(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _on_expanded(OrderBookRowVM row) {
|
||||
foreach (var node in _row_collection.get_inner_nodes(row)) {
|
||||
node.value.is_visible = true;
|
||||
if (node.value.is_expanded)
|
||||
_on_expanded(node.value);
|
||||
}
|
||||
}
|
||||
|
||||
void _on_collapsed(OrderBookRowVM row) {
|
||||
foreach (var dr in _row_collection.get_descendent_rows(row))
|
||||
dr.is_visible = false;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(_resources);
|
||||
OnDisposed?.Invoke(true);
|
||||
}
|
||||
|
||||
static _GridSettingsManager _create_grid_settings_manager(
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model,
|
||||
ObservableCollection<OrderBookRowVM> rows, ICollectionView rows_view, ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, ObservableCollection<OrderBookRowVM> rows, ObservableCollection<DIYGrid.ColumnBase> columns) {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
// TODO: Remove the immutable font-weight flags when cell animation is ready
|
||||
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
@@ -267,7 +173,7 @@ namespace mbts.spark {
|
||||
col_settings_flags[c.ToString()] = GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableFontWeight;
|
||||
}
|
||||
return new _GridSettingsManager(
|
||||
owner_title, bus, notification_service, model, rows, rows_view, columns,
|
||||
owner_title, bus, notification_service, model, rows, columns,
|
||||
() => {
|
||||
var pvm = PredicateBuilderVMWrapper.create<OrderRecordTuple, OrderRecordTuplePredicate, OrderRecordTuplePredicate.PredicateTypeOneofCase>(
|
||||
OrderRecordTuplePredicateVMFactory.get_oneofs(bus, model), rp => rp.predicate_type_case);
|
||||
@@ -309,84 +215,5 @@ namespace mbts.spark {
|
||||
default: return default;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BranchFilterSource {
|
||||
readonly HashSet<uint> _orders_pass_filter = new HashSet<uint>();
|
||||
|
||||
public event Action<bool> passes_filter_changed;
|
||||
|
||||
public bool passes_filter => _orders_pass_filter.Count > 0;
|
||||
|
||||
public void set_row_passes_filter(uint key, bool value) {
|
||||
if (value) {
|
||||
if (_orders_pass_filter.Add(key) && _orders_pass_filter.Count == 1)
|
||||
passes_filter_changed?.Invoke(value);
|
||||
} else if (_orders_pass_filter.Remove(key) && _orders_pass_filter.Count == 0) {
|
||||
passes_filter_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LiveSortingColumKeys : HashSet<Columns> {
|
||||
public LiveSortingColumKeys(ObservableCollection<string> live_sorting_props) {
|
||||
_add_items(live_sorting_props);
|
||||
live_sorting_props.CollectionChanged += (s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: _add_items(e.NewItems); break;
|
||||
case NotifyCollectionChangedAction.Remove: _remove_items(e.OldItems); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
_remove_items(e.OldItems);
|
||||
_add_items(e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
Clear();
|
||||
_add_items(live_sorting_props);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static bool try_parse(string prop_name, out Columns result) {
|
||||
switch (prop_name) {
|
||||
case nameof(OrderBookRowVM.security): result = Columns.Security; break;
|
||||
case nameof(OrderBookRowVM.symbol): result = Columns.Symbol; break;
|
||||
case nameof(OrderBookRowVM.time_created): result = Columns.Time; break;
|
||||
case nameof(OrderBookRowVM.last_change): result = Columns.LastChange; break;
|
||||
case nameof(OrderBookRowVM.user): result = Columns.User; break;
|
||||
case nameof(OrderBookRowVM.account): result = Columns.Account; break;
|
||||
case nameof(OrderBookRowVM.exchange): result = Columns.Exchange; break;
|
||||
case nameof(OrderBookRowVM.time_in_force): result = Columns.TimeInForce; break;
|
||||
case nameof(OrderBookRowVM.order_type): result = Columns.OrderType; break;
|
||||
case nameof(OrderBookRowVM.order_sub_type): result = Columns.SubType; break;
|
||||
case nameof(OrderBookRowVM.side): result = Columns.Side; break;
|
||||
case nameof(OrderBookRowVM.quantity): result = Columns.Size; break;
|
||||
case nameof(OrderBookRowVM.filled_quantity): result = Columns.Filled; break;
|
||||
case nameof(OrderBookRowVM.price): result = Columns.Price; break;
|
||||
case nameof(OrderBookRowVM.avg_price): result = Columns.AvgFilled; break;
|
||||
case nameof(OrderBookRowVM.last_price): result = Columns.LastPrice; break;
|
||||
case nameof(OrderBookRowVM.status): result = Columns.Status; break;
|
||||
case nameof(OrderBookRowVM.last_msg): result = Columns.LastMessage; break;
|
||||
case nameof(OrderBookRowVM.security_key): result = Columns.SecurityKey; break;
|
||||
case nameof(OrderBookRowVM.order_key): result = Columns.OrderKey; break;
|
||||
case nameof(OrderBookRowVM.parent_order_key): result = Columns.ParentKey; break;
|
||||
default: result = default; return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _add_items(IList prop_names) {
|
||||
foreach (string pn in prop_names) {
|
||||
if (try_parse(pn, out Columns c))
|
||||
Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
void _remove_items(IList prop_names) {
|
||||
foreach (string pn in prop_names) {
|
||||
if (try_parse(pn, out Columns c))
|
||||
Remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status.value}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
|
||||
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
|
||||
@@ -62,12 +62,17 @@
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<c:DIYGrid
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="2"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows_view}"
|
||||
Rows="{Binding rows}"
|
||||
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
|
||||
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
@@ -148,5 +153,16 @@
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -121,7 +121,7 @@ namespace mbts.spark {
|
||||
_vm.expand_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
|
||||
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
@@ -137,11 +137,11 @@ namespace mbts.spark {
|
||||
}
|
||||
);
|
||||
// TODO: bind to main window's Title
|
||||
var dialog = new SimpleGridSettingsDialog { DataContext = vm };
|
||||
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
|
||||
var dialog = new SimpleGridSettingsDialog { DataContext = gs_vm };
|
||||
dialog.SetBinding(Window.TitleProperty, new Binding($"{nameof(gs_vm.instance_vm)}.{nameof(OrderBookGridSettingsVM.owner_title)}") {
|
||||
Converter = ValueConverter.create<string, string>(str => (string.IsNullOrEmpty(str) ? _default_title : str) + " Settings")
|
||||
});
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
gs_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -37,7 +36,8 @@ namespace bts.shell {
|
||||
|
||||
public override Task<IShellWindow> load(OrderBookStartupData state) {
|
||||
var settings_manager = new WindowSettingsManager<OrderBookGridSettings>(_settings_hub, state.grid_settings_wrapper(),
|
||||
us => us.order_book_grid, (us, s) => us.with(order_book_grid: s));
|
||||
us => us.order_book_grid ?? OrderBookStartupData.DefaultGrid,
|
||||
(us, s) => us.with(order_book_grid: s));
|
||||
var w = new OrderBookWindow(_model, this, _bus, _notification_service, state, SparkCommands.ShowOrders.icon, settings_manager.as_non_shared());
|
||||
return Task.FromResult<IShellWindow>(w);
|
||||
}
|
||||
@@ -149,7 +149,7 @@ namespace bts.shell {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
public class FilterStates {
|
||||
public class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
@@ -190,6 +190,28 @@ namespace bts.shell {
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -225,4 +247,4 @@ namespace bts.shell {
|
||||
EqualityComparer.safe_aggregate_hash(predicate_animations));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate LeanOrderTemplate { get; set; }
|
||||
public DataTemplate JoinOrderTemplate { get; set; }
|
||||
public DataTemplate TopOrderTemplate { get; set; }
|
||||
public DataTemplate CoverOrderTemplate { get; set; }
|
||||
public DataTemplate CoverOrderTemplate { get; set; }
|
||||
public DataTemplate OffsetOrderTemplate { get; set; }
|
||||
public DataTemplate TriggerOrderTemplate { get; set; }
|
||||
public DataTemplate IcebergOrderTemplate { get; set; }
|
||||
public DataTemplate SpreaderTemplate { get; set; }
|
||||
@@ -42,6 +43,7 @@ namespace mbts.ui.spark {
|
||||
case CustomOrderDefinition.CustomOneofCase.Join: return JoinOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Top: return TopOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover: return CoverOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return OffsetOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return TriggerOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg: return IcebergOrderTemplate;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return SpreaderTemplate;
|
||||
@@ -51,4 +53,5 @@ namespace mbts.ui.spark {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,5 +42,6 @@ namespace mbts.spark {
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as OrderRecordTuple);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
public override string ToString() => $"order:{order.Key}, sec:{security_md.display_name()}";
|
||||
}
|
||||
}
|
||||
@@ -7,82 +7,99 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
class OrderRecordTupleSource : IObservable<IIncrementalUpdate<OrderRecordTuple, uint>>, IDisposable {
|
||||
// returns a ref-count Observable.Create-wrapper that initializes/destroys the [hot-observable] OrderRecordTupleSource.
|
||||
// (initializes when the first subscription is made, and destroys when the last subscription is disposed).
|
||||
public static IObservable<IIncrementalUpdate<OrderRecordTuple, uint>> create_observable(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders) {
|
||||
OrderRecordTupleSource source = null;
|
||||
int ref_count = 0;
|
||||
return Observable.Create<IIncrementalUpdate<OrderRecordTuple, uint>>(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 OrderRecordTupleSource(securities, orders);
|
||||
}
|
||||
IDisposable sub = source.Subscribe(observer);
|
||||
return Disposable.Create(() => {
|
||||
Disposable.dispose_once(ref sub);
|
||||
if (Interlocked.Decrement(ref ref_count) == 0)
|
||||
Disposable.dispose_once(ref source);
|
||||
});
|
||||
});
|
||||
string fmt_assert_msg(string msg) => $"{nameof(OrderRecordTupleSource)}.{nameof(create_observable)}: {msg}";
|
||||
}
|
||||
|
||||
class OrderRecordTupleSource : IDisposable {
|
||||
readonly object _lock = new object();
|
||||
// subjects
|
||||
readonly Subject<IIncrementalUpdate<OrderRecordTuple, uint>> _subject = new Subject<IIncrementalUpdate<OrderRecordTuple, uint>>();
|
||||
readonly Subject<IIncrementalTableUpdateTransform<OrderRecordTuple, uint>> _table_subject = new Subject<IIncrementalTableUpdateTransform<OrderRecordTuple, uint>>();
|
||||
readonly Subject<IIncrementalUpdate<SecurityMD, uint>> _order_securities_subject = new Subject<IIncrementalUpdate<SecurityMD, uint>>();
|
||||
// data cache
|
||||
readonly Dictionary<uint, OrderRecordTuple> _state = new Dictionary<uint, OrderRecordTuple>();
|
||||
readonly SetMap<uint, uint> _security_orders = new SetMap<uint, uint>();
|
||||
readonly IDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>(0);
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
|
||||
IDisposable _dispose;
|
||||
object _lock = new object();
|
||||
|
||||
OrderRecordTupleSource(IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities, IObservable<TableUpdate<OrderRecordImmutable, uint>> orders) =>
|
||||
public OrderRecordTupleSource(IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities, IObservable<TableUpdate<OrderRecordImmutable, uint>> orders) =>
|
||||
_dispose = Disposable.Create(
|
||||
securities.Subscribe(_update_securities),
|
||||
orders.AddRemoveOnly().Subscribe(_update_orders));
|
||||
orders.Subscribe(_update_orders));
|
||||
|
||||
public IDisposable Subscribe(IObserver<IIncrementalUpdate<OrderRecordTuple, uint>> observer) {
|
||||
lock (_lock) {
|
||||
observer.OnNext(new IncrementalUpdate<OrderRecordTuple, uint>(new Dictionary<uint, OrderRecordTuple>(_state), new Dictionary<uint, OrderRecordTuple>(0)));
|
||||
return _subject.Subscribe(observer);
|
||||
}
|
||||
}
|
||||
public IObservable<IIncrementalUpdate<OrderRecordTuple, uint>> subscribe_incremental_update() =>
|
||||
Observable.Create<IIncrementalUpdate<OrderRecordTuple, uint>>(observer => {
|
||||
lock (_lock) {
|
||||
observer.OnNext(new IncrementalUpdate<OrderRecordTuple, uint>(new Dictionary<uint, OrderRecordTuple>(_state), new Dictionary<uint, OrderRecordTuple>(0)));
|
||||
return _subject.Subscribe(observer);
|
||||
}
|
||||
});
|
||||
|
||||
public IObservable<IIncrementalTableUpdateTransform<OrderRecordTuple, uint>> subscribe_incremental_table_update() =>
|
||||
Observable.Create<IIncrementalTableUpdateTransform<OrderRecordTuple, uint>>(observer => {
|
||||
lock (_lock) {
|
||||
observer.OnNext(IncrementalTableUpdateTranform<OrderRecordTuple, uint>.init(_state.Values));
|
||||
return _table_subject.Subscribe(observer);
|
||||
}
|
||||
});
|
||||
|
||||
public IObservable<IIncrementalUpdate<SecurityMD, uint>> subscribe_order_securities() =>
|
||||
Observable.Create<IIncrementalUpdate<SecurityMD, uint>>(observer => {
|
||||
lock (_lock) {
|
||||
var init = new Dictionary<uint, SecurityMD>();
|
||||
foreach (uint sec_key in _security_orders.Keys) {
|
||||
if (_security_md_cache.TryGetValue(sec_key, out SecurityMD smd))
|
||||
init.Add(sec_key, smd);
|
||||
}
|
||||
observer.OnNext(new IncrementalUpdate<SecurityMD, uint>(init, new Dictionary<uint, SecurityMD>(0)));
|
||||
return _order_securities_subject.Subscribe(observer);
|
||||
}
|
||||
});
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
void _update_orders(LegacyTableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
void _update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
lock (_lock) {
|
||||
Dictionary<uint, OrderRecordTuple> removed = new Dictionary<uint, OrderRecordTuple>(), added = new Dictionary<uint, OrderRecordTuple>();
|
||||
Dictionary<uint, SecurityMD> added_securities = new Dictionary<uint, SecurityMD>(), removed_securities = new Dictionary<uint, SecurityMD>();
|
||||
foreach (OrderRecordImmutable order in orders.removed) {
|
||||
uint sec_key = order.security_key();
|
||||
OrderRecordTuple old_ft = new OrderRecordTuple(order, _get_security_md(sec_key));
|
||||
removed.Add(old_ft.order.Key, old_ft);
|
||||
_security_orders.remove(sec_key, order.Key);
|
||||
if (_security_orders.removes_key(sec_key, order.Key) && old_ft.security_md.HasValue)
|
||||
removed_securities.Add(sec_key, old_ft.security_md.Value);
|
||||
}
|
||||
foreach (Change<OrderRecordImmutable> ch in orders.changes) {
|
||||
// orders cannot change securities; ch.New can use the same security_md as ch.Old
|
||||
var sec_md = _get_security_md(ch.Old.security_key());
|
||||
removed.Add(ch.Old.Key, new OrderRecordTuple(ch.Old, sec_md));
|
||||
added.Add(ch.New.Key, new OrderRecordTuple(ch.New, sec_md));
|
||||
}
|
||||
foreach (OrderRecordImmutable order in orders.added) {
|
||||
uint sec_key = order.security_key();
|
||||
OrderRecordTuple new_ort = new OrderRecordTuple(order, _get_security_md(sec_key));
|
||||
added.Add(new_ort.order.Key, new_ort);
|
||||
_security_orders.add(sec_key, order.Key);
|
||||
if (_security_orders.adds_key(sec_key, order.Key) && new_ort.security_md.HasValue)
|
||||
added_securities.Add(sec_key, new_ort.security_md.Value);
|
||||
}
|
||||
_orders_cache = orders.state;
|
||||
_apply_changes(added, removed);
|
||||
_apply_changes(added, removed, added_securities, removed_securities);
|
||||
}
|
||||
}
|
||||
|
||||
void _update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
lock (_lock) {
|
||||
Dictionary<uint, OrderRecordTuple> added = new Dictionary<uint, OrderRecordTuple>(), removed = new Dictionary<uint, OrderRecordTuple>();
|
||||
Dictionary<uint, SecurityMD> added_securities = new Dictionary<uint, SecurityMD>(), removed_securities = new Dictionary<uint, SecurityMD>();
|
||||
_security_md_cache.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
foreach (var k in removed_keys) {
|
||||
/// Warning: <see cref="_security_md_cache"/> must NOT be reassigned with <see cref="securities"/>; the latter is always the same object.
|
||||
/// This would mean that the <see cref="GlobalExtensions.difference{T}(IEnumerable{T}, IEnumerable{T}, out IEnumerable{T}, out IEnumerable{T})"/>
|
||||
/// call above would literally be calculating the difference between the keys of the same dictionary!
|
||||
foreach (var k in removed_keys.ToList()) {
|
||||
if (_security_orders.try_lookup(k, out ISet<uint> order_keys)) {
|
||||
IEnumerable<OrderRecordImmutable> orders = order_keys.Select(fk => _orders_cache[fk]);
|
||||
SecurityMD old_sec_md = _security_md_cache[k];
|
||||
removed_securities.Add(k, old_sec_md);
|
||||
IEnumerable<OrderRecordImmutable> orders = order_keys.Select(fk => _orders_cache[fk]);
|
||||
foreach (OrderRecordImmutable order in orders) {
|
||||
OrderRecordTuple old_ft = new OrderRecordTuple(order, old_sec_md);
|
||||
removed.Add(old_ft.order.Key, old_ft);
|
||||
@@ -90,11 +107,13 @@ namespace mbts.spark {
|
||||
added.Add(new_ft.order.Key, new_ft);
|
||||
}
|
||||
}
|
||||
_security_md_cache.Remove(k);
|
||||
}
|
||||
foreach (var k in added_keys) {
|
||||
foreach (var k in added_keys.ToList()) {
|
||||
SecurityMD new_sec_md = securities[k];
|
||||
if (_security_orders.try_lookup(k, out ISet<uint> order_keys)) {
|
||||
added_securities.Add(k, new_sec_md);
|
||||
IEnumerable<OrderRecordImmutable> orders = order_keys.Select(fk => _orders_cache[fk]);
|
||||
SecurityMD new_sec_md = securities[k];
|
||||
foreach (OrderRecordImmutable order in orders) {
|
||||
OrderRecordTuple old_ft = new OrderRecordTuple(order, null);
|
||||
removed.Add(old_ft.order.Key, old_ft);
|
||||
@@ -102,9 +121,9 @@ namespace mbts.spark {
|
||||
added.Add(new_ft.order.Key, new_ft);
|
||||
}
|
||||
}
|
||||
_security_md_cache.Add(k, new_sec_md);
|
||||
}
|
||||
_security_md_cache = securities;
|
||||
_apply_changes(added, removed);
|
||||
_apply_changes(added, removed, added_securities, removed_securities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,12 +133,15 @@ namespace mbts.spark {
|
||||
OrderRecordImmutable? _get_order(uint key) =>
|
||||
_orders_cache.TryGetValue(key, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?();
|
||||
|
||||
void _apply_changes(Dictionary<uint, OrderRecordTuple> added, Dictionary<uint, OrderRecordTuple> removed) {
|
||||
void _apply_changes(Dictionary<uint, OrderRecordTuple> added, Dictionary<uint, OrderRecordTuple> removed, Dictionary<uint, SecurityMD> added_securities, Dictionary<uint, SecurityMD> removed_securities) {
|
||||
foreach (var kv in removed)
|
||||
_state.Remove(kv.Key);
|
||||
foreach (var kv in added)
|
||||
_state.Add(kv.Key, kv.Value);
|
||||
_subject.OnNext(new IncrementalUpdate<OrderRecordTuple, uint>(added, removed));
|
||||
var update = new IncrementalUpdate<OrderRecordTuple, uint>(added, removed);
|
||||
_subject.OnNext(update);
|
||||
_table_subject.OnNext(update.as_table());
|
||||
_order_securities_subject.OnNext(new IncrementalUpdate<SecurityMD, uint>(added_securities, removed_securities));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
[Export]
|
||||
public class DynamicOrderHelper
|
||||
{
|
||||
readonly RegisteredDynamicOrderTypes _dynamic_types;
|
||||
[ImportingConstructor]
|
||||
private DynamicOrderHelper(OrderService order_service)
|
||||
{
|
||||
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
|
||||
}
|
||||
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory)
|
||||
{
|
||||
foreach (var d in _dynamic_types.DynamicOrderDesc)
|
||||
{
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
|
||||
//Add only the descriptor here for reflection later
|
||||
foreach(var p in d.ParamDesc)
|
||||
dorder.Dynamic.OrderParams.Add( p.ParamName, new DynamicOrderParam() );
|
||||
defs.Add(new CustomOrderDefinitionImmutable(dorder));
|
||||
}
|
||||
}
|
||||
|
||||
internal IDictionary<string, DynamicOrderParamVM> get_parameters(string dyntype)
|
||||
{
|
||||
Dictionary<string, DynamicOrderParamVM> par = new Dictionary<string, DynamicOrderParamVM>();
|
||||
var order = _dynamic_types.DynamicOrderDesc.FirstOrDefault(x => x.DynamicOrderType == dyntype);
|
||||
if (order != null)
|
||||
{
|
||||
foreach (var p in order.ParamDesc)
|
||||
par[p.ParamName] = new DynamicOrderParamVM() { param = new DynamicOrderParam(), descriptor = p };
|
||||
}
|
||||
return par;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
|
||||
class DynamicOrderParamVM : mbts.ui.shell.ViewModelBase
|
||||
{
|
||||
public DynamicOrderParamVM() { }
|
||||
public DynamicOrderParamDescriptor descriptor { get; set; }
|
||||
public DynamicOrderParam param { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using bts.api.orderman;
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.spark
|
||||
{
|
||||
class DynamicOrderParamTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate BoolTemplate { get; set; }
|
||||
public DataTemplate DoubleTemplate { get; set; }
|
||||
public DataTemplate IntTemplate { get; set; }
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
public DataTemplate OrderDefTemplate { get; set; }
|
||||
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
|
||||
public override DataTemplate SelectTemplate(object item, DependencyObject container)
|
||||
{
|
||||
var vm = item as DynamicOrderParamVM;
|
||||
if (vm != null)
|
||||
{
|
||||
switch (vm.descriptor.ParamType.DescriptorCase)
|
||||
{
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive:
|
||||
{
|
||||
switch (vm.descriptor.ParamType.Primitive)
|
||||
{
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return BoolTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DoubleTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return IntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return UIntTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return StringTemplate;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: return OrderDefTemplate;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: return EnumTemplate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,47 +7,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using static bts.api.orderman.CustomOrderDefinition;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
public static class DynamicOrderHelper
|
||||
{
|
||||
static Lazy<bts.config.dynamic_orders.DynamicOrderList> _list = new Lazy<bts.config.dynamic_orders.DynamicOrderList>(
|
||||
() => mbts.config.Configuration.get<bts.config.dynamic_orders.DynamicOrderList>(), true);
|
||||
public static void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
Func<CustomOrderDefinition.CustomOneofCase, CustomOrderDefinition> factory)
|
||||
{
|
||||
var dyntypes = _list.Value;
|
||||
foreach (var d in dyntypes.dynamic_orders)
|
||||
{
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
dorder.Dynamic.DynamicOrderType = d.type_name;
|
||||
foreach (var p in d.parameter_keys)
|
||||
dorder.Dynamic.Parameters.Add(p, string.Empty);
|
||||
defs.Add(new CustomOrderDefinitionImmutable(dorder));
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<NameValueVM> get_parameters(string dyntype)
|
||||
{
|
||||
List<NameValueVM> par = new List<NameValueVM>();
|
||||
var order = _list.Value.dynamic_orders.FirstOrDefault(x => x.type_name == dyntype);
|
||||
if (order != null)
|
||||
par.AddRange(order.parameter_keys.Select(x => new NameValueVM() { name = x, value = string.Empty }));
|
||||
return par;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NameValueVM : mbts.ui.shell.ViewModelBase
|
||||
{
|
||||
public NameValueVM() { }
|
||||
public string name { get; set; }
|
||||
public string value { get; set; }
|
||||
}
|
||||
|
||||
static partial class OrderTypeVMFactory
|
||||
{
|
||||
@@ -75,7 +41,8 @@ namespace mbts.spark
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
string _dyn_type;
|
||||
List<NameValueVM> _parameters;
|
||||
IDictionary<string, DynamicOrderParamVM> _parameters;
|
||||
IEnumerable<string> _order_def_tags;
|
||||
|
||||
public DynamicOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string dyn_type, string id, string display_name)
|
||||
: this(model, security_md_source, dyn_type, security_md_source.Select(smd =>
|
||||
@@ -87,7 +54,9 @@ namespace mbts.spark
|
||||
{
|
||||
_security_md_source = security_md_source;
|
||||
_dyn_type = dyn_type;
|
||||
_parameters = DynamicOrderHelper.get_parameters(dyn_type);
|
||||
_parameters = model.dynamic_helper.get_parameters(dyn_type);
|
||||
_order_def_tags = _parameters.Values.Where(x => x.descriptor.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
|
||||
x.descriptor.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef).Select(xx=>xx.descriptor.ParamName);
|
||||
_dispose = Disposable.Create(
|
||||
security_md_source.OnDispatcher()
|
||||
.Subscribe(smd => _security = smd.security),
|
||||
@@ -111,7 +80,12 @@ namespace mbts.spark
|
||||
|
||||
public override string type_name => _dyn_type;
|
||||
|
||||
public IEnumerable<NameValueVM> parameters => _parameters;
|
||||
public IEnumerable<DynamicOrderParamVM> parameters => _parameters.Values.Where(x =>
|
||||
{
|
||||
return x.descriptor.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
|
||||
(x.descriptor.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
|
||||
x.descriptor.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
|
||||
});
|
||||
|
||||
public override void assign_to(CustomOrderDefinition value)
|
||||
{
|
||||
@@ -122,16 +96,25 @@ namespace mbts.spark
|
||||
{
|
||||
var i = factory.dynamic_order();
|
||||
i.DynamicOrderType = _dyn_type;
|
||||
set_parameter_dictionary(i.Parameters);
|
||||
child_order.assign_to(i.Child);
|
||||
set_parameter_dictionary(i.OrderParams);
|
||||
value.Dynamic = i;
|
||||
}
|
||||
}
|
||||
|
||||
void set_parameter_dictionary(Google.Protobuf.Collections.MapField<string, string> par)
|
||||
void set_parameter_dictionary(Google.Protobuf.Collections.MapField<string, DynamicOrderParam> par)
|
||||
{
|
||||
foreach(var vm in _parameters)
|
||||
par[vm.name] = vm.value ?? string.Empty;
|
||||
foreach(var vm in _parameters.Values)
|
||||
par[vm.descriptor.ParamName] = vm.param;
|
||||
|
||||
if (child_order != null)
|
||||
{
|
||||
var newkid = new DynamicOrderParam();
|
||||
var cust = new CustomOrderDefinition();
|
||||
newkid.AsOrderDef = cust;
|
||||
child_order.assign_to(newkid.AsOrderDef);
|
||||
foreach(var child in _order_def_tags)
|
||||
par[child] = newkid;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
@@ -204,8 +187,7 @@ namespace mbts.spark
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,33 +22,14 @@ namespace mbts.spark
|
||||
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<OrderTemplateHeader>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
|
||||
|
||||
var hedgetype = CustomOneofCase.Lean;
|
||||
if(!child_order_types.Any(t => t.key.type == 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<OrderTemplateHeader>(new OrderTemplateHeader(hedgetype), true);
|
||||
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(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<OrderTemplateHeader> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
|
||||
public readonly InlineUserProperty<OrderTemplateHeader> selected_hedge_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> 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;
|
||||
|
||||
public readonly InlineUserProperty<bts.spark.QuantityMeasure> join_threshold;
|
||||
}
|
||||
|
||||
class JoinOrderVM : OrderTypeVMBase<JoinOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
@@ -61,23 +42,7 @@ namespace mbts.spark
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _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, string id, string display_name)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
@@ -100,30 +65,11 @@ namespace mbts.spark
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value.key.type);
|
||||
}
|
||||
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
|
||||
{
|
||||
_select_hedge_order(p.selected_hedge_order_type.value.key.type);
|
||||
}
|
||||
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();
|
||||
})
|
||||
@@ -135,19 +81,12 @@ namespace mbts.spark
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -161,16 +100,6 @@ namespace mbts.spark
|
||||
return _child_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> hedge_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
@@ -196,45 +125,17 @@ namespace mbts.spark
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -252,35 +153,13 @@ namespace mbts.spark
|
||||
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.key.type :
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace mbts.spark
|
||||
cross_threshold = new InlineUserProperty<bts.spark.QuantityMeasure>(
|
||||
new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1}, false);
|
||||
max_chase_ticks = new InlineUserProperty<uint>(2, false);
|
||||
trade_toward_behavior = new InlineUserProperty<LeanOrderDefinition.Types.OnToward>(LeanOrderDefinition.Types.OnToward.Cancel, false);
|
||||
trade_toward_behavior = new InlineUserProperty<LeanOrderDefinition.Types.OnToward>(LeanOrderDefinition.Types.OnToward.NoChange, false);
|
||||
}
|
||||
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
|
||||
@@ -47,7 +47,7 @@ namespace mbts.spark
|
||||
|
||||
public bts.spark.QuantityMeasure cross_threshold { get; set; } = new bts.spark.QuantityMeasure() { Ratio = 1.0, Absolute = 1 };
|
||||
public uint max_chase_ticks { get; set; } = 2;
|
||||
public LeanOrderDefinition.Types.OnToward trade_toward_behavior { get; set; } = LeanOrderDefinition.Types.OnToward.Cancel;
|
||||
public LeanOrderDefinition.Types.OnToward trade_toward_behavior { get; set; } = LeanOrderDefinition.Types.OnToward.NoChange;
|
||||
|
||||
public LeanOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
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 InlineOffsetOrderProperties
|
||||
{
|
||||
public InlineOffsetOrderProperties (OrderTemplateHeader selected_child_order_type, IReadOnlyCollection<OrderTemplateHeader> 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<OrderTemplateHeader>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
|
||||
tick_offset_on_deploy = new InlineUserProperty<uint>(0, false);
|
||||
}
|
||||
public readonly InlineUserProperty<OrderTemplateHeader> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
|
||||
|
||||
public readonly InlineUserProperty<uint> tick_offset_on_deploy;
|
||||
}
|
||||
|
||||
class OffsetOrderVM : OrderTypeVMBase<OffsetOrderDefinition>, IHierarchicalOrderTypeVM, IDisposable
|
||||
{
|
||||
readonly IObservable<SecurityMD> _security_md_source;
|
||||
SecurityImmutable? _security;
|
||||
ObservableCollection<OrderTemplateHeader> _order_types = new ObservableCollection<OrderTemplateHeader>();
|
||||
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _child_order_choices;
|
||||
InlineOffsetOrderProperties _inline_props;
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
public uint tick_offset_on_deploy { get; set; } = 0;
|
||||
|
||||
public OffsetOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
new InlineOffsetOrderProperties(new OrderTemplateHeader(model.factory.default_order_type(smd.security)), model.factory.default_order_types(smd.security).Select(t => new OrderTemplateHeader(t)).ToList())), id, display_name) { }
|
||||
|
||||
public OffsetOrderVM(OrdersModel model, IObservable<SecurityMD> security_md_source,
|
||||
IObservable<InlineOffsetOrderProperties> inline_properties, string id, string display_name) : base(model, id, display_name)
|
||||
{
|
||||
_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.key.type);
|
||||
}
|
||||
if (p.tick_offset_on_deploy.force_override)
|
||||
{
|
||||
tick_offset_on_deploy = p.tick_offset_on_deploy.value;
|
||||
RaisePropertyChanged(nameof(tick_offset_on_deploy));
|
||||
}
|
||||
_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.offset_order();
|
||||
i.TickOffsetOnDeploy = tick_offset_on_deploy;
|
||||
child_order.assign_to(i.Child);
|
||||
value.Offset = i;
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> child_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_child_order_choices == null)
|
||||
_child_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, 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.key.type :
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ namespace mbts.spark {
|
||||
public static IOrderTypeVM join(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new JoinOrderVM(model, security_md_source, id, display_name);
|
||||
public static IOrderTypeVM top(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new TopOrderVM(model, security_md_source, id, display_name);
|
||||
public static IOrderTypeVM cover(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new CoverOrderVM(model, security_md_source, id, display_name);
|
||||
public static IOrderTypeVM offset(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new OffsetOrderVM(model, security_md_source, id, display_name);
|
||||
public static IOrderTypeVM trigger(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new TriggerOrderVM(model, security_md_source, id, display_name);
|
||||
public static IOrderTypeVM iceberg(OrdersModel model, IObservable<SecurityMD> security_md_source, string id, string display_name) => new IcebergOrderVM(model, security_md_source, id, display_name);
|
||||
public static IOrderTypeVM spreader(OrdersModel model, IObservable<SecurityImmutable> security_source, string id, string display_name) => new SpreaderOrderVM(model, security_source, id, display_name);
|
||||
@@ -38,10 +39,11 @@ namespace mbts.spark {
|
||||
case CustomOneofCase.Join: return join(model, security_md_source, id, display_name);
|
||||
case CustomOneofCase.Top: return top(model, security_md_source, id, display_name);
|
||||
case CustomOneofCase.Cover: return cover(model, security_md_source, id, display_name);
|
||||
case CustomOneofCase.Offset: return offset(model, security_md_source, id, display_name);
|
||||
case CustomOneofCase.Trigger: return trigger(model, security_md_source, id, display_name);
|
||||
case CustomOneofCase.Iceberg: return iceberg(model, security_md_source, id, display_name);
|
||||
case CustomOneofCase.Spreader: return spreader(model, security_md_source.Select(smd => smd.security), id, display_name);
|
||||
case CustomOneofCase.Dynamic: return dynamic(model, security_md_source, dynamic_type, id, display_name);
|
||||
case CustomOneofCase.Dynamic: return dynamic(model, security_md_source, dynamic_type, id, /*display_name*/ dynamic_type);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,30 +23,15 @@ namespace mbts.spark
|
||||
this.selected_child_order_type = new InlineUserProperty<OrderTemplateHeader>(selected_child_order_type, true);
|
||||
this.child_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(child_order_types, true);
|
||||
|
||||
var hedgetype = CustomOneofCase.Lean;
|
||||
if(!child_order_types.Any(ct => ct.key.type == hedgetype))
|
||||
hedgetype = CustomOneofCase.Limit;
|
||||
this.selected_hedge_order_type = new InlineUserProperty<OrderTemplateHeader>(new OrderTemplateHeader(hedgetype), true);
|
||||
this.hedge_order_types = new InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>>(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<OrderTemplateHeader> selected_child_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> child_order_types;
|
||||
public readonly InlineUserProperty<OrderTemplateHeader> selected_hedge_order_type;
|
||||
public readonly InlineUserProperty<IReadOnlyCollection<OrderTemplateHeader>> 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
|
||||
@@ -59,23 +44,8 @@ namespace mbts.spark
|
||||
IOrderTypeVM _child_order;
|
||||
IDisposable _dispose, _child_order_errors_sub;
|
||||
|
||||
ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader> _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, string id, string display_name)
|
||||
: this(model, security_md_source, security_md_source.Select(smd =>
|
||||
@@ -100,10 +70,6 @@ namespace mbts.spark
|
||||
{
|
||||
_select_child_order(p.selected_child_order_type.value.key.type);
|
||||
}
|
||||
if (_hedge_order_choices != null && (_hedge_order == null || p.selected_hedge_order_type.force_override))
|
||||
{
|
||||
_select_hedge_order(p.selected_hedge_order_type.value.key.type);
|
||||
}
|
||||
if (p.watch_level_tick_offset.force_override)
|
||||
{
|
||||
watch_level_tick_offset = p.watch_level_tick_offset.value;
|
||||
@@ -114,16 +80,6 @@ namespace mbts.spark
|
||||
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();
|
||||
})
|
||||
@@ -135,19 +91,13 @@ namespace mbts.spark
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -162,16 +112,6 @@ namespace mbts.spark
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<IOrderTypeVM> hedge_order_types
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hedge_order_choices == null)
|
||||
_hedge_order_choices = new ObservableAutoWrapper<IOrderTypeVM, OrderTemplateHeader>(_order_types, ot => create(ot.key.type, ot.key.name, ot.display_name, model, _security_md_source));
|
||||
return _hedge_order_choices;
|
||||
}
|
||||
}
|
||||
|
||||
public IOrderTypeVM child_order
|
||||
{
|
||||
get
|
||||
@@ -196,45 +136,18 @@ namespace mbts.spark
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -254,33 +167,11 @@ namespace mbts.spark
|
||||
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.key.type :
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,19 +26,22 @@ namespace mbts.spark {
|
||||
readonly PositionService _position_service;
|
||||
readonly SpreaderModel _spreader;
|
||||
readonly ErrorReporter _error;
|
||||
readonly DynamicOrderHelper _dynamic_helper;
|
||||
readonly OrderActivitySummarizer _activity;
|
||||
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
|
||||
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error, AdminModel admin_model) {
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service,
|
||||
SpreaderModel spreader, ErrorReporter error, DynamicOrderHelper dynamic_helper, AdminModel admin_model) {
|
||||
_bus = bus;
|
||||
_factory = factory;
|
||||
_service = service;
|
||||
_position_service = position_service;
|
||||
_spreader = spreader;
|
||||
_error = error;
|
||||
_dynamic_helper = dynamic_helper;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
@@ -48,7 +51,10 @@ namespace mbts.spark {
|
||||
ImmutableDictionary<uint,OrderRecordImmutable>.Empty,
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
on_order_tuples = OrderRecordTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders);
|
||||
var order_record_tuple_source = new OrderRecordTupleSource(bus.subscribe(SparkTags.SecuritiesTag), on_orders);
|
||||
on_order_tuples = order_record_tuple_source.subscribe_incremental_update();
|
||||
on_order_tuples_table = order_record_tuple_source.subscribe_incremental_table_update();
|
||||
on_order_securities = order_record_tuple_source.subscribe_order_securities();
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
|
||||
@@ -57,6 +63,7 @@ namespace mbts.spark {
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.AddRemoveOnly().Subscribe(_security_orders_demuxer),
|
||||
on_positions.AddRemoveOnly().Subscribe(_security_positions_demuxer),
|
||||
order_record_tuple_source,
|
||||
_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()),
|
||||
@@ -65,11 +72,14 @@ namespace mbts.spark {
|
||||
);
|
||||
}
|
||||
|
||||
#region Output
|
||||
#region Output
|
||||
public OrderFactory factory => _factory;
|
||||
public SpreaderModel spreader => _spreader;
|
||||
public DynamicOrderHelper dynamic_helper => _dynamic_helper;
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
|
||||
public IObservable<IIncrementalUpdate<OrderRecordTuple, uint>> on_order_tuples { get; }
|
||||
public IObservable<IIncrementalTableUpdateTransform<OrderRecordTuple, uint>> on_order_tuples_table { get; }
|
||||
public IObservable<IIncrementalUpdate<SecurityMD, uint>> on_order_securities { get; }
|
||||
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
|
||||
public IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
|
||||
@@ -335,4 +345,4 @@ namespace mbts.spark {
|
||||
public TableUpdateTransform<OrderRecordImmutable, uint> last_update { get; set; } = new TableUpdateTransform<OrderRecordImmutable, uint>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,9 +82,4 @@
|
||||
</ItemGroup>
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
|
||||
</Target>
|
||||
<Target Name="copy_defaults" AfterTargets="PostBuildEvent">
|
||||
<!-- this feels like the wrong place to copy defaults but it works alright -->
|
||||
<!-- <Message Importance="High" Text="copy defaults :: @(ConfigDefaults)" /> -->
|
||||
<Copy SourceFiles="@(ConfigDefaults)" DestinationFolder="$(OutputPath)\defaults\" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -77,7 +77,7 @@ namespace bts.spark
|
||||
SparkCommand.create("Manual", Icons.Browser, SparkTags.ShowUri, new Uri("https://www.bluetradingsystems.com/documentation/"));
|
||||
|
||||
public static SparkCommand ShowReleaseNotes =>
|
||||
SparkCommand.create("Release Notes", Icons.Browser, SparkTags.ShowUri, new Uri("bts://files/release_notes.txt"));
|
||||
SparkCommand.create("Release Notes", Icons.Browser, SparkTags.ShowUri, new Uri("bts://docs/release_notes/"));
|
||||
|
||||
public static SparkCommand DoNothing =>
|
||||
SparkCommand.create("Nothing", Icons.Support, SparkTags.NotImplemented);
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace mbts.collections {
|
||||
public int Count => _dictionary.Count;
|
||||
public object SyncRoot => null;
|
||||
public bool IsSynchronized => false;
|
||||
public IEnumerable<TKey> Keys => _dictionary.Keys;
|
||||
public ISet<TElement> this[TKey key] => _dictionary[key];
|
||||
|
||||
public void CopyTo(Array array, int index) =>
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace mbts.ui {
|
||||
public bool CanExecute(object parameter) {
|
||||
if (!allow_concurrent_executions && _n_executing > 0)
|
||||
return false;
|
||||
else return _can_execute(ObjectHelper.try_convert<T>(parameter));
|
||||
else return _can_execute(ConvertUtils.try_convert<T>(parameter));
|
||||
}
|
||||
|
||||
public async Task Execute(object parameter) {
|
||||
@@ -51,7 +51,7 @@ namespace mbts.ui {
|
||||
if (!allow_concurrent_executions)
|
||||
RaiseCanExecuteChanged();
|
||||
}
|
||||
await _execute(ObjectHelper.try_convert<T>(parameter));
|
||||
await _execute(ConvertUtils.try_convert<T>(parameter));
|
||||
if (--_n_executing == 0) {
|
||||
_raise_is_executing_changed();
|
||||
if (!allow_concurrent_executions)
|
||||
|
||||
@@ -27,13 +27,13 @@ namespace mbts.ui
|
||||
if (_can_execute == null)
|
||||
return true;
|
||||
else
|
||||
return _can_execute(ObjectHelper.try_convert<T>(parameter));
|
||||
return _can_execute(ConvertUtils.try_convert<T>(parameter));
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public void Execute(object parameter) =>
|
||||
_execute(ObjectHelper.try_convert<T>(parameter));
|
||||
_execute(ConvertUtils.try_convert<T>(parameter));
|
||||
}
|
||||
|
||||
public class DelegateCommand : DelegateCommand<object> {
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace mbts.ui {
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object parameter) => _can_execute;
|
||||
public void Execute(object parameter) => _execute(ObjectHelper.try_convert<T>(parameter));
|
||||
public void Execute(object parameter) => _execute(ConvertUtils.try_convert<T>(parameter));
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
|
||||
@@ -364,7 +364,7 @@ namespace System.Collections.Generic {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses <see cref="ObjectHelper.try_convert{TResult}(object)"/> to convert the elements of an <see cref="IEnumerable"/> to the specified type.
|
||||
/// Uses <see cref="ConvertUtils.try_convert{TResult}(object)"/> to convert the elements of an <see cref="IEnumerable"/> to the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type to convert the elements of source to.</typeparam>
|
||||
/// <param name="source">The <see cref="IEnumerable"/> that contains the elements to be cast to type <typeparamref name="TResult"/></param>
|
||||
@@ -373,7 +373,7 @@ namespace System.Collections.Generic {
|
||||
public static IEnumerable<TResult> convert<TResult>(this IEnumerable source) {
|
||||
ExceptionAssert.Argument.NotNull(source, nameof(source));
|
||||
foreach (object obj in source)
|
||||
yield return ObjectHelper.try_convert<TResult>(obj);
|
||||
yield return ConvertUtils.try_convert<TResult>(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,95 +427,6 @@ namespace System.Collections.Generic {
|
||||
}
|
||||
}
|
||||
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
#region TryGetValue
|
||||
public static bool TryGetValue<K, VK1, VK2, V>(this Dictionary<K, Dictionary<VK1, Dictionary<VK2, V>>> target, K k1, VK1 k2, VK2 k3, out V value) {
|
||||
if (target.TryGetValue(k1, out Dictionary<VK1, Dictionary<VK2, V>> d1) && d1.TryGetValue(k2, out Dictionary<VK2, V> d2))
|
||||
return d2.TryGetValue(k3, out value);
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetValue<K, VK, V, VKD>(this IDictionary<K, VKD> target, K k1, VK k2, out V value) where VKD : IDictionary<VK, V> {
|
||||
if (target.TryGetValue(k1, out VKD d))
|
||||
return d.TryGetValue(k2, out value);
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static void AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> add_value_factory, Action<TKey, TValue> update_value) {
|
||||
if (!dictionary.TryGetValue(key, out TValue value))
|
||||
dictionary.Add(key, add_value_factory(key));
|
||||
else update_value(key, value);
|
||||
}
|
||||
|
||||
public static void AddOrUpdate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> add_value_factory, Func<TKey, TValue, TValue> update_value) {
|
||||
if (!dictionary.TryGetValue(key, out TValue value))
|
||||
dictionary.Add(key, add_value_factory(key));
|
||||
dictionary[key] = update_value(key, value);
|
||||
}
|
||||
|
||||
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> add_value_factory) {
|
||||
if (!dictionary.TryGetValue(key, out TValue value))
|
||||
dictionary.Add(key, value = add_value_factory(key));
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void SetValue<K, VK, V, VKD>(this IDictionary<K, VKD> target, K k1, VK k2, V value) where VKD : IDictionary<VK, V> {
|
||||
if (!target.TryGetValue(k1, out VKD d))
|
||||
target.Add(k1, d = Activator.CreateInstance<VKD>());
|
||||
d[k2] = value;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TKey, (TValue1, TValue2)> Join<TKey, TValue1, TValue2>(this IReadOnlyDictionary<TKey, TValue1> dictionary, IReadOnlyDictionary<TKey, TValue2> merge_target) =>
|
||||
Join(dictionary, merge_target, (v1, v2) => (v1, v2));
|
||||
|
||||
public static IReadOnlyDictionary<TKey, TResult> Join<TKey, TValue1, TValue2, TResult>(this IReadOnlyDictionary<TKey, TValue1> dictionary, IReadOnlyDictionary<TKey, TValue2> merge_target, Func<TValue1, TValue2, TResult> select) {
|
||||
var iks = dictionary.Keys.Intersect(merge_target.Keys);
|
||||
return new ReadOnlyDictionary<TKey, TResult>(iks.ToDictionary(k => k, k => select(dictionary[k], merge_target[k])));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TKey, TResult> LeftJoin<TKey, TValue1, TValue2, TResult>(this IReadOnlyDictionary<TKey, TValue1> dictionary, IReadOnlyDictionary<TKey, TValue2> merge_target,
|
||||
Func<TValue1, TValue2, TResult> select, Func<TValue1, TResult> select_left_only) =>
|
||||
dictionary.ToDictionary(kv => kv.Key, kv => {
|
||||
if (merge_target.TryGetValue(kv.Key, out TValue2 v2))
|
||||
return select(kv.Value, v2);
|
||||
else
|
||||
return select_left_only(kv.Value);
|
||||
});
|
||||
|
||||
public static IReadOnlyDictionary<TKey, (TValue1, TValue2)> SuperJoin<TKey, TValue1, TValue2>(this IReadOnlyDictionary<TKey, TValue1> dictionary, IReadOnlyDictionary<TKey, TValue2> merge_target) {
|
||||
var eks1 = dictionary.Keys.Except(merge_target.Keys);
|
||||
var eks2 = merge_target.Keys.Except(dictionary.Keys);
|
||||
var iks = dictionary.Keys.Intersect(merge_target.Keys);
|
||||
return new ReadOnlyDictionary<TKey, (TValue1, TValue2)>(eks1
|
||||
.Select(k => (k, dictionary[k], default(TValue2)))
|
||||
.Concat(eks2.Select(k => (k, default(TValue1), merge_target[k])))
|
||||
.Concat(iks.Select(k => (k, dictionary[k], merge_target[k])))
|
||||
.ToDictionary(vals => vals.k, vals => (vals.Item2, vals.Item3))
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If TryGetValue succeeds, result will be set with the corresponding value, and the key will be removed (returns true).
|
||||
/// Otherwise, result is set to default, and nothing is removed (returns false).
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dictionary"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryPopValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, out TValue result) {
|
||||
if (dictionary.TryGetValue(key, out result)) {
|
||||
dictionary.Remove(key);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
#region InserSorted
|
||||
|
||||
@@ -131,12 +131,6 @@ namespace mbts
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int safe_compare<T>(this T a, T b, Func<T, T, int> compare) =>
|
||||
a == null ? (b == null ? 0 : -1) : (b == null ? 1 : compare(a, b));
|
||||
|
||||
public static int safe_compare<T>(this T a, T b) where T : IComparable<T> =>
|
||||
a == null ? (b == null ? 0 : -1) : (b == null ? 1 : a.CompareTo(b));
|
||||
|
||||
public static bool safe_equals<T>(this T a, T b, Func<T, T, bool> equals) =>
|
||||
a == null ? b == null : b != null && equals(a, b);
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.utils {
|
||||
class ComparerBuilder<T> {
|
||||
public readonly List<IComparer<T>> _comparers_list;
|
||||
|
||||
public ComparerBuilder() =>
|
||||
_comparers_list = new List<IComparer<T>>(2);
|
||||
|
||||
public ComparerBuilder(IEnumerable<IComparer<T>> comparers) =>
|
||||
_comparers_list = new List<IComparer<T>>(comparers);
|
||||
|
||||
public void add(IComparer<T> comparer) =>
|
||||
_comparers_list.Add(comparer);
|
||||
|
||||
public void add<TValue>(Func<T, TValue> get) =>
|
||||
add(get, Comparer<TValue>.Default);
|
||||
|
||||
public void add<TValue>(Func<T, TValue> get, IComparer<TValue> comparer) =>
|
||||
add(CompareUtils.wrap(comparer, get));
|
||||
|
||||
public void clear() =>
|
||||
_comparers_list.Clear();
|
||||
|
||||
public IComparer<T> to_comparer() => new _Comparer(_comparers_list.ToArray());
|
||||
|
||||
class _Comparer : IComparer<T> {
|
||||
readonly IComparer<T>[] _comparers;
|
||||
|
||||
public _Comparer(IComparer<T>[] comparers) =>
|
||||
_comparers = comparers;
|
||||
|
||||
public int Compare(T x, T y) {
|
||||
foreach (var comp in _comparers) {
|
||||
int result = comp.Compare(x, y);
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,11 @@ namespace mbts.utils {
|
||||
// need to lock bc simultaneous callbacks on diff threads could generate 2 subscriptions at once,
|
||||
// only 1 of which would get disposed in the next callback.
|
||||
Disposable.dispose(ref _current_key_sub);
|
||||
if (!_is_disposed)
|
||||
_current_key_sub = _selector(key)?.Subscribe(_value_subject);
|
||||
if (!_is_disposed) {
|
||||
// IMPORTANT: do NOT subscribe _value_subject directly to the inner sequences. Subscribe with a callback delegate instead (e.g. _value_subject.OnNext).
|
||||
// Reason: If a sequence 'completes' while _value_subject's subscription is active, it will enter a 'Terminated' state, and forever cease notifying observers of subsequent updates.
|
||||
_current_key_sub = _selector(key)?.Subscribe(_value_subject.OnNext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,13 @@ namespace mbts.utils {
|
||||
IEnumerable<T> removed { get; }
|
||||
}
|
||||
|
||||
interface IIncrementalTableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
IEnumerable<T> added { get; }
|
||||
IEnumerable<T> removed { get; }
|
||||
IEnumerable<Change<T>> changes { get; }
|
||||
IEnumerable<T> changed { 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)) { }
|
||||
@@ -76,6 +83,51 @@ namespace mbts.utils {
|
||||
new TableUpdateTransform<T, TKey>(_state, _state.Values.ToList(), new T[0]);
|
||||
}
|
||||
|
||||
class IncrementalTableUpdateTranform<T, TKey> : IIncrementalTableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public static IncrementalTableUpdateTranform<T, TKey> init(IEnumerable<T> initial_values) =>
|
||||
new IncrementalTableUpdateTranform<T, TKey>(initial_values, Enumerable.Empty<T>(), Enumerable.Empty<Change<T>>());
|
||||
|
||||
public static IncrementalTableUpdateTranform<T, TKey> from_incremental_update(IIncrementalUpdate<T, TKey> source) {
|
||||
// copy the smaller dictionary (removed vs added)
|
||||
if (source.removed.Count < source.added.Count) {
|
||||
var changes_list = new List<Change<T>>();
|
||||
var removed_copy = source.removed.ToDictionary();
|
||||
var added_list = new List<T>();
|
||||
foreach (var kv in source.added) {
|
||||
if (removed_copy.TryPopValue(kv.Key, out T oldval)) {
|
||||
changes_list.Add(new Change<T>(oldval, kv.Value));
|
||||
} else {
|
||||
added_list.Add(kv.Value);
|
||||
}
|
||||
}
|
||||
return new IncrementalTableUpdateTranform<T, TKey>(added_list, removed_copy.Values, changes_list);
|
||||
} else {
|
||||
var changes_list = new List<Change<T>>();
|
||||
var added_copy = source.added.ToDictionary();
|
||||
var removed_list = new List<T>();
|
||||
foreach (var kv in source.removed) {
|
||||
if (added_copy.TryPopValue(kv.Key, out T newval)) {
|
||||
changes_list.Add(new Change<T>(kv.Value, newval));
|
||||
} else {
|
||||
removed_list.Add(kv.Value);
|
||||
}
|
||||
}
|
||||
return new IncrementalTableUpdateTranform<T, TKey>(added_copy.Values, removed_list, changes_list);
|
||||
}
|
||||
}
|
||||
|
||||
IncrementalTableUpdateTranform(IEnumerable<T> added, IEnumerable<T> removed, IEnumerable<Change<T>> changes) {
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
public IEnumerable<T> added { get; }
|
||||
public IEnumerable<T> removed { get; }
|
||||
public IEnumerable<Change<T>> changes { get; }
|
||||
public IEnumerable<T> changed => changes.Select(ch => ch.New);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
static class TableUpdateTransformExtensions {
|
||||
@@ -135,6 +187,12 @@ namespace mbts.utils {
|
||||
.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);
|
||||
|
||||
public static IIncrementalTableUpdateTransform<T, TKey> as_table<T, TKey>(this IIncrementalUpdate<T, TKey> update) where TKey : IEquatable<TKey> =>
|
||||
IncrementalTableUpdateTranform<T, TKey>.from_incremental_update(update);
|
||||
|
||||
public static IEnumerable<T> changed<T, TKey>(this IIncrementalTableUpdateTransform<T, TKey> update) where TKey : IEquatable<TKey> =>
|
||||
update.changes.Select(ch => ch.New);
|
||||
|
||||
class FilteredTableUpdateTransform<T, TKey> : ITableUpdateTransform<T, TKey> where TKey : IEquatable<TKey> {
|
||||
internal FilteredTableUpdateTransform(ITableUpdateTransform<T, TKey> source, Func<T, bool> predicate) {
|
||||
state = source.state;
|
||||
@@ -247,6 +305,6 @@ namespace mbts.utils {
|
||||
state_clone.Add(kv.Key, kv.Value);
|
||||
return new PivotTableUpdateTransform<T, TKey, TResult, TResultKey>(state_clone, added.Values.ToList(), removed.Values.ToList(), _key_map, _selector, _result_key, _key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
@@ -9,7 +10,7 @@ namespace mbts.ui.controls.adorners {
|
||||
public class DragRectAdorner : Adorner, IDisposable {
|
||||
Brush _fill = DIYGrid.DefaultSelectionHighlight;
|
||||
Pen _stroke = DIYGrid.DefaultSelectionStroke;
|
||||
readonly ScrollViewer _scroll_viewer;
|
||||
readonly ScrollViewerTuple _scroll_viewer_tup;
|
||||
readonly double _drift_multiplier;
|
||||
Point? _drag_start;
|
||||
double _x_drift, _y_drift;
|
||||
@@ -47,21 +48,22 @@ namespace mbts.ui.controls.adorners {
|
||||
/// <param name="scroll_viewer">The element's ScrollViewer.</param>
|
||||
/// <param name="drift_multiplier">The number of pixels per scroll unit.</param>
|
||||
public DragRectAdorner(FrameworkElement adorned_element, ScrollViewer scroll_viewer = null, double drift_multiplier = 1) : base(adorned_element) {
|
||||
_scroll_viewer = scroll_viewer;
|
||||
_drift_multiplier = drift_multiplier;
|
||||
AdornedElement.PreviewMouseDown += _on_preview_mouse_down;
|
||||
AdornedElement.PreviewMouseMove += _on_preview_mouse_move;
|
||||
AdornedElement.PreviewMouseUp += _on_preview_mouse_up;
|
||||
if (_scroll_viewer != null)
|
||||
_scroll_viewer.ScrollChanged += _scroll_viewer_ScrollChanged;
|
||||
if (scroll_viewer != null) {
|
||||
_scroll_viewer_tup = new ScrollViewerTuple(scroll_viewer);
|
||||
_scroll_viewer_tup.scroll_viewer.ScrollChanged += _scroll_viewer_ScrollChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
AdornedElement.PreviewMouseDown -= _on_preview_mouse_down;
|
||||
AdornedElement.PreviewMouseMove -= _on_preview_mouse_move;
|
||||
AdornedElement.PreviewMouseUp -= _on_preview_mouse_up;
|
||||
if (_scroll_viewer != null)
|
||||
_scroll_viewer.ScrollChanged -= _scroll_viewer_ScrollChanged;
|
||||
if (_scroll_viewer_tup != null)
|
||||
_scroll_viewer_tup.scroll_viewer.ScrollChanged -= _scroll_viewer_ScrollChanged;
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext) {
|
||||
@@ -82,8 +84,10 @@ namespace mbts.ui.controls.adorners {
|
||||
_y_drift = 0;
|
||||
}
|
||||
|
||||
void _on_preview_mouse_down(object sender, MouseButtonEventArgs e) =>
|
||||
_drag_start = e.GetPosition(this);
|
||||
void _on_preview_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (_scroll_viewer_tup == null || (_scroll_viewer_tup.presenter != null && _scroll_viewer_tup.presenter.IsMouseOver))
|
||||
_drag_start = e.GetPosition(this);
|
||||
}
|
||||
|
||||
void _on_preview_mouse_move(object sender, MouseEventArgs e) {
|
||||
if (_drag_start.HasValue) {
|
||||
@@ -105,5 +109,15 @@ namespace mbts.ui.controls.adorners {
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
class ScrollViewerTuple {
|
||||
public readonly ScrollViewer scroll_viewer;
|
||||
ScrollContentPresenter _presenter;
|
||||
|
||||
public ScrollViewerTuple(ScrollViewer scroll_viewer) =>
|
||||
this.scroll_viewer = scroll_viewer;
|
||||
|
||||
public ScrollContentPresenter presenter => _presenter ?? (_presenter = scroll_viewer.descendants_of_type<ScrollContentPresenter>().FirstOrDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +348,7 @@ namespace mbts.ui.controls {
|
||||
|
||||
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))
|
||||
if (ConvertUtils.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];
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
public DataTemplate EditableTemplate { get; set; }
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ObjectHelper.try_convert_args(values, out ColorPaletteVM vm, out bool is_selected)) {
|
||||
if (ConvertUtils.try_convert_args(values, out ColorPaletteVM vm, out bool is_selected)) {
|
||||
if (vm.can_user_edit && is_selected)
|
||||
return EditableTemplate;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace mbts.ui.controls.color_picker {
|
||||
|
||||
class NamedColorItemToolTipConverter : IMultiValueConverter {
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
|
||||
ObjectHelper.try_convert_args(values, out string name, out bool can_user_edit) ?
|
||||
ConvertUtils.try_convert_args(values, out string name, out bool can_user_edit) ?
|
||||
$"{name} (Alt + Click for info{(can_user_edit ? ", Ctrl + Click to assign" : string.Empty)})" :
|
||||
string.Empty;
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
interface IHandleGridSorting {
|
||||
void on_sorting(object sender, DIYGridSortingEventArgs e);
|
||||
}
|
||||
|
||||
class DIYGridColumnCollection : ObservableCollection<DIYGrid.ColumnBase> { }
|
||||
class DIYGridSortingEventArgs : EventArgs {
|
||||
public DIYGridSortingEventArgs(DIYGrid.ColumnBase column) => Column = column;
|
||||
@@ -159,7 +163,7 @@ namespace mbts.ui.controls {
|
||||
public static readonly SolidColorBrush DefaultSelectionHighlight = new SolidColorBrush(Colors.MediumSlateBlue) { Opacity = 0.4 };
|
||||
public static readonly Pen DefaultSelectionStroke = new Pen(Brushes.MediumSlateBlue, 1);
|
||||
readonly NotifyCollectionChangedEventHandler _sort_descriptions_collection_changed_cb;
|
||||
IDisposable _selection_handler;
|
||||
DataContextHooks _data_context_hooks;
|
||||
|
||||
public static readonly DependencyProperty SelectionHighlightProperty =
|
||||
WPFHelper.create_dp<SolidColorBrush, DIYGrid>(
|
||||
@@ -241,7 +245,8 @@ namespace mbts.ui.controls {
|
||||
}));
|
||||
Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(SelectionHighlight.Color) { Opacity = 0.2 };
|
||||
Resources[ActiveSelectionHighlightKey] = SelectionHighlight;
|
||||
DataContextChanged += (s, e) => _try_connect_selection_handler();
|
||||
_data_context_hooks = new DataContextHooks(this, DataContext);
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
public event DIYGridSortingEventHandler Sorting;
|
||||
@@ -306,15 +311,8 @@ namespace mbts.ui.controls {
|
||||
// Private Methods
|
||||
#region Private Methods
|
||||
|
||||
void _try_connect_selection_handler() {
|
||||
Disposable.dispose(ref _selection_handler);
|
||||
if (_row_presenter != null && DataContext is IHandleRowSelection hrs) {
|
||||
_row_presenter.SelectionChanged += on_selection_changed;
|
||||
_selection_handler = Disposable.Create(() => _row_presenter.SelectionChanged -= on_selection_changed);
|
||||
void on_selection_changed(object sender, SelectionChangedEventArgs e) =>
|
||||
hrs.row_selection_changed(e);
|
||||
}
|
||||
}
|
||||
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) =>
|
||||
Disposable.replace(ref _data_context_hooks, new DataContextHooks(this, e.NewValue));
|
||||
|
||||
#region Column Handling
|
||||
private void invalidate_column_layout(object sender = null, EventArgs args = null) {
|
||||
@@ -789,7 +787,7 @@ namespace mbts.ui.controls {
|
||||
|
||||
_scroll_horizontal.ValueChanged += on_horizontal_scroll;
|
||||
_superheader_container = GetTemplateChild("_super_headers") as Panel;
|
||||
_try_connect_selection_handler();
|
||||
Disposable.replace(ref _data_context_hooks, new DataContextHooks(this, DataContext));
|
||||
update_superheaders();
|
||||
|
||||
// TODO: All edit controls go in a canvas - edit api is called with an edit type.
|
||||
@@ -908,6 +906,31 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
class DataContextHooks : IDisposable {
|
||||
List<IDisposable> _hooks = new List<IDisposable>(3);
|
||||
|
||||
// this constructor provides a way to avoid repetative dp lookups ala owner.DataContext
|
||||
public DataContextHooks(DIYGrid owner, object data_context)
|
||||
: this(owner, data_context as IHandleRowSelection, data_context as IHandleGridSorting, data_context as IWillLetYouKnowWhenIDispose) { }
|
||||
|
||||
public DataContextHooks(DIYGrid owner, IHandleRowSelection selection_handler, IHandleGridSorting sorting_handler, IWillLetYouKnowWhenIDispose dispose_broadcaster) {
|
||||
if (selection_handler != null && owner._row_presenter is ListBox rp) {
|
||||
rp.SelectionChanged += on_selection_changed;
|
||||
_hooks.Add(Disposable.Create(() => rp.SelectionChanged -= on_selection_changed));
|
||||
void on_selection_changed(object sender, SelectionChangedEventArgs e) => selection_handler.row_selection_changed(e);
|
||||
}
|
||||
if (sorting_handler != null) {
|
||||
owner.Sorting += sorting_handler.on_sorting;
|
||||
_hooks.Add(Disposable.Create(() => owner.Sorting -= sorting_handler.on_sorting));
|
||||
}
|
||||
if (dispose_broadcaster != null)
|
||||
_hooks.Add(dispose_broadcaster.SubscribeDisposed(this));
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(_hooks);
|
||||
}
|
||||
}
|
||||
|
||||
static class DIYGridExtensions {
|
||||
|
||||
@@ -18,13 +18,13 @@ namespace mbts.ui.controls {
|
||||
public interface IRowVM {
|
||||
event Action invalidated;
|
||||
void raise_invalidated();
|
||||
ObservableCollection<RowAnimation> row_animations { get; }
|
||||
IReadOnlyList<IGridCellStore> cell_stores { get; }
|
||||
}
|
||||
|
||||
public interface IRowVM<TRowData> : IRowVM {
|
||||
event Action<(TRowData old_val, TRowData new_val)> updated;
|
||||
public interface IAnimatableRowVM<TRowData> : IRowVM {
|
||||
ObservableCollection<RowAnimation> row_animations { get; }
|
||||
IReadOnlyList<IGridCellStore> cell_stores { get; }
|
||||
TRowData current_data { get; }
|
||||
event Action<(TRowData old_val, TRowData new_val)> updated;
|
||||
}
|
||||
|
||||
public interface IActionableRowVM {
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.ui {
|
||||
class PredicateAnimator<TRow, TRowData> where TRow : DIYGrid.IRowVM<TRowData> {
|
||||
class PredicateAnimator<TRow, TRowData> where TRow : DIYGrid.IAnimatableRowVM<TRowData> {
|
||||
readonly TRow _row;
|
||||
SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>> _predicate_animation_map = new SetMap<IPredicate<TRowData>, AnimationDefinitionWrapper<TRow>>();
|
||||
HashSet<IPredicate<TRowData>> _active_predicates = new HashSet<IPredicate<TRowData>>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using mbts.collections;
|
||||
@@ -11,7 +11,6 @@ using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
@@ -19,20 +18,16 @@ using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.ui {
|
||||
class GridSettingsManager<TRow, TRowData, TPredicateBuilderArg, TPredicateBuilder, TSettingsData> : ViewModelBase, IValueFactory<TSettingsData>, IObserver<TSettingsData>, IDisposable
|
||||
where TRow : IRowVM<TRowData>
|
||||
where TRow : IRowVM
|
||||
where TPredicateBuilder : IPredicateBuilder<TRowData, TPredicateBuilderArg>, new()
|
||||
where TSettingsData : class, IGridSettingsWithNotifications<TRowData, TPredicateBuilder> {
|
||||
//
|
||||
public readonly ObservableCollection<string> live_sorting_props;
|
||||
|
||||
readonly IBus _bus;
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly INotificationService _notification_service;
|
||||
readonly List<IDisposable> _resources, _notification_subs = new List<IDisposable>();
|
||||
readonly ObservableCollection<TRow> _rows;
|
||||
readonly ICollectionView _rows_view;
|
||||
readonly List<IDisposable> _resources, _notification_subs = new List<IDisposable>();
|
||||
readonly ObservableCollection<ColumnBase> _columns;
|
||||
//readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
readonly ObservableCollection<TRow> _rows;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
@@ -47,8 +42,9 @@ namespace mbts.ui {
|
||||
IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>> _predicate_animations_data;
|
||||
|
||||
public GridSettingsManager(
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel orders_model, ObservableCollection<TRow> rows,
|
||||
ICollectionView rows_view, ObservableCollection<ColumnBase> columns, Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm,
|
||||
IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel orders_model,
|
||||
ObservableCollection<TRow> rows, ObservableCollection<ColumnBase> columns,
|
||||
Func<IPropertyWrapperVM<TPredicateBuilder>> create_predicate_vm,
|
||||
Func<string, IPropertyWrapperVM<TPredicateBuilder>> create_column_predicate_vm,
|
||||
Func<GridSettings, IEnumerable<IPredicateAnimationsData<TRowData, TPredicateBuilder>>, TSettingsData> create_settings_obj,
|
||||
TPredicateBuilderArg arg, IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags = null) {
|
||||
@@ -57,7 +53,6 @@ namespace mbts.ui {
|
||||
_notification_service = notification_service;
|
||||
_orders_model = orders_model;
|
||||
_rows = rows;
|
||||
_rows_view = rows_view;
|
||||
_columns = columns;
|
||||
_create_predicate_vm = create_predicate_vm;
|
||||
_create_column_predicate_vm = create_column_predicate_vm;
|
||||
@@ -70,18 +65,10 @@ namespace mbts.ui {
|
||||
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
|
||||
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
|
||||
//_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
_resources = new List<IDisposable> {
|
||||
ObservableAutoWrapper.connect<SortDescriptionCollection, SortDescription, string>(
|
||||
rows_view.SortDescriptions, live_sorting_props = ((ICollectionViewLiveShaping)rows_view).LiveSortingProperties, sd => sd.PropertyName),
|
||||
(launch_settings_editor = new DialogCommand(create_settings_view))
|
||||
};
|
||||
//
|
||||
//PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
// var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
// pa.set_predicate_animations(_animation_defs);
|
||||
// return pa;
|
||||
//}
|
||||
// local methods
|
||||
Window create_settings_view() {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateBuilder, TSettingsData>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm, _create_settings_obj);
|
||||
// need to refresh before launching the settings-view bc users can chanve some grid-column-settings properties directly from the grid (e.g. width, is_collapsed, etc.)
|
||||
@@ -94,12 +81,7 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
var window = new GridSettingsDialog { DataContext = vm };
|
||||
vm.committed += () => {
|
||||
TSettingsData sd = vm.get_result();
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
sd = _create_settings_obj(sd.general.with(sorting_criterion: rows_view.get_sorting_criteria(columns)), sd.predicate_animations);
|
||||
load_from(sd);
|
||||
};
|
||||
vm.committed += () => load_from(vm.get_result());
|
||||
vm.RequestClose += (s, e) => WindowUtilities.force_close(window);
|
||||
return window;
|
||||
}
|
||||
@@ -125,13 +107,12 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
public TSettingsData create() {
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
_grid_settings.set(grid_settings.with(_columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
if (_column_flags.TryGetValue(c.id, out ImmutableColumnSettingStore store))
|
||||
col_settings = store.apply(col_settings);
|
||||
return col_settings;
|
||||
}), _rows_view.get_sorting_criteria(_columns)));
|
||||
})));
|
||||
return _create_settings_obj(grid_settings, _predicate_animations_data);
|
||||
}
|
||||
|
||||
@@ -143,11 +124,6 @@ namespace mbts.ui {
|
||||
_grid_settings.set(grid);
|
||||
foreach (ColumnBase col in _columns)
|
||||
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight);
|
||||
if (reset_immutables) {
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_rows_view.set_sort_descriptions(grid.sorting_criterion, _columns);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
_predicate_animations_data = value.predicate_animations;
|
||||
_animation_defs.clear();
|
||||
foreach (var pa in _predicate_animations_data) {
|
||||
@@ -158,13 +134,7 @@ namespace mbts.ui {
|
||||
if (pa.predicate_builder is INotificationPredicateBuilder<TRowData, TPredicateBuilderArg> tpb && tpb.is_toast_enabled) {
|
||||
_notification_subs.Add(_notification_service.link(tpb.create_notification_predicate(_arg)));
|
||||
}
|
||||
if (pa.row_animations.Count > 0 || pa.cell_animations.Count > 0) {
|
||||
_animation_defs.add(pa.predicate_builder.create_predicate(_arg), PredicateAnimations.flat_definitions<TRow, TRowData>(pa.row_animations, pa.cell_animations, lookup_column));
|
||||
}
|
||||
}
|
||||
//foreach (var a in _animators) {
|
||||
// a.set_predicate_animations(_animation_defs);
|
||||
//}
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null && _columns != null) {
|
||||
@@ -186,12 +156,11 @@ namespace mbts.ui {
|
||||
if (col_settings_id_map.TryGetValue(id_key(col.id), out GridColumnSettings cs))
|
||||
col.load_from(cs);
|
||||
}
|
||||
// invalidate all the rows to draw them again to reflect the latest settings
|
||||
foreach (var r in _rows) r.raise_invalidated();
|
||||
//
|
||||
string id_key(string id) => id.ToLower();
|
||||
}
|
||||
//
|
||||
IGridColumn<TRow> lookup_column(string col_id) =>
|
||||
_columns.OfType<IGridColumn<TRow>>().FirstOrDefault(c => string.Equals(col_id, c.id, StringComparison.CurrentCultureIgnoreCase));
|
||||
}
|
||||
|
||||
void IObserver<TSettingsData>.OnNext(TSettingsData sd) {
|
||||
@@ -215,4 +184,4 @@ namespace mbts.ui {
|
||||
void IObserver<TSettingsData>.OnError(Exception error) { }
|
||||
void IObserver<TSettingsData>.OnCompleted() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using ProtoBuf;
|
||||
using bts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
@@ -8,7 +9,7 @@ namespace mbts.spark.filters {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class DoubleStatus : INullableStructProto<double> {
|
||||
class DoubleStatus : INullableStructProto<double>, IEquatable<DoubleStatus> {
|
||||
[ProtoMember(1)]
|
||||
bool _active;
|
||||
[ProtoMember(2)]
|
||||
@@ -19,10 +20,19 @@ namespace mbts.spark.filters {
|
||||
get => _active ? new double?(_value) : new double?();
|
||||
set { if (_active = value.HasValue && !double.IsNaN(value.Value)) _value = value.Value; }
|
||||
}
|
||||
|
||||
public bool Equals(DoubleStatus other) =>
|
||||
other != null &&
|
||||
_active == other._active &&
|
||||
_value == other._value;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as DoubleStatus);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(_active.GetHashCode(), _value.GetHashCode());
|
||||
public override string ToString() => _active ? _value.ToString() : $"({_value})";
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class DoubleMinMaxState : IMinMaxState<double> {
|
||||
class DoubleMinMaxState : IMinMaxState<double>, IEquatable<DoubleMinMaxState> {
|
||||
[ProtoMember(1)]
|
||||
readonly DoubleStatus _minimum;
|
||||
[ProtoMember(2)]
|
||||
@@ -35,5 +45,13 @@ namespace mbts.spark.filters {
|
||||
|
||||
public INullableStructProto<double> minimum => _minimum;
|
||||
public INullableStructProto<double> maximum => _maximum;
|
||||
|
||||
public bool Equals(DoubleMinMaxState other) =>
|
||||
other != null &&
|
||||
_minimum.safe_equals(other._minimum) &&
|
||||
_maximum.safe_equals(other._maximum);
|
||||
public override bool Equals(object obj) => Equals(obj as DoubleMinMaxState);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(_minimum?.GetHashCode() ?? 0, _maximum?.GetHashCode() ?? 0);
|
||||
public override string ToString() => $"Min:{_minimum}, Max:{_maximum}";
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,6 @@ namespace mbts.spark.filters {
|
||||
protected virtual void _validate() { }
|
||||
|
||||
bool IDataGridColumnFilter.filter(object row) =>
|
||||
filter(ObjectHelper.try_convert<TRow>(row));
|
||||
filter(ConvertUtils.try_convert<TRow>(row));
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,6 @@ namespace mbts.spark.filters {
|
||||
|
||||
|
||||
bool IDataGridColumnFilter.filter(object row) =>
|
||||
filter(ObjectHelper.try_convert<TRow>(row));
|
||||
filter(ConvertUtils.try_convert<TRow>(row));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using ProtoBuf;
|
||||
using bts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
@@ -8,7 +9,7 @@ namespace mbts.spark.filters {
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class UIntStatus : INullableStructProto<uint> {
|
||||
class UIntStatus : INullableStructProto<uint>, IEquatable<UIntStatus> {
|
||||
[ProtoMember(1)]
|
||||
bool _active;
|
||||
[ProtoMember(2)]
|
||||
@@ -19,10 +20,19 @@ namespace mbts.spark.filters {
|
||||
get => _active ? new uint?(_value) : new uint?();
|
||||
set { if (_active = value.HasValue) _value = value.Value; }
|
||||
}
|
||||
|
||||
public bool Equals(UIntStatus other) =>
|
||||
other != null &&
|
||||
_active == other._active &&
|
||||
_value == other._value;
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as UIntStatus);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(_active.GetHashCode(), _value.GetHashCode());
|
||||
public override string ToString() => _active ? _value.ToString() : $"({_value})";
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
class UIntMinMaxState : IMinMaxState<uint> {
|
||||
class UIntMinMaxState : IMinMaxState<uint>, IEquatable<UIntMinMaxState> {
|
||||
[ProtoMember(1)]
|
||||
readonly UIntStatus _minimum;
|
||||
[ProtoMember(2)]
|
||||
@@ -35,5 +45,13 @@ namespace mbts.spark.filters {
|
||||
|
||||
public INullableStructProto<uint> minimum => _minimum;
|
||||
public INullableStructProto<uint> maximum => _maximum;
|
||||
|
||||
public bool Equals(UIntMinMaxState other) =>
|
||||
other != null &&
|
||||
_minimum.safe_equals(other._minimum) &&
|
||||
_maximum.safe_equals(other._maximum);
|
||||
public override bool Equals(object obj) => Equals(obj as UIntMinMaxState);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(_minimum?.GetHashCode() ?? 0, _maximum?.GetHashCode() ?? 0);
|
||||
public override string ToString() => $"Min:{_minimum}, Max:{_maximum}";
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
Content="Apply"
|
||||
CommandParameter="{TemplateBinding ApplyCommandParameter}"
|
||||
Command="{TemplateBinding ApplyCommand}"
|
||||
CommandTarget="{TemplateBinding ApplyCommandTarget}"
|
||||
IsDefault="True"
|
||||
Focusable="False"
|
||||
Style="{StaticResource CommonButton}"
|
||||
|
||||
@@ -24,6 +24,11 @@ namespace mbts.ui.spark {
|
||||
public object ApplyCommandParameter { get => GetValue(ApplyCommandParameterProperty); set => SetValue(ApplyCommandParameterProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ApplyCommandTarget
|
||||
public static readonly DependencyProperty ApplyCommandTargetProperty = WPFHelper.create_dp<IInputElement, FilterContentControl>(nameof(ApplyCommandTarget));
|
||||
public IInputElement ApplyCommandTarget { get => GetValue(ApplyCommandTargetProperty) as IInputElement; set => SetValue(ApplyCommandTargetProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region ClearCommand
|
||||
public static readonly DependencyProperty ClearCommandProperty = DependencyProperty.Register(
|
||||
nameof(ClearCommand), typeof(ICommand), typeof(FilterContentControl));
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ApplyCommandTarget="{Binding ElementName=Filter}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
@@ -115,6 +116,9 @@
|
||||
Begin="{Binding begin}"
|
||||
End="{Binding end}"
|
||||
>
|
||||
<c:TimeFilterControl.CommandBindings>
|
||||
<CommandBinding Command="{x:Static c:DataGridFilterProperties.CommitCommand}" Executed="{x:Static c:TimeFilterControl.CommitExecuted}"/>
|
||||
</c:TimeFilterControl.CommandBindings>
|
||||
<u:Behavior.BindingUpdateGroups>
|
||||
<u:BindingUpdateGroupCollection>
|
||||
<u:BindingUpdateGroup Source="{Binding DataContext, Source={u:DirectReference Filter}}" Target="{Binding Source={u:DirectReference Filter}}">
|
||||
|
||||
Loaded 100 of 426 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user