Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82a2ba4a86 | ||
|
|
ccbe20eb57 | ||
|
|
70a2feb92a | ||
|
|
6aeba4f599 | ||
|
|
523f02b4c2 | ||
|
|
edb4ed02c9 | ||
|
|
96da8e3276 | ||
|
|
c056bfcf7a | ||
|
|
2ddf1a1762 | ||
|
|
8a9ddfded7 | ||
|
|
a83f97e1ef | ||
|
|
3630bf5b9a | ||
|
|
c7ea6c4ca4 | ||
|
|
1ffda9c413 | ||
|
|
1ca5ed4520 | ||
|
|
8115f9df74 | ||
|
|
306cbaa837 | ||
|
|
efa31059d9 | ||
|
|
73089634f9 | ||
|
|
a84e5e3460 | ||
|
|
4d53bfb3f0 | ||
|
|
717391afb3 | ||
|
|
38e0d40364 | ||
|
|
7053c2ee03 | ||
|
|
cdde730fc5 | ||
|
|
95b0119d22 | ||
|
|
07a965a3e0 | ||
|
|
eba153e289 | ||
|
|
fc5238c56f | ||
|
|
bc367b9f56 | ||
|
|
ba07041c26 | ||
|
|
d3101b1cd8 | ||
|
|
3a9a86020b | ||
|
|
6c93208e11 | ||
|
|
bf1ca4e5fa | ||
|
|
3f9e64016b | ||
|
|
c672967caf | ||
|
|
1e0dac0fbc | ||
|
|
aec8de54bd | ||
|
|
a960093cf6 | ||
|
|
4601e49106 | ||
|
|
7e65db38cf | ||
|
|
d70bd63659 | ||
|
|
246f9240e7 | ||
|
|
a8ebb9cdcb | ||
|
|
bdb2d6cf9e | ||
|
|
1966c8e0c6 | ||
|
|
a418f47d1c | ||
|
|
df516fbd2d | ||
|
|
b8e298c524 | ||
|
|
d96c9b16bc | ||
|
|
3df7156ba3 | ||
|
|
4376605d5e | ||
|
|
4d8a2f28d3 | ||
|
|
930f74ff27 | ||
|
|
4ddb8fe9c5 | ||
|
|
74aed2144d | ||
|
|
87437883a6 | ||
|
|
d946cb4765 | ||
|
|
37c9707c63 | ||
|
|
02d1badf0d | ||
|
|
2a295237a6 | ||
|
|
dc8b0b8359 | ||
|
|
d36a5b30e6 | ||
|
|
616fbdba00 | ||
|
|
753221e429 | ||
|
|
d5f41d8405 | ||
|
|
3490834108 | ||
|
|
64bef0bd06 | ||
|
|
e4ea5cd769 | ||
|
|
c8790f766a | ||
|
|
2243dc566e | ||
|
|
94c2d9182c | ||
|
|
12d6374158 | ||
|
|
58b9c27098 | ||
|
|
f68aac2015 | ||
|
|
ca5334fd87 | ||
|
|
71961637f1 | ||
|
|
5aab80aed6 | ||
|
|
12e7c82698 | ||
|
|
0c4cdd0bc9 | ||
|
|
ca0c17a41b | ||
|
|
3acaf5b2c0 | ||
|
|
00d8ea9322 | ||
|
|
eedf19474e | ||
|
|
a586b75226 | ||
|
|
c6039723da | ||
|
|
6f26164072 | ||
|
|
a3de2c1983 | ||
|
|
8bd2e5e5f3 | ||
|
|
7b01fdcb23 | ||
|
|
d6df085d8e | ||
|
|
5e2a7c82c0 | ||
|
|
c6f2fe9b13 | ||
|
|
c56b41bf47 | ||
|
|
484c7a98a8 | ||
|
|
21b65a92fe | ||
|
|
559b6f8fd1 | ||
|
|
7149a20865 | ||
|
|
407dbc4c0a | ||
|
|
eff1954260 | ||
|
|
81880779c3 | ||
|
|
41fa3046f1 | ||
|
|
21a3386574 | ||
|
|
a9ef7e47b5 | ||
|
|
0c19f82a24 | ||
|
|
9762a81a59 | ||
|
|
c34c99a874 | ||
|
|
c19aad55a7 | ||
|
|
744260bea5 | ||
|
|
dfe480d1f7 | ||
|
|
7620b7878e | ||
|
|
fdf21c71bb | ||
|
|
f03453f7c5 | ||
|
|
9ebdb107fa | ||
|
|
e0f1137ee2 | ||
|
|
079a7ad439 | ||
|
|
ae100f66c7 | ||
|
|
057d5df6da | ||
|
|
f539659ab5 | ||
|
|
e4d9b902d8 | ||
|
|
9fb180ff20 | ||
|
|
a6fde760ff | ||
|
|
019bbaa0d2 | ||
|
|
94f3c5a6c4 | ||
|
|
c3f9e5fa91 | ||
|
|
c127f488ff | ||
|
|
ba7a28aa8a | ||
|
|
95a7f7ccb6 | ||
|
|
f90961ce22 | ||
|
|
6c03a263a1 | ||
|
|
862e199495 | ||
|
|
5218b748e8 | ||
|
|
ba9bd53c1e | ||
|
|
8b816f1491 | ||
|
|
04cc18553b | ||
|
|
c1c067522c | ||
|
|
cdf537afaa | ||
|
|
7867cc934b | ||
|
|
1ec7c141b9 | ||
|
|
0b7cdd656e | ||
|
|
c705867218 | ||
|
|
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
+13
-60
@@ -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",
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,6 +44,7 @@ else ()
|
||||
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)
|
||||
|
||||
@@ -54,6 +55,8 @@ else ()
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/web ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/data_files ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND ln ARGS -sf ${CMAKE_BINARY_DIR}/src/lib/libspark.so ${CMAKE_BINARY_DIR}/server
|
||||
COMMAND 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)
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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})
|
||||
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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -198,7 +198,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 +207,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
|
||||
|
||||
|
||||
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 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>
|
||||
@@ -1,8 +1,42 @@
|
||||
# 1.2
|
||||
# Release Notes
|
||||
|
||||
## 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 access from the gears icon in the Order Book toolbar or from the right-click menu
|
||||
@@ -18,7 +52,7 @@
|
||||
### 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
|
||||
@@ -37,7 +71,7 @@
|
||||
- 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
|
||||
@@ -48,7 +82,7 @@
|
||||
- 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
|
||||
@@ -59,3 +93,41 @@
|
||||
- Individual fills be compacted into start of day position records
|
||||
- Orders from the day before the recycle will be deleted
|
||||
|
||||
|
||||
##1.1
|
||||
|
||||
###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
|
||||
+1
-1
Submodule docker/linux updated: 8ee9692597...aedfd79e02.
@@ -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">
|
||||
|
||||
@@ -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,14 +29,20 @@ 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))
|
||||
|
||||
@@ -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,7 +58,7 @@ 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
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
@@ -204,7 +202,7 @@ namespace mbts.spark {
|
||||
new HedgeRatioModel(),
|
||||
new QuoteHiddenModel(),
|
||||
new MarketDataTypeModel(),
|
||||
//new MarketDataCalculationModel(),
|
||||
new MarketDataCalculationModel(),
|
||||
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
|
||||
quote_throttle_inside_ticks,
|
||||
quote_throttle_outside_ticks,
|
||||
@@ -296,7 +294,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 +336,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,18 +53,22 @@ namespace mbts.ui {
|
||||
ToastNotificationController _toast_controller;
|
||||
SoundNotificationPlayer _sound_player;
|
||||
Dispatcher _dispatcher;
|
||||
bool _toast_notifications_enabled, _sound_notifications_enabled = NotificationSettings.Default.sound_notification.enabled;
|
||||
bool
|
||||
_sound_notifications_enabled = Configuration.get<bts.config.toggles.SoundNotifications>().enabled,
|
||||
_toast_notifications_enabled = Configuration.get<bts.config.toggles.ToastNotifications>().enabled;
|
||||
|
||||
[ImportingConstructor]
|
||||
public Notifier(IBus bus, UserSettingsHub settings_hub) {
|
||||
_disposer = new ThreadSafeDisposable(dispose_core);
|
||||
Thread thread = new Thread(start) {
|
||||
IsBackground = true,
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
Name = "BTSNotifications"
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
if (_sound_notifications_enabled || _toast_notifications_enabled) {
|
||||
Thread thread = new Thread(start) {
|
||||
IsBackground = true,
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
Name = "BTSNotifications"
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
void start() {
|
||||
try {
|
||||
@@ -102,7 +106,9 @@ namespace mbts.ui {
|
||||
(_dispatcher = Dispatcher.CurrentDispatcher).UnhandledException += _on_dispatcher_UnhandledException;
|
||||
Dispatcher.Run();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (NAudio.MmException) { }
|
||||
catch (Exception ex) {
|
||||
Log.assert(false, $"Exception in {Thread.CurrentThread.Name} thread: {ex}");
|
||||
_disposer.Dispose();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
/// 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) {
|
||||
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) {
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -521,12 +521,14 @@
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ItemsControl}">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
|
||||
>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
|
||||
<Border
|
||||
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true"
|
||||
>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
@@ -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>>();
|
||||
|
||||
+6
-2
@@ -28,7 +28,7 @@ namespace mbts.ui.controls {
|
||||
new AnonymousValueConverter(obj =>
|
||||
obj is SoundNotification.SystemSound s ?
|
||||
EnumHelper.get_attribute<SoundNotification.SystemSound, LicenseAttribute>(s)?.link : null);
|
||||
|
||||
|
||||
public static readonly DependencyProperty BusProperty =
|
||||
WPFHelper.create_dp<IBus, SoundAnimationView>(nameof(Bus));
|
||||
|
||||
@@ -38,7 +38,11 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
|
||||
public static ICommand PreviewSoundCommand =>
|
||||
new DelegateCommand<SoundNotification.SystemSound>(s => NAudioPlayer.create(s).play());
|
||||
new DelegateCommand<SoundNotification.SystemSound>(s => {
|
||||
try {
|
||||
NAudioPlayer.create(s).play();
|
||||
} catch (NAudio.MmException) { }
|
||||
});
|
||||
|
||||
#region SoundTypes
|
||||
public static readonly DependencyProperty SoundTypesProperty =
|
||||
|
||||
@@ -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}}">
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
@@ -14,6 +15,18 @@ namespace mbts.ui.controls {
|
||||
/// Interaction logic for TimeFilterControl.xaml
|
||||
/// </summary>
|
||||
public class TimeFilterControl : Control {
|
||||
public static readonly ExecutedRoutedEventHandler CommitExecuted = (s, e) => {
|
||||
if (s is TimeFilterControl tfc) {
|
||||
tfc.get_template_child<DateTimePicker>(_begin_dtp_name)?.flush_pending_changes();
|
||||
tfc.get_template_child<DateTimePicker>(_end_dtp_name)?.flush_pending_changes();
|
||||
(e.Parameter as IDataGridColumnFilter)?.apply.TryExecuteIfCan(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
};
|
||||
const string
|
||||
_begin_dtp_name = "PART_Begin_DateTimePicker",
|
||||
_end_dtp_name = "PART_End_DateTimePicker";
|
||||
|
||||
static TimeFilterControl() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeFilterControl), new FrameworkPropertyMetadata(typeof(TimeFilterControl)));
|
||||
|
||||
@@ -45,17 +58,17 @@ namespace mbts.ui.controls {
|
||||
var clear_begin_time = this.assert_template_child<Hyperlink>("ClearBeginLink");
|
||||
var clear_end_time = this.assert_template_child<Hyperlink>("ClearEndLink");
|
||||
var set_last_hour = this.assert_template_child<Hyperlink>("SetLastHourLink");
|
||||
clear_begin_time.Command = new DelegateCommand(() => Begin = default);
|
||||
clear_end_time.Command = new DelegateCommand(() => End = default);
|
||||
set_last_hour.Command = new DelegateCommand(() => Begin = TimeTextTuple.FromDateTime(DateTime.Now.AddHours(-1)));
|
||||
clear_begin_time.Command = new DelegateCommand(() => SetCurrentValue(BeginProperty, default(TimeTextTuple)));
|
||||
clear_end_time.Command = new DelegateCommand(() => SetCurrentValue(EndProperty, default(TimeTextTuple)));
|
||||
set_last_hour.Command = new DelegateCommand(() => SetCurrentValue(BeginProperty, TimeTextTuple.FromDateTime(DateTime.Now.AddHours(-1))));
|
||||
}
|
||||
|
||||
class TimeFilterErrorAdorner : NotifyDataErrorAdorner<TimeFilterControl, ITimeFilterVM>, IDisposable {
|
||||
readonly ChildProperty<DateTimePicker> _begin_picker, _end_picker;
|
||||
|
||||
public TimeFilterErrorAdorner(TimeFilterControl view) : base(view) {
|
||||
_begin_picker = child_property<DateTimePicker>("PART_Begin_DateTimePicker");
|
||||
_end_picker = child_property<DateTimePicker>("PART_End_DateTimePicker");
|
||||
_begin_picker = child_property<DateTimePicker>(_begin_dtp_name);
|
||||
_end_picker = child_property<DateTimePicker>(_end_dtp_name);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawing_context) {
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace mbts.ui.controls
|
||||
protected DataGridExRow() { }
|
||||
|
||||
public abstract object get_cell(TColKey col_key);
|
||||
public object get_cell(object col_key) { return get_cell(ObjectHelper.try_convert<TColKey>(col_key)); }
|
||||
public object get_cell(object col_key) { return get_cell(ConvertUtils.try_convert<TColKey>(col_key)); }
|
||||
|
||||
|
||||
}
|
||||
@@ -60,6 +60,6 @@ namespace mbts.ui.controls
|
||||
public abstract class DataGridExCell<TData> : ViewModelBase, IDataGridExCell<TData>, IDataGridExCell
|
||||
{
|
||||
public abstract void update(TData data);
|
||||
public void update(object data) { update(ObjectHelper.try_convert<TData>(data)); }
|
||||
public void update(object data) { update(ConvertUtils.try_convert<TData>(data)); }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using mbts.ui.utility;
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
@@ -77,7 +77,7 @@ namespace mbts.ui.controls {
|
||||
|
||||
class FontFamilySupportsWeightConverter : IMultiValueConverter {
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ObjectHelper.try_convert_args(values, out FontFamily ff, out FontWeight fw))
|
||||
if (ConvertUtils.try_convert_args(values, out FontFamily ff, out FontWeight fw))
|
||||
return TextUtils.available_weights(ff).Contains(fw);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -268,11 +268,9 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Join Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding join_threshold}"/>
|
||||
<!-- <TextBlock Grid.Row="3" VerticalAlignment="Center">Tick Offset</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="3" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/> -->
|
||||
<TextBlock Grid.Row="5">Child</TextBlock>
|
||||
<TextBlock Grid.Row="3">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="5" Grid.Column="2"
|
||||
Grid.Row="3" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
@@ -299,8 +297,6 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Top Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="1" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
|
||||
<!-- <TextBlock Grid.Row="9" VerticalAlignment="Center">Watch Levels</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="9" Grid.Column="2" Value="{Binding watch_level_tick_offset}"/> -->
|
||||
<TextBlock Grid.Row="3">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="3" Grid.Column="2"
|
||||
@@ -351,6 +347,37 @@
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.CoverOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.OffsetOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="4" />
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="1" VerticalAlignment="Center">Tick Offset</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="1" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<TextBlock Grid.Row="3">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="3" Grid.Column="2"
|
||||
IsOpen="{Binding child_order.editable_properties_open, FallbackValue=False}"
|
||||
ItemsSource="{Binding child_order_types}"
|
||||
SelectedItem="{Binding child_order}"
|
||||
HasEditableProperties="{Binding child_order.has_editable_properties, FallbackValue=False}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:OrderModificationTemplateSelector.OffsetOrderTemplate>
|
||||
<usp:OrderModificationTemplateSelector.TriggerOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
@@ -595,19 +622,88 @@
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ItemsControl Grid.Row = "1" ItemsSource="{Binding parameters}" Grid.ColumnSpan="3">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding name}"/>
|
||||
<TextBox HorizontalAlignment="Stretch" Grid.Column="2" Text="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemTemplateSelector>
|
||||
<usp:DynamicOrderParamTemplateSelector>
|
||||
<usp:DynamicOrderParamTemplateSelector.BoolTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding descriptor.ParamName}"/>
|
||||
<CheckBox HorizontalAlignment="Stretch" Grid.Column="2" IsChecked="{Binding param.AsBool, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.BoolTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.DoubleTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding descriptor.ParamName}"/>
|
||||
<c:TickUpDownDouble Grid.Column="2" Value="{Binding param.AsDouble, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.DoubleTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.IntTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding descriptor.ParamName}"/>
|
||||
<c:TickUpDownInt Grid.Column="2" Value="{Binding param.AsInt32, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.IntTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.UIntTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding descriptor.ParamName}"/>
|
||||
<c:TickUpDownUInt Grid.Column="2" Value="{Binding param.AsUint32, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.UIntTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.StringTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding descriptor.ParamName}"/>
|
||||
<TextBox HorizontalAlignment="Stretch" Grid.Column="2" Text="{Binding param.AsString, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.StringTemplate>
|
||||
<usp:DynamicOrderParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding descriptor.ParamName}"/>
|
||||
<ComboBox Grid.Column="2" ItemsSource="{Binding descriptor.ParamType.Enumerated.Element}" SelectedItem="{Binding param.AsEnumerated.EnumValue}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.EnumTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector>
|
||||
</ItemsControl.ItemTemplateSelector>
|
||||
</ItemsControl>
|
||||
<TextBlock Grid.Row="3">Child</TextBlock>
|
||||
<uag:OrderDetailsPopupControl
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<cv:KeyValue Key="{x:Null}" Value="{u:Visibility Visible}"/>
|
||||
</cv:ValueMap>
|
||||
|
||||
<ControlTemplate x:Key="HoverButtonOverlayTemplate2" TargetType="c:TickUpDown">
|
||||
<ControlTemplate x:Key="HoverButtonOverlayTemplate" TargetType="c:TickUpDown">
|
||||
<Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
@@ -86,8 +86,8 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="38"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<c:TickUpDown x:Name="PART_time_tick" HorizontalAlignment="Stretch" EnableArrowKeyTick="False" Template="{StaticResource HoverButtonOverlayTemplate2}">
|
||||
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True">
|
||||
<c:TickUpDown x:Name="PART_time_tick" HorizontalAlignment="Stretch" EnableArrowKeyTick="False" Template="{StaticResource HoverButtonOverlayTemplate}">
|
||||
<TextBox x:Name="PART_time_text" u:TextBoxBehavior.CommitOnEnter="True" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int 1}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int -1}"/>
|
||||
@@ -95,15 +95,15 @@
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
</c:TickUpDown>
|
||||
<c:TickUpDown x:Name="PART_month_tick" Margin="2,0,0,0" Grid.Column="1" Template="{StaticResource HoverButtonOverlayTemplate2}">
|
||||
<c:TickUpDown x:Name="PART_month_tick" Margin="2,0,0,0" Grid.Column="1" Template="{StaticResource HoverButtonOverlayTemplate}">
|
||||
<TextBox x:Name="PART_month_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
|
||||
</c:TickUpDown>
|
||||
<TextBlock Grid.Column="2" Margin="2,0">/</TextBlock>
|
||||
<c:TickUpDown x:Name="PART_day_tick" Grid.Column="3" Template="{StaticResource HoverButtonOverlayTemplate2}">
|
||||
<c:TickUpDown x:Name="PART_day_tick" Grid.Column="3" Template="{StaticResource HoverButtonOverlayTemplate}">
|
||||
<TextBox x:Name="PART_day_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
|
||||
</c:TickUpDown>
|
||||
<TextBlock Grid.Column="4" Margin="2,0">/</TextBlock>
|
||||
<c:TickUpDown x:Name="PART_year_tick" Grid.Column="5" Template="{StaticResource HoverButtonOverlayTemplate2}">
|
||||
<c:TickUpDown x:Name="PART_year_tick" Grid.Column="5" Template="{StaticResource HoverButtonOverlayTemplate}">
|
||||
<TextBox x:Name="PART_year_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
|
||||
</c:TickUpDown>
|
||||
<TextBlock Grid.Column="0" Text="hh:mm tt" Style="{StaticResource TextPromptStyle}" Visibility="{Binding Text, ElementName=PART_time_text, Converter={StaticResource NullOrEmptyVisibilityConverter}}"/>
|
||||
|
||||
@@ -18,6 +18,11 @@ namespace mbts.ui.controls {
|
||||
public static readonly RoutedCommand TickTimeComponentCommand = new RoutedCommand();
|
||||
|
||||
DTPController _controller;
|
||||
TextBox
|
||||
_month_text,
|
||||
_day_text,
|
||||
_year_text,
|
||||
_time_text;
|
||||
bool _suspend_notify_vm;
|
||||
|
||||
static DateTimePicker() =>
|
||||
@@ -34,18 +39,25 @@ namespace mbts.ui.controls {
|
||||
|
||||
IList<IDisposable> _hooks = new List<IDisposable>();
|
||||
|
||||
public void flush_pending_changes() {
|
||||
_month_text?.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
_day_text?.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
_year_text?.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
_time_text?.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
var month = this.assert_template_child<TickUpDown>("PART_month_tick");
|
||||
var month_text = this.assert_template_child<TextBox>("PART_month_text");
|
||||
_month_text = this.assert_template_child<TextBox>("PART_month_text");
|
||||
var day = this.assert_template_child<TickUpDown>("PART_day_tick");
|
||||
var day_text = this.assert_template_child<TextBox>("PART_day_text");
|
||||
_day_text = this.assert_template_child<TextBox>("PART_day_text");
|
||||
var year = this.assert_template_child<TickUpDown>("PART_year_tick");
|
||||
var year_text = this.assert_template_child<TextBox>("PART_year_text");
|
||||
_year_text = this.assert_template_child<TextBox>("PART_year_text");
|
||||
var time = this.assert_template_child<TickUpDown>("PART_time_tick");
|
||||
var time_text = this.assert_template_child<TextBox>("PART_time_text");
|
||||
_time_text = this.assert_template_child<TextBox>("PART_time_text");
|
||||
|
||||
if (_controller != null) _controller.PropertyChanged -= _vm_PropertyChanged;
|
||||
_controller = new DTPController(time_text);
|
||||
_controller = new DTPController(_time_text);
|
||||
_controller.result = TimeTuple;
|
||||
_controller.PropertyChanged += _vm_PropertyChanged;
|
||||
time.CommandBindings.Add(new CommandBinding(TickTimeComponentCommand, (s, e) => {
|
||||
@@ -53,53 +65,52 @@ namespace mbts.ui.controls {
|
||||
}));
|
||||
time.CommandBindings.Add(new CommandBinding(TextBoxBehavior.SelectAllCommand, TextBoxBehavior.SelectAllCommandExecutedHandler));
|
||||
|
||||
const int delay = 500;
|
||||
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _controller });
|
||||
time.TickUpParameter = 1;
|
||||
time.TickDownParameter = -1;
|
||||
time.TickUpAltParameter = 60;
|
||||
time.TickDownAltParameter = -60;
|
||||
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _controller, Delay = delay, ValidatesOnExceptions = true });
|
||||
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) => {
|
||||
string preview = time_text.PeekText(e);
|
||||
_time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _controller, ValidatesOnExceptions = true });
|
||||
_hooks.Add(_time_text.SubscribePreviewTextInput((s, e) => {
|
||||
string preview = _time_text.PeekText(e);
|
||||
if (RegexFilters.Time.StandardTimeOptionalSeconds.IsMatch(preview)) {
|
||||
_controller.use_military_time = false;
|
||||
} else if (RegexFilters.Time.MilitaryTimeOptionalSeconds.IsMatch(preview)) {
|
||||
_controller.use_military_time = true;
|
||||
} else {
|
||||
e.Handled = true;
|
||||
e.Handled = !RegexFilters.Time.FlexibleTime.IsMatch(preview);
|
||||
}
|
||||
}));
|
||||
|
||||
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _controller });
|
||||
month.TickUpParameter = 1;
|
||||
month.TickDownParameter = -1;
|
||||
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _controller, Delay = delay });
|
||||
_hooks.Add(month_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.MonthAbbrev.IsMatch(month_text.PeekText(e))
|
||||
_month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _controller });
|
||||
_hooks.Add(_month_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.MonthAbbrev.IsMatch(_month_text.PeekText(e))
|
||||
));
|
||||
|
||||
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _controller });
|
||||
day.TickUpParameter = 1;
|
||||
day.TickDownParameter = -1;
|
||||
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _controller, Delay = delay });
|
||||
_hooks.Add(day_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.DaysInMonth.IsMatch(day_text.PeekText(e))
|
||||
_day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _controller });
|
||||
_hooks.Add(_day_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.DaysInMonth.IsMatch(_day_text.PeekText(e))
|
||||
));
|
||||
|
||||
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _controller });
|
||||
year.TickUpParameter = 1;
|
||||
year.TickDownParameter = -1;
|
||||
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _controller, Delay = delay });
|
||||
_hooks.Add(year_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.Year.IsMatch(year_text.PeekText(e))
|
||||
_year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _controller });
|
||||
_hooks.Add(_year_text.SubscribePreviewTextInput((s, e) =>
|
||||
e.Handled = !RegexFilters.Time.Year.IsMatch(_year_text.PeekText(e))
|
||||
));
|
||||
}
|
||||
|
||||
void _vm_PropertyChanged(object sender, PropertyChangedEventArgs e) {
|
||||
if (e.PropertyName == nameof(DTPController.result)) {
|
||||
_suspend_notify_vm = true;
|
||||
TimeTuple = _controller.result;
|
||||
SetCurrentValue(TimeTupleProperty, _controller.result);
|
||||
_suspend_notify_vm = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace mbts.ui.converters {
|
||||
private readonly Func<TIn, TOut> _convert;
|
||||
private readonly Func<TOut, TIn> _convert_back;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert(ObjectHelper.try_convert<TIn>(value)); }
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert_back(ObjectHelper.try_convert<TOut>(value)); }
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert(ConvertUtils.try_convert<TIn>(value)); }
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return _convert_back(ConvertUtils.try_convert<TOut>(value)); }
|
||||
}
|
||||
|
||||
public class AnonymousMultiValueConverter<TIn1, TIn2, TOut> : IMultiValueConverter {
|
||||
@@ -49,7 +49,7 @@ namespace mbts.ui.converters {
|
||||
public TOut ErrorValue { get; set; }
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
|
||||
ObjectHelper.try_convert_args(values, out TIn1 a, out TIn2 b) ? _convert(a, b) : ErrorValue;
|
||||
ConvertUtils.try_convert_args(values, out TIn1 a, out TIn2 b) ? _convert(a, b) : ErrorValue;
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace mbts.ui.converters {
|
||||
public TOut ErrorValue { get; set; }
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
|
||||
ObjectHelper.try_convert_args(values, out TIn1 a, out TIn2 b, out TIn3 c) ? _convert(a, b, c) : ErrorValue;
|
||||
ConvertUtils.try_convert_args(values, out TIn1 a, out TIn2 b, out TIn3 c) ? _convert(a, b, c) : ErrorValue;
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using mbts.utils;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
@@ -19,7 +19,7 @@ namespace mbts.ui.converters
|
||||
}
|
||||
|
||||
public class DynamicValueEquals<T> : DynamicValueEquals {
|
||||
protected override bool ValuesEqual(object x, object y) { return ObjectHelper.try_convert(x, out T tx) && ObjectHelper.try_convert(y, out T ty) && ValuesEqual(tx, ty); }
|
||||
protected override bool ValuesEqual(object x, object y) { return ConvertUtils.try_convert(x, out T tx) && ConvertUtils.try_convert(y, out T ty) && ValuesEqual(tx, ty); }
|
||||
protected virtual bool ValuesEqual(T x, T y) { return CompareUtils.NullCheckEquals(x, y); }
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace mbts.ui.converters
|
||||
}
|
||||
|
||||
public class StaticValueEqualsConverter<T> : StaticValueEqualsConverter {
|
||||
protected override bool ValueEquals(object value) { return ObjectHelper.try_convert(value, out T result) && ValueEquals(result); }
|
||||
protected override bool ValueEquals(object value) { return ConvertUtils.try_convert(value, out T result) && ValueEquals(result); }
|
||||
protected virtual bool ValueEquals(T value) { return base.ValueEquals(value); }
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace mbts.ui
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object parameter) {
|
||||
return !_is_executing && (_can_execute == null || _can_execute(ObjectHelper.try_convert<T>(parameter)));
|
||||
return !_is_executing && (_can_execute == null || _can_execute(ConvertUtils.try_convert<T>(parameter)));
|
||||
}
|
||||
|
||||
public void RaiseCanExecuteChanged() {
|
||||
@@ -45,7 +45,7 @@ namespace mbts.ui
|
||||
_is_executing = true;
|
||||
RaiseCanExecuteChanged();
|
||||
try {
|
||||
var w = await _factory(ObjectHelper.try_convert<T>(parameter));
|
||||
var w = await _factory(ConvertUtils.try_convert<T>(parameter));
|
||||
if (w != null) {
|
||||
if (_off_x.HasValue && _off_y.HasValue)
|
||||
w.move_to_mouse(_off_x.Value, _off_x.Value);
|
||||
|
||||
@@ -28,12 +28,12 @@ namespace mbts.ui
|
||||
private IDisposable _dialog;
|
||||
public Predicate<T> can_execute { get; set; }
|
||||
|
||||
public bool CanExecute(object parameter) { return can_execute == null ? true : can_execute(ObjectHelper.try_convert<T>(parameter)); }
|
||||
public bool CanExecute(object parameter) { return can_execute == null ? true : can_execute(ConvertUtils.try_convert<T>(parameter)); }
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public void Execute(object parameter) {
|
||||
Disposable.dispose(ref _dialog);
|
||||
Window w = _factory(ObjectHelper.try_convert<T>(parameter));
|
||||
Window w = _factory(ConvertUtils.try_convert<T>(parameter));
|
||||
if (w != null) {
|
||||
if (_try_dispose_vm_on_close) {
|
||||
w.Closed += (s, e) => {
|
||||
|
||||
@@ -101,10 +101,10 @@ namespace mbts.ui {
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
public bool CanExecute(object parameter) => _can_execute(ObjectHelper.try_convert<TParam>(parameter));
|
||||
public bool CanExecute(object parameter) => _can_execute(ConvertUtils.try_convert<TParam>(parameter));
|
||||
public void Execute(object parameter) {
|
||||
_safe_close_dialog();
|
||||
TParam tparam = ObjectHelper.try_convert<TParam>(parameter);
|
||||
TParam tparam = ConvertUtils.try_convert<TParam>(parameter);
|
||||
_dialog = _dialog_factory(tparam);
|
||||
if (_dialog.ShowDialog() == DialogResult.OK)
|
||||
_commit(tparam, _dialog.FileName);
|
||||
|
||||
@@ -63,5 +63,16 @@ namespace mbts.ui
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable SubscribeDisposed(this IWillLetYouKnowWhenIDispose dispose_broadcaster, Action callback) =>
|
||||
dispose_broadcaster.SubscribeDisposed(_ => { callback?.Invoke(); callback = null; });
|
||||
|
||||
public static IDisposable SubscribeDisposed(this IWillLetYouKnowWhenIDispose dispose_broadcaster, IDisposable dispose) =>
|
||||
dispose_broadcaster.SubscribeDisposed(_ => Disposable.dispose(ref dispose));
|
||||
|
||||
public static IDisposable SubscribeDisposed(this IWillLetYouKnowWhenIDispose dispose_broadcaster, Action<bool> callback) {
|
||||
dispose_broadcaster.OnDisposed += callback;
|
||||
return Disposable.Create(() => dispose_broadcaster.OnDisposed -= callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace mbts.ui {
|
||||
|
||||
public static T get_template_child<T>(this Control control, string name) {
|
||||
object obj = get_template_child(control, name);
|
||||
return ObjectHelper.try_convert<T>(obj);
|
||||
return ConvertUtils.try_convert<T>(obj);
|
||||
}
|
||||
|
||||
public static bool try_get_template_child(this Control control, string name, out object result) {
|
||||
@@ -40,7 +40,7 @@ namespace mbts.ui {
|
||||
|
||||
public static bool try_get_template_child<T>(this Control control, string name, out T result) {
|
||||
if (try_get_template_child(control, name, out object obj))
|
||||
return ObjectHelper.try_convert(obj, out result);
|
||||
return ConvertUtils.try_convert(obj, out result);
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -25,24 +25,17 @@ namespace mbts.ui.shell
|
||||
internal class LoginContainer
|
||||
{
|
||||
[ImportingConstructor]
|
||||
public LoginContainer(UserService user, [Import("settings")] PersistenceService persist, [Import("autologin", AllowDefault=true)]string auto_login) {
|
||||
if (string.IsNullOrEmpty(auto_login)) {
|
||||
var model = LoginModel.instance;
|
||||
model.provide_services(user, persist);
|
||||
// await_login pumps the dispatcher but doesn't allow control to return to MEF until the username and workspace are available
|
||||
model.await_login();
|
||||
var u = model.get_user();
|
||||
UserName = u.name;
|
||||
UserId = u.id;
|
||||
Workspace = model.get_workspace();
|
||||
model.Dispose();
|
||||
}
|
||||
else {
|
||||
UserName = auto_login;
|
||||
Environment.SetEnvironmentVariable("BTS_USER", UserName);
|
||||
Workspace = string.Empty;
|
||||
LoginModel.instance.Dispose();
|
||||
}
|
||||
public LoginContainer(UserService user, [Import("settings")] PersistenceService persist) {
|
||||
var model = LoginModel.instance;
|
||||
model.provide_services(user, persist);
|
||||
// await_login pumps the dispatcher but doesn't allow control to return to MEF until the username and workspace are available
|
||||
model.await_login();
|
||||
var u = model.get_user();
|
||||
UserName = u.name;
|
||||
UserId = u.id;
|
||||
Token = u.token;
|
||||
Workspace = model.get_workspace();
|
||||
model.Dispose();
|
||||
}
|
||||
|
||||
[Export("username")]
|
||||
@@ -50,14 +43,22 @@ namespace mbts.ui.shell
|
||||
|
||||
[Export("userid")]
|
||||
public uint UserId { get; private set; }
|
||||
[Export]
|
||||
public AuthToken Token { get; private set; }
|
||||
|
||||
[Export("workspace")]
|
||||
public string Workspace { get; private set; }
|
||||
}
|
||||
|
||||
public struct User {
|
||||
public string name;
|
||||
public UInt32 id;
|
||||
public User(string name, uint id, AuthToken token) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.token = token;
|
||||
}
|
||||
public readonly string name;
|
||||
public readonly UInt32 id;
|
||||
public readonly AuthToken token;
|
||||
}
|
||||
|
||||
public class LoginModel : IDisposable
|
||||
@@ -117,7 +118,7 @@ namespace mbts.ui.shell
|
||||
_enum_workspace = new PreloginWorkspaceEnumerator(persist);
|
||||
var response = await _logon.get_users_async(new GetUsersRequest());
|
||||
if (response.success) {
|
||||
Users = response.value.Users.Select(x => new User { name = x.Login, id = x.Id }).OrderBy(u => u.name).ToArray();
|
||||
Users = response.value.Users.Select(x => new User(x.Login,x.Id, null)).OrderBy(u => u.name).ToArray();
|
||||
}
|
||||
else {
|
||||
ErrorMessage = "Logon: " + response.error_message;
|
||||
@@ -137,7 +138,7 @@ namespace mbts.ui.shell
|
||||
if (response.success) {
|
||||
if (response.value.IsValid) {
|
||||
_workspace = workspace;
|
||||
set_authenticated_user(new User { name =username, id = response.value.UserId});
|
||||
set_authenticated_user(new User(username, response.value.UserId, response.value.Token));
|
||||
return (true, "");
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -65,15 +65,13 @@ namespace mbts.ui.shell
|
||||
public string TitleOverride {
|
||||
get => _title_override;
|
||||
set {
|
||||
if (_title_override != value) {
|
||||
if (value == _model.Title) {
|
||||
_title_override = null;
|
||||
}
|
||||
else {
|
||||
_title_override = value;
|
||||
}
|
||||
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
|
||||
if (value == _model.Title) {
|
||||
_title_override = null;
|
||||
}
|
||||
else {
|
||||
_title_override = value;
|
||||
}
|
||||
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
|
||||
_edit_mode.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<UserControl x:Class="mbts.ui.controls.WorkspaceItemControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:mbts.ui.controls"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
mc:Ignorable="d"
|
||||
ui:StateManager.VisualState="{Binding VisualState}"
|
||||
>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="Overridden">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="_main_button"
|
||||
Storyboard.TargetProperty="Background.Color"
|
||||
To="{x:Static ui:BTSColors.success}"
|
||||
Duration="0:0:0.2"
|
||||
AutoReverse="True" RepeatBehavior="3x" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
|
||||
<Button x:Name="_main_button" Command="{Binding ActivateCommand}" Style="{DynamicResource DialogListButton}">
|
||||
<Grid>
|
||||
<Image Source="{Binding Icon}" HorizontalAlignment="Left" Height="16" Margin="4" />
|
||||
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="28,0,0,0" x:Name="text" />
|
||||
<Button Style="{StaticResource RoundCloseButton}" HorizontalAlignment="Right" VerticalAlignment="Center" Command="{Binding DeleteCommand}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
/// <summary>
|
||||
/// Interaction logic for WorkspaceItemControl.xaml
|
||||
/// </summary>
|
||||
public partial class WorkspaceItemControl : UserControl
|
||||
{
|
||||
public WorkspaceItemControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace mbts.ui.shell
|
||||
{
|
||||
public class WorkspaceViewModel : ViewModelBase
|
||||
{
|
||||
private const string INITIAL_VISUAL_STATE = "Normal";
|
||||
|
||||
WorkspacesModel _parent;
|
||||
public WorkspaceViewModel(WorkspacesModel parent, string name, WorkspaceDefinition definition) {
|
||||
_parent = parent;
|
||||
@@ -21,6 +23,25 @@ namespace mbts.ui.shell
|
||||
public object Icon {
|
||||
get { return Icons.Window; }
|
||||
}
|
||||
|
||||
private string _vstate = INITIAL_VISUAL_STATE;
|
||||
public string VisualState {
|
||||
get {
|
||||
// this unconventional getter acts as a reset after the VisualStateManager has played
|
||||
// i.e. if we overwrite a workspace then save, then close the window, when we open the
|
||||
// window back up, we'll see item animate b/c it has never been reset from the animate
|
||||
// state. This is necessary b/c the lifetime of Workspaces is not tied to the lifetime
|
||||
// of the View, rather it lives in the Model and is only instantiated on startup via MEF
|
||||
var return_val = _vstate;
|
||||
_vstate = INITIAL_VISUAL_STATE;
|
||||
return return_val;
|
||||
}
|
||||
set {
|
||||
_vstate = value;
|
||||
RaisePropertyChanged("VisualState");
|
||||
}
|
||||
}
|
||||
|
||||
internal WorkspaceDefinition Definition { get; private set; }
|
||||
|
||||
public ICommand ActivateCommand {
|
||||
|
||||
@@ -59,13 +59,7 @@
|
||||
<ItemsControl ItemsSource="{Binding Workspaces}" Style="{StaticResource CommonList}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Command="{Binding ActivateCommand}" Style="{DynamicResource DialogListButton}">
|
||||
<Grid>
|
||||
<Image Source="{Binding Icon}" HorizontalAlignment="Left" Height="16" Margin="4" />
|
||||
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" Margin="28,0,0,0" x:Name="text" />
|
||||
<Button Style="{StaticResource RoundCloseButton}" HorizontalAlignment="Right" VerticalAlignment="Center" Command="{Binding DeleteCommand}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
<controls:WorkspaceItemControl />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
Loaded 100 of 281 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user