Compare commits

..
Author SHA1 Message Date
Eric Harding dd5c680155 work in progress 2019-04-29 11:18:11 -04:00
1217 changed files with 19815 additions and 77018 deletions

No files matched your search

+34 -36
View File
@@ -1,36 +1,34 @@
nohup.out
build/
cmake-build-debug/
.idea/
.dir-locals.el
*.pyc
.vscode/*
!.vscode/*.json
.vs
obj/
*.csproj.user
*.vspx
*.psess
msbuild.log
*.orig
vcxproj.user
src/client/spark/Properties/launchSettings.json
compile_commands.json
.cquery_cached_index/
src/client/sparkcmd/Properties/launchSettings.json
node_modules
paket-files
project.lock.json
src/server/sparkd/orderman/perf_analysis/data
src/server/sparkd/orderman/perf_analysis/logs/
src/server/sparkd/orderman/perf_analysis/trace_data/
.fake
*.fsx.lock
*.code-workspace
src/lib/orderman/perf_analysis/logs/latency_data/
trace_data/
/src/lib/orderman/perf_analysis/last_pretty_log.csv
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
.ionide/*
/.ignore
nohup.out
build/
cmake-build-debug/
.idea/
.dir-locals.el
*.pyc
.vscode/*
!.vscode/*.json
.vs
obj/
*.csproj.user
*.vspx
*.psess
msbuild.log
*.orig
vcxproj.user
src/client/spark/Properties/launchSettings.json
compile_commands.json
.cquery_cached_index/
src/client/sparkcmd/Properties/launchSettings.json
node_modules
paket-files
project.lock.json
src/server/sparkd/orderman/perf_analysis/data
src/server/sparkd/orderman/perf_analysis/logs/
src/server/sparkd/orderman/perf_analysis/trace_data/
.fake
*.fsx.lock
*.code-workspace
src/lib/orderman/perf_analysis/logs/latency_data/
trace_data/
/src/lib/orderman/perf_analysis/last_pretty_log.csv
/src/lib/orderman/perf_analysis/log.csv
/doc/user/public/*
+93 -179
View File
@@ -1,14 +1,83 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
"args": ["run"],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "Client Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "SDK Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": ["md","GLBX","Future","CL", "CLZ9", "--user=bts", "--pass="],
"cwd": "${workspaceRoot}",
"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-packed.xll"],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": ["test","test_filter"],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Attach",
"type": "clr",
"request": "attach",
"processId":"${command:pickProcess}"
},
{
"name": "SparkVM Launch",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"run"
],
"args": ["run"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -33,115 +102,26 @@
"description": "LD LIBRARY",
"text": "set env LD_LIBRARY_PATH=.",
"ignoreFailures": false
}
}
],
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
"args": [
"run"
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
{
"name": "Client Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
"args": [],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "SDK Launch",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
"args": [
"md",
"GLBX",
"Future",
"CL",
"CLZ9",
"--user=bts",
"--pass="
],
"cwd": "${workspaceRoot}",
"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-packed.xll"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Tests",
"type": "clr",
"request": "launch",
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
"args": [
"test",
"ApiUpdateTests"
],
"cwd": "${workspaceRoot}",
"stopAtEntry": false,
"externalConsole": true,
"requireExactSource": false
},
{
"name": "Client Attach",
"type": "clr",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "Spreader Tests",
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"test",
"[spreader]"
],
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": ["test", "[spreader]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
"externalConsole": true,
"MIMode": "gdb",
@@ -159,9 +139,7 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -173,10 +151,7 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": [
"test",
"[admin_service]"
],
"args": ["test", "[admin_service]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -196,9 +171,7 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -210,10 +183,7 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
"args": [
"test",
"[limiter]"
],
"args": ["test", "[limiter]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
"environment": [],
@@ -233,9 +203,7 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
@@ -247,10 +215,7 @@
"type": "cppdbg",
"request": "launch",
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
"args": [
"test",
"[algo]"
],
"args": ["test", "[mbo]"],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server/",
"environment": [],
@@ -275,16 +240,14 @@
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
{
"name": "SparkVM attach",
"type": "cppdbg",
"request": "attach",
@@ -292,18 +255,16 @@
"processId": "${command:pickRemoteProcess}",
"MIMode": "gdb",
"pipeTransport": {
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/usr/bin/gdb"
"pipeCwd": "${workspaceRoot}",
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": ["bts@sparkvm"],
"debuggerPath": "/usr/bin/gdb"
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
"/home/bts/spark/": "${workspaceRoot}"
}
},
{
},
{
"name": "Python",
"type": "python",
"request": "launch",
@@ -313,59 +274,12 @@
"cwd": "${workspaceRoot}",
"env": {},
"envFile": "${workspaceRoot}/.env",
"args": [
"DEBUG"
],
"args": [ "DEBUG" ],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},
{
"name": ".NET Core Remote Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickRemoteProcess}",
"pipeTransport": {
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": true
},
{
"name": ".NET Core Remote Launch",
"type": "coreclr",
"request": "launch",
"program": "/usr/bin/dotnet",
"args": [
"sparkcmd.dll",
"test",
"test_join_create_and_takeout_market"
],
"stopAtEntry": false,
"cwd": "/home/bts/spark/build/ninja_debug/server",
"pipeTransport": {
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
"pipeArgs": [
"bts@sparkvm"
],
"debuggerPath": "/home/bts/vsdbg/vsdbg",
"pipeCwd": "${workspaceRoot}",
"quoteArgs": true
},
"sourceFileMap": {
"/home/bts/spark/": "${workspaceRoot}"
},
"justMyCode": false
},
]
}
+1 -1
View File
@@ -1 +1 @@
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Help/index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Launcher/index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Logging-In-to-the-System/index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/index.md;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/My-Windows/index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Settings/index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Spreader/index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/index.md;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/bts_spark_sdk/Client/Overview.md;content/bts_spark_sdk/Client/Threading.md;content/bts_spark_sdk/Client/_index.md;content/bts_spark_sdk/Server/_index.md;content/bts_spark_sdk/_index.md;content/release_notes/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+1
View File
@@ -7,6 +7,7 @@ zola_path=$(readlink -f "../../externals/linux/bin/zola/zola")
mkdir -p $temp_path
cp -r ./* $temp_path
cp -r ../../src/sdk/doc/* $temp_path/content/bts_spark_sdk/Server/
cd $temp_path
$zola_path build -u '/docs/' -o $1 >$tmpfile 2>&1
@@ -0,0 +1,46 @@
+++
title = "BTS Basic Algos"
weight = 10
sort_by = "weight"
insert_anchor_links = "right"
+++
## Lean
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
##### Parameters
* Cross Thresh - size thresh in absolute or ratio terms
* Max Chase Ticks - max levels to pay up, in ticks
## Join
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
##### Parameters
* Join Thresh - size thresh in absolute or ratio terms
## Top
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
##### Parameters
* Top Thresh - size thresh in absolute or ratio terms
## Cover
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
##### Parameters
* Profit Ticks - ticks above or below fill price to place profit / cover order
## OCO
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
Composing this order with Cover simulates scalping functionality
##### Parameters
* Stop Ticks - ticks below or above main order to place stop / other order
@@ -1,77 +0,0 @@
+++
title = "Basic Algos"
weight = 1
sort_by = "weight"
insert_anchor_links = "right"
aliases = ["./bts_spark/Algos/"]
+++
## Lean
The goal of the lean order is to watch the opposite-inside market size and cross when that size goes below the threshold (this also implies a cross if that size goes to zero and the market trades away from you) If the market trades toward you, you can opt into several different behaviors
##### Parameters
* Cross Thresh - size thresh in absolute or ratio terms
* Max Chase Ticks - max levels to pay up, in ticks
## Join
The goal of the join order is to watch the opposite-inside market and join when the market flips (that is, when the best offer becomes the best bid) and has at least threshold size.
##### Parameters
* Join Thresh - size thresh in absolute or ratio terms
## Top
The goal of the top order is to not trade by yourself, but the intent at the time of order entry is to get filled.If the order is on the market (say current bid price) AND the quantity on that bid price is below top thresh then cancel the order else remain working.
##### Parameters
* Top Thresh - size thresh in absolute or ratio terms
## Cover
A Cover order’s purpose is to fire a hedge order (of any type) in response to fills from it’s child order on the opposite side of the market N ticks away
##### Parameters
* Profit Ticks - ticks above or below fill price to place profit / cover order
## OCO
An OCO order creates a relationship between a child order and a 'stop' or 'other' order such that when one of these is cancelled, both cancel, or when one is filled, the other order's size is debited in the amount of the fill.
Composing this order with Cover simulates scalping functionality
##### Parameters
* Stop Ticks - ticks below or above main order to place stop / other order
## Splitter
The splitter is useful for pro-rata markets where you want to vary your exposure independent of the order quantity that you want to fill. The splitter will complete when the number of fills reaches or exceeds the order quantity but it will put exposed_qty total size on the market. Splitter favors order size over queue position.
If exposed_qty is set to 0 the splitter will continue to exist in the Order Book but will cancel all of its child orders. This makes it a useful building block for custom algos that need to implement a 'paused' state.
##### Parameters
* exposed_qty - The total quantity to expose on the market.
* max_order_qty - The maximum size of a single order.
## Scratch
The scratch algo is similar to Lean, but instead of watching the opposite inside market, it watches the same side market and pays up when this quantity recedes below the scratch_threshold
##### Parameters
* scratch_threshold - The quantity on the same side market below which to scratch
* chase_ticks - The number of ticks through the opposite market to pay up
## ProRata
The prorata algo is a special case hedging algo for pro-rata markets. Users can specify two hedge types-- normal hedge and overfill hedge, the latter is spawned when you receive more fills than your fill_limit. Further, the algo attempts to scratch when in an overfilled state AND optionally the same side size falls below stay_in_threshold
##### Parameters
* hedge_ticks - The amount of ticks better hedges are placed on the opposite side market as the fill
* chase_ticks - How many ticks through the opposite market on a scratch
* fill_limit - The limit after which the algo considers further fills to be 'overfills'
* stay_in_threshold - A same side size threshold that enables the algo to 'stay in' even if overfilled
* enable_scratch_on_overfill - When true, overfills are immediately scratched upon the algo exiting
* enable_stay_in - When true, the algo will 'stay in' so long as the same side size is above stay_in_threshold, when false the algo attempts to 'get out' (and potentially scratch overfills) as soon as it reaches overfill status
* hedge - Child order type to send for normal hedges
* overfill_hedge - Child order type to send for overfill hedges
+1 -280
View File
@@ -4,283 +4,6 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.10.0
### User Visible Changes
- Fills now include information about the algo that originated the fill
- A new, more powerful PnL form is now available
### API Changes
- Added security state information (pre open, halted, normal, closed, etc)
### Bug Fixes
- Brought back the "select all" check box in the mini order book
- Fixed an issue in the market data subsystem that on rare occasions could result in a startup crash
### Internal Changes
- Fixed an issue that was stopping us from interpreting SOD times as local rather than GMT
## 2.9.3
### Internal Changes
- Fixed system test scripts
## 2.9.2
### Bug Fixes
- Fixed an issue with the exchange simulator that could lead to crossed markets
## 2.9.1
### Bug Fixes
- Fixed an issue with trade sound alerts, where in some cases the use of a "In Between" condition could lead to a UI failure.
- Fixed an issue with the display of the new raw position column in the market view window.
- Fixed the popup label that describes the raw position information in the ladder
### Internal Changes
- Avoided shipping production configuration defaults that lead to trying to download a position file for the development sftp server.
### Api
- C# API sample now uses synchronous calls.
## 2.9.0
### User Visible Changes
- Improved Workspace management User Interface
- Added the ability to change grid background colors
- The Spark UI Window group now has a title in the Windows task bar
- Added sound alerts for trade events, see Launcher -> Settings -> Alerts
- Allowed composite algos that are attached to existing orders to identify fills that were already present prior to attachment
- Added import facilities for GMI position files
- Added the ability for risk managers to enter manual fills
- Added "raw" P&L columns, that is columns that *do not* include spread legs in their accounting
### Bug Fixes
- Fixed the settings action menu (Load/Save/Reset to Factory)
- Fixed the method used to determine if a security is expired to take into account the start of day time
### API Changes
- Fixed an issue in the Client SDK that could lead to occasional failures to connect to the spark server as well as excessive memory usage.
- Added the ability for algo orders to distinguish fills existing prior to launch
### Internal Changes
- Changed how we packaged certain third-party components
- Added logging of entitlement status
- Tagged outgoing orders with sequence number of marketdata or FIX event that triggered them
## 2.8.3
### Bug Fixes
- Fixed a bug that could cause an OrderBook's Security column filter to be blank
- Fixed a bug that could cause a UI failure when clearing a filter selection on a filtering widget
## 2.8.2
### Bug Fixes
- Added proper display factors for SOM spreads
- Fixed a bug that could cause a UI failure when trying to change settings in a synthetic ladder
## 2.8.1
### Bug fixes
- Fixed an error in the UI grids filtering widget
## 2.8.0
### User Visible Changes
- Added ProRata and Scratch algos
- Ladder badge now grows/shrinks to fill the containing cell
- Attached algo parameters are now displayed in the update panel
- Fixed a bug where the update panel would show '-' values when only
1 order was selected
- Fixed a bug that cause cause a failure on startup if the user
loaded a workspace with a Ladder that had a dynamic order type
selected by default
- Increased width of login panel to better show login error messages
- Added optional 'Maker/Taker' pricing to Spreader algo
- Algo buttons in ladder can now be made to deactivate after launching an algo or remain 'sticky'
### Internal changes
- Made it easier to generate simple UI's for new order types
### API Changes
- Dynamic orders can now use QuantityMeasure parameter types
## 2.7.2
### User Visible Changes
- Changed the login dialog to avoid leaking workspace names from other users.
- Added new Splitter algo. From the manual: The splitter is useful
for pro-rata markets where you want to vary your exposure
independent of the order quantity that you want to fill. The
splitter will complete when the number of fills reaches or exceeds
the order quantity but it will put exposed_qty total size on the
market. Splitter favors order size over queue position.
### Internal Changes
- Made spark more resilient to lack of connectivity to the CME web site for downloading of templates.
## 2.7.1
### User Visible Changes
- Fixed some issues with the Cover algo, where in some circumstances
it would erroneously cancel launched hedges on completion.
## 2.7.0
### User Visible Changes
- Allowed algos to process "in-flight" fills while shutting down
- Changed the cover algo to continue to hedge while shutting down
### API Changes
- Changed the API so that algos do not fully terminate until all
their children are terminated. Algos that are in the process of
shutting down in response to a complete or cancel call will still
receive child updates, which they can use to calculate and report
an accurate fill count (during this shutdown stage algos cannot
create, cancel or update children). A new IAlgoServices method,
is_shutting_down(), can be used to determine if an algo is
shutting down.
## 2.6.2
### User Visible Changes
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
- Fixed a rare race condition that could lead to erroneous limit enforcement
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
## 2.6.1
### User Visible Changes
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
## 2.6.0
### User Visible Changes
- In the ladder made last trade more distinctive when overlayed on the volumn column
- In the ladder, corrected display of volume tool tip
- In the ladder, changed the consolidation of last trade to consider only the price ignoring aggressor changes
- Added an option for the Top Algo to delete itself if within N ticks from top of book
- Address a rare issue that could lead to an error report on UI exit
- Added margin risk limits
- Added the "Offset" algo. Offset prices a child algo at a parameterizable distance from the top of the book
- Avoided switching the current account and order type on open ladders when limits are changed
### Internal Changes
- Ensure that admin logins are always allowed to login, regardless of keycode status.
## 2.5.0
### User Visible Changes
- Added loss limit risk check
- Made algos, synthetics and user names private so that multiple users can share a single system privately.
- Allowed the mini order book defaults for column widths to be changed from the mini order book
- Allowed the mini order book to shrink to the minimum size that will be needed to display the selected columns
- Improved accuracy of the directional arrow when displaying last trades in the volumn column
## 2.4.0
### User Visible Settings
- In the ladder, changed the last trade column to accumulate successive trades at the same price
- Fixed a bug in the order book where the right side ladder would display the top leve order's security rather then current row's
- Added OK, Cancel buttons to the security picker for ladder profiles or sounds
- Added ladder settings for the mini order book
- Added a safety check to guard against unintentional market crosses
- Improved the Queue Holder mode in the spreader:
- Added a Show Quantity, allowing the spreader to keep queue position of the trailing orders as Fills (of size Show Qty) happen
- Improved spreader performance when in Queue Holder mode
### Internal changes
- Suppressed order callbacks for completed/canceled orders. This should eliminate some bogus error logs by spreader orders.
- Addressed a potential concurrency issues with service initialization.
## 2.3.0
### User Visible Changes
- Introduced multiple settings profiles for the ladder
- This allows multiple standard configuration for the ladder, optionally with security specific variations
- Made Cover order more independent from its hedging chidren:
- Cover continues to operate even if an hedging child is canceled
- When a cover is canceled it will detach from any running hedging children, instead of canceling them
- Changed Lean, Join and Cover algos to hide themselves in the ladder once they have completed their work
- Changed manual fills to appear in day PnL rather than in the SOD records
- Add buy/sell coloring to fill rows in fills window
- Streamlined the mini order book:
- Display of the mini order book popup is quicker
- Only exchange orders are displayed
- Update buttons are only displayed for orders that are under control of an algo with the update applying to the entire algo, not just the exchange order
- We can now select multiple rows to attach, detach, update or cancel multiple orders in a batch
- A checkbox column has been added to make clearer which rows are selected
### API Changes
- Enhanced the API to allow algos to implement custom cancel handling
- Allowed algos to control their visibility in the ladder through the API
### Internal Changes
- Added system requirements to Spark documentation
- Implemented keycodes
- Temporarily rejected stop and stop limit orders, pending bug fixes
## 2.2.0
### User Visible Changes
- Made our orders easier to spot in MBO display
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
- Fixed Net/Absolute radio button in risk admin UI
- Fixed double counting of spread legs in limits
- Removed expired fills from the system
- Improved handling of orders with undiscovered securities
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
- Fixed display of algo studio algo types in order book
- Hid net change setting in synthetic ladders
- Fixed a number of problems with filtering on the Fills form
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
- Added security symbol column to fills window
- Re-organized ladder settings dialog to make it clearer and easier to use
## 2.1.4
### User Visible Changes
- Fixed handling of cancel button in security picker
## 2.1.3
### User Visible Changes
- Fixed a simulator bug in the handling of 0 sized orders
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
## 2.1.1
### User Visible Changes
- Added PIQ to GLBX Iceberg orders
- Corrected handling of stop limit orders
## 2.1.0
### User Visible Changes
- Added support for the GLBX Iceberg order type
- Allowed quick algo param edits by middle-clicking on either the bid/ask columns or a "favorite" button
- Added support for manual fills, either entered manually or imported from file. Manual fills are used only for P&L purposes, they have no impact on limits
- Allowed the LTQ and VAP values to be overlayed on the same ladder column
- Added day position information to the ladder
- Added PIQ to the mini order book
- Fixed a rare bug that could lead to server failure when requesting market data
- Fixed a bug that could cause incorrect price displays for certain types of synthetic securities
## 2.0.5
### User Visible Changes
- Fixed a bug in the handling of rejected QHO orders
- Implemented stricter position limit enforcement
- Fixed a bug that could cause server failure when trying to attach to already attached orders
## 2.0.4
### User Visible Changes
- Fixed performance regression in order manager
### Internal changes
- Improved market data diagnostics for bad configurations
- Changed market data default buffer size
## 2.0.3
### Internal changes
- Improved backwards compatibility in UI settings
## 2.0.2
### User Visible Changes
- Middle click the ladder price column to show the orders for that price level
- Middle click the bid or ask column in the ladder no longer sends an order
### Internal changes
- Improved market by order performance under heavy load
- Fixed a concurrency bug in SIM
## 2.0.0
### User Visible Changes
- Support for attaching and detaching algos to existing orders
- Support for display and update of all parameters in running algos
- Streamlining of order badge decorations in ladder
- New order search/filtering algorithm for order book
- Simpler representation of complex algo chains in order book and mini order book
- Visual and usability improvements to mini order book
- Streamlining of algo parameter dialog in ladder
- New spreader queue holder behavior
- Visual improvements to the order book
- New server side API
- Improved admin UI performance
- Optimized PIQ rendering
### Internal changes
- Exchange simulator no longer requires daily sequence number reset
- Improved MD resilience to corrupted shared memory
- Native orders can now buffer updates/cancels when disconnected or pending first acknowledgment
- Improved settings manager tool output
- Added diagnostics in the event MD memory corruption is detected
- Added systems tests for join,lean, iceberg and spreader orders
- Made exchange order recovery more resilient in the presence of database loss
## 1.6.2
### User Visible Changes
### Exchange simulator
- Exchange simulator now now produce position in queue data
## 1.6.0
### User Visible Changes
#### New Features
@@ -300,7 +23,7 @@ insert_anchor_links = "right"
- Integrated stunnel into UI to make VPN setup unnecessary in unsecured environments.
## 1.5.7
### User Visible Changes
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/_index.md#algo-studio) and [Spreader](./bts_spark/Spreader/_index.md)
- New videos have been added to the manual describing the [Algo Studio](./bts_spark/Trading/index.md#algo-studio) and [Spreader](./bts_spark/Spreader/index.md)
- Fixed a bug that occasionally would cause windows to be position outside the visible screen on startup
- Fixed a bug in the order book form that would occasionally cause the Recreate Order option to be unavailable
- Added a confirmation step to Cancel All, Cancel Buys and Cancel Sells commands
@@ -424,7 +147,6 @@ insert_anchor_links = "right"
- The zoom functionality can also be performed via hotkey and mouse wheel combinations
- Zoom In = CTRL + Mouse wheel up
- Zoom Out = CTRL + Mouse wheel down
- Reset zoom = CTRL + Mouse wheel click or double click on the zoom slider
### Exchange simulator
- Improved the exchange simulator to track real market data more closely
@@ -531,7 +253,6 @@ insert_anchor_links = "right"
- 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
@@ -1,55 +0,0 @@
+++
title = "System Requirements"
weight = 4
sort_by = "weight"
insert_anchor_links = "right"
+++
### Minimum Workstation
* New XPS Tower
* 8th Generation Intel® Core™ i7-8700 6-Core Processor (12M Cache, up to 4.6 GHz)
* 16GB, 2666MHz, DDR4 up to 64GB
* Windows 10 Professional 64-bit
* NVIDIA® GeForce® GTX 1070 with 8GB GDDR5 Graphics Memory
* 1 x 2.5" 256GB SATA Solid State Drive
### Recommended Workstation
* Similar or better than:
* Dell Precision Tower 5820
* Intel Xeon W-2145 3.7GHz, 4.5GHz Turbo, 8C, 11M Cache, HT, (140W) DDR4-2666
* Windows 10 Professional 64-bit
* NVIDIA® Quadro® P1000, 4GB, 4 mDP
* 16GB (4x4GB) 2133MHz DDR4 RDIMM ECC
* 2 x 2.5" 256GB SATA Solid State Drive running RAID1
### BTS Recommended Server Specifications
* Dell R640
* 2x Intel Xeon Gold 6148 2.40GHz 20-Core Processors. For better performance BTS recommends Intel® Xeon® Platinum 8268 2.9G, 24 Core CPU
* 192GB RAM 2666MHz DDR4
* 3x 480GB SSD RAID 5 HBA Minicard
* iDRAC 9 Enterprise
* Embedded Broadcom NIC
* Solarflare SFN8522-R2
* Redundant Power Supply
### BTS Minimum Server Specifications
* Dell R620
* Chassis with up to 8, 2.5” Hard Drives, up to 2 PCIe Slots (With Optional Riser)
* 2 x Intel® E5-2670 (16 total cores/ 32 total threads)
* 64GB total memory
* RAID 1 for H330/H730/H730P
* PERC H730 RAID Controller, 1GB NV Cache
* 3 x 300GB 10K RPM SATA 6Gbps 2.5in Hot-plug Hard Drive (RAID 5)
* iDRAC8 Enterprise, integrated Dell Remote Access Controller, Enterprise (varies by hosting company)
* Solarflare Ultra SFN7122F Dual-Port 10GbE (10 GB connectivity requirements, copper vs optical, varies by hosting company).
* Dual, Hot-plug, Redundant Power Supply (1+1), 750W
* Performance BIOS Setting
* ReadyRails Sliding Rails Without Cable Management Arm (varies by hosting company)
### Edge Tablet Requirements
* Similar or better than:
* Microsoft Surface Pro 4 Or Motion R12
* Intel i7
* 8 GB DDR4 RAM
+5
View File
@@ -1,6 +1,7 @@
#/bin/bash
#python -m mkdocs build -d site
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
./run_zola.sh build
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
@@ -13,3 +14,7 @@ sudo mv ./public/* /var/www/documentation/
EOF
rm -r public
cd ./content/bts_spark_sdk/server/
find . -not -name '_index.md' -delete
cd ../..
+14 -6
View File
@@ -7,12 +7,12 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using bts.api.core;
using mbts.ui.shell;
namespace mbts.spark {
[Export]
class AdminModel : IDisposable {
public readonly UserTuple user;
readonly string _user_name;
public string user_name => _user_name;
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
@@ -24,14 +24,14 @@ namespace mbts.spark {
IDisposable _dispose;
[ImportingConstructor]
AdminModel(AdminService service, [Import]UserTuple user) {
this.user = user;
AdminModel(AdminService service, [Import("username")]string user_name) {
_user_name = user_name;
_dispose = service.subscribei_admin_data().Select(update => {
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), user.name));
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.TryFirst(), _user_name));
IEnumerable<BTSUserImmutable> active_users =
from admin_data in update.all
from bts_user in admin_data.Users
where bts_user.Name == user.name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
where bts_user.Name == user_name && bts_user.HasUserData && bts_user.UserData.HasAccountPermissions
select bts_user;
IEnumerable<BTSAccountImmutable> active_accounts =
@@ -62,6 +62,14 @@ namespace mbts.spark {
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
public UserTuple? get_current_user() {
foreach (var kv in _user_map) {
// just for good measure, there should never be > 1 element in _user_map, and it should always match _user_name
if (kv.Value == _user_name)
return new UserTuple(kv.Key, kv.Value);
}
return null;
}
public AccountTuple? get_default_account() {
foreach (var kv in _account_map) {
return new AccountTuple(kv.Key, kv.Value);
+1
View File
@@ -70,6 +70,7 @@ namespace mbts.ui.shell
{
Log.Gen.info("Application.OnExit");
if (_runtime != null) _runtime.Dispose();
utils.BTSNativeMethods.finit_spark_lib();
base.OnExit(e);
Environment.Exit(0);
}
+1 -2
View File
@@ -3,7 +3,6 @@ using mbts;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -23,7 +22,7 @@ namespace bts.ui {
IDisposable _dispose;
[ImportingConstructor]
public AboutDaemon(IBus bus, [Import(LoginContainer.Exports.UserName)]string username) {
public AboutDaemon(IBus bus, [Import("username")]string username) {
_user = username;
_bus = bus;
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
+2 -28
View File
@@ -10,7 +10,6 @@ using System.Reactive.Linq;
using System.Windows.Threading;
using System.Windows;
using System.Threading.Tasks;
using mbts.ui.shell;
namespace bts.spark
{
@@ -21,7 +20,7 @@ namespace bts.spark
IDisposable _dispose;
[ImportingConstructor]
public ErrorReporter(ErrorReportService svc, IBus bus, [Import(LoginContainer.Exports.UserName)] string username) {
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
_svc = svc;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
@@ -83,6 +82,7 @@ namespace bts.spark
}
}
//public static async Task<Result<T>>
public static async Task<(T value, bool success)> safe_execute<T>(this ErrorReporter error_reporter, Func<Task<Result<T>>> execute, Func<T, bool> get_success, Func<T, string> get_error_msg, string desc) {
try {
var result = await execute();
@@ -101,31 +101,5 @@ namespace bts.spark
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
public static async Task<TResult> safe_execute<T, TResult>(
this ErrorReporter error_reporter,
Func<Task<Result<T>>> execute,
Func<T, bool> get_success,
Func<T, string> get_error_msg,
Func<T, bool, TResult> result_selector,
string desc) {
//
try {
var result = await execute();
if (!result.success) {
_report_error(result.error_message);
} else if (!get_success(result.value)) {
_report_error(get_error_msg(result.value));
} else {
return result_selector(result.value, true);
}
} catch (Exception ex) {
_report_error(ex.ToString());
}
return result_selector(default, false);
//
void _report_error(string msg) =>
error_reporter.report_error($"Error {desc}: {msg}", AlgoStudioWindow.NAME, MessageBoxImage.Warning);
}
}
}
@@ -1,68 +0,0 @@
using bts.api;
using bts.shell;
using mbts;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.spark;
using mbts.ui.utilities;
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
using Disposable = mbts.Disposable;
namespace bts {
[Export(typeof(IShellDaemon))]
class GlobalAlertsDaemon : IShellDaemon, IDisposable {
const string
// used for logging/diagnostic purposes
_window_name = "Global Alerts Dialog",
// actual window title
_window_title = "Alerts";
readonly ThreadedWindowLauncher _launcher;
readonly UserSettingsHub _hub;
readonly IBus _bus;
readonly INotificationService _notification_service;
IDisposable _dispose;
[ImportingConstructor]
public GlobalAlertsDaemon(IBus bus, UserSettingsHub hub, INotificationService notification_service) {
_bus = bus;
_hub = hub;
_notification_service = notification_service;
_launcher = new ThreadedWindowLauncher(_window_name);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.ShowGlobalAlertsTag)
.Subscribe(_ => _launcher.show(_view_factory)),
_hub.subscribe_default_settings()
.SubscribeEach(s => new CompositeDisposable(
s.alerts.trade_alerts.Where(t => t.is_enabled).Select(
a => _notification_service.link_to_trades(
a.predicate.security_criteria, a.predicate.create(), a.sound)))));
}
Window _view_factory() {
var vm = new GlobalAlertsVM();
vm.load_from(_hub.current.alerts);
vm.request_commit += commit;
var sec_sub = _bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m => vm.security_map = m);
var win = new GlobalAlertsDialog {
Title = "Alerts",
DataContext = vm
};
win.Closed += (s, e) => Disposable.dispose(ref sec_sub);
return win;
void commit() {
var s = _hub.current.with(alerts: vm.create());
_hub.save_default(s).ignore();
}
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
+2 -3
View File
@@ -15,7 +15,6 @@ using System.Collections.Concurrent;
using System.Threading;
using bts.api.user;
using bts.md;
using mbts.ui.shell;
namespace bts {
[Export(typeof(IInitializable))]
@@ -28,8 +27,8 @@ namespace bts {
static uint get_naggle() => mbts.config.Configuration.get<md.RelayOptions>().naggle_ms;
[ImportingConstructor]
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss, [Import(LoginContainer.Exports.UserID)]uint user_id) :
base(get_naggle(), MDSourceOptions.DeferInit, user_id) {
public MDDaemon(Lazy<IBus> bus, Lazy<AuthTokenImmutable> token, Lazy<SyntheticSecurityService> ss) :
base(get_naggle(), MDSourceOptions.DeferInit) {
_bus = bus;
_token = token;
_ss = ss;
@@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Windows;
using LadderSettings = mbts.spark.LadderSettings;
namespace bts.shell {
[Export(typeof(IShellDaemon))]
@@ -49,14 +48,14 @@ namespace bts.shell {
void _show_order_ticket(SecurityMD smd) =>
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd,
Observable.Return(LadderSettings.Default))
Observable.Return(_model.factory.default_ladder_settings(smd.security)))
});
void _show_order_ticket((OrderDefinitionImmutable def, double price, uint size) ops) {
if (_md_map.TryGetValue(ops.def.SecurityKey, out SecurityMD smd))
_show_order_window(new OrderTicket {
DataContext = new NewOrderTicketVM(_model, smd, ops.def, ops.price, ops.size,
Observable.Return(LadderSettings.Default.calculator_buttons))
Observable.Return(_model.factory.default_ladder_settings(smd.security).calculator_buttons))
});
}
@@ -67,7 +66,7 @@ namespace bts.shell {
_show_order_window(new OrderModificationDialog {
DataContext = new ExistingOrderTicketVM(_model,
Observable.Return((md,ori.order)),
Observable.Return(LadderSettings.Default.calculator_buttons)
Observable.Return(_model.factory.default_ladder_settings(ori.sec.Value).calculator_buttons)
)
});
}
@@ -69,11 +69,10 @@ namespace bts {
cpv.Width = double.NaN;
cpv.Height = double.NaN;
vm.ok_action += () => {
vm.ok_action = () => {
window.Close();
message.complete(select_result(vm)).execute(_bus);
};
vm.cancel_action += () => window.Close();
window.move_to_mouse();
window.Show();
}
@@ -3,7 +3,6 @@ using bts.metrics;
using mbts;
using mbts.config;
using mbts.ui;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -24,7 +23,7 @@ namespace bts.perf {
Ping _pinger = new Ping();
[ImportingConstructor]
public ShellLatencyDaemon(ShellLatencyService service, [Import(LoginContainer.Exports.UserName)] string user) {
public ShellLatencyDaemon(ShellLatencyService service, [Import("username")] string user) {
_service = service;
_version = Configuration.get<bts.config.System>().version;
_username = user;
+31 -2
View File
@@ -2,7 +2,6 @@
using bts.api.core;
using bts.api.md.auxiliary;
using bts.api.orderman;
using bts.api.orderman.algo;
using bts.api.persistence;
using bts.api.position;
using bts.api.synthetic;
@@ -46,11 +45,41 @@ namespace Spark {
[Export]
public MarketDataRelay md_replay => context.locate<MarketDataRelay>();
//[Export]
//public AlgoService algo_service => context.locate<AlgoService>();
AlgoServiceWrapper _algo_service_wrapper;
[Export]
public AlgoDefinitionService algo_definition_service => context.locate<AlgoDefinitionService>();
public AlgoServiceWrapper algo_service_wrapper => _algo_service_wrapper ?? (_algo_service_wrapper = new AlgoServiceWrapper(context));
[Export]
public MBOService mbo_service => context.locate<MBOService>();
}
class AlgoServiceWrapper {
public enum Mode {
None,
Hierarchical,
Flat
}
public AlgoServiceWrapper(ServiceContext context) {
var flat_model_options = mbts.config.Configuration.get<bts.config.AlgoStudioFlatDefinitionModelOptions>();
if (flat_model_options.enabled) {
flat = context.locate<bts.api.orderman.algo.AlgoDefinitionService>();
} else {
hierarhical = context.locate<AlgoService>();
}
}
public AlgoService hierarhical { get; }
public bts.api.orderman.algo.AlgoDefinitionService flat { get; }
public Mode mode {
get {
if (hierarhical != null) return Mode.Hierarchical;
if (flat != null) return Mode.Flat;
return Mode.None;
}
}
}
}
-38
View File
@@ -22,15 +22,10 @@ namespace mbts.ui {
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
AddFileGeometryKey = nameof(AddFileGeometryKey),
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
AttachAlgosKey = nameof(AttachAlgosKey),
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
CopyWFGeometryKey = nameof(CopyWFGeometryKey),
DataEditGeometryKey = nameof(DataEditGeometryKey),
DetachAlgosKey = nameof(DetachAlgosKey),
DiskGeometryKey = nameof(DiskGeometryKey),
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
DocumentGeometryKey = nameof(DocumentGeometryKey),
DocumentAddGeometryKey = nameof(DocumentAddGeometryKey),
DocumentBrokenGeometryKey = nameof(DocumentBrokenGeometryKey),
DocumentCopyGeometryKey = nameof(DocumentCopyGeometryKey),
DocumentEmptyGeometryKey = nameof(DocumentEmptyGeometryKey),
@@ -40,18 +35,10 @@ namespace mbts.ui {
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
GearGeometryKey = nameof(GearGeometryKey),
HornGeometryKey = nameof(HornGeometryKey),
KeyGeometryKey = nameof(KeyGeometryKey),
LetterAKey = nameof(LetterAKey),
LetterDKey = nameof(LetterDKey),
OrdersGeometryKey = nameof(OrdersGeometryKey),
PauseGeometryKey = nameof(PauseGeometryKey),
PaymentGeometryKey = nameof(PaymentGeometryKey),
PencilGeometryKey = nameof(PencilGeometryKey),
PlayGeometryKey = nameof(PlayGeometryKey),
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
SpeakerGeometryKey = nameof(SpeakerGeometryKey),
StarGeometryKey = nameof(StarGeometryKey),
ToolsGeometryKey = nameof(ToolsGeometryKey),
TrashGeometryKey = nameof(TrashGeometryKey),
@@ -128,7 +115,6 @@ namespace mbts.ui {
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
public static ImageSource AttachAlgos => create_icon(AttachAlgosKey);
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
@@ -152,14 +138,10 @@ namespace mbts.ui {
public static ImageSource ModifyOrder { get { return load_resource_and_freeze("ModifyOrder"); } }
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
public static ImageSource CopyWF => create_icon(CopyWFGeometryKey);
public static ImageSource DataEdit => create_icon(DataEditGeometryKey);
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
public static ImageSource DetachAlgos => create_icon_and_rotate(DetachAlgosKey, Brushes.Red);
public static ImageSource Disk => create_icon(DiskGeometryKey);
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
public static ImageSource DocumentAdd => create_icon(DocumentAddGeometryKey);
public static ImageSource DocumentBroken => create_icon(DocumentBrokenGeometryKey);
public static ImageSource DocumentCopy => create_icon(DocumentCopyGeometryKey);
public static ImageSource DocumentEmpty => create_icon(DocumentEmptyGeometryKey);
@@ -172,19 +154,12 @@ namespace mbts.ui {
public static ImageSource FilterActive { get { return load_resource_and_freeze("FilterActive"); } }
public static ImageSource FilterError { get { return load_resource_and_freeze("FilterError"); } }
public static ImageSource Gear => create_icon(GearGeometryKey);
public static ImageSource Horn => create_icon(HornGeometryKey);
public static ImageSource Import => load_resource_and_freeze("Import");
public static ImageSource Key => create_icon(KeyGeometryKey);
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
public static ImageSource Pause => create_icon(PauseGeometryKey);
public static ImageSource Payment => create_icon(PaymentGeometryKey);
public static ImageSource Pencil => create_icon(PencilGeometryKey);
public static ImageSource Play => create_icon(PlayGeometryKey);
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
public static ImageSource RubberStamp => create_icon(RubberStampGeometryKey);
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
public static ImageSource Speaker => create_icon(SpeakerGeometryKey);
public static ImageSource Star => create_icon(StarGeometryKey);
public static ImageSource Target { get { return load_resource_and_freeze("Target"); } }
public static ImageSource Tetris { get { return load_resource_and_freeze("Tetris"); } }
@@ -211,19 +186,6 @@ namespace mbts.ui {
return rv;
}
public static ImageSource create_icon_and_rotate(string geometric_path_key, Brush brush = null, Pen pen = null, Transform transform = null, double degrees = 90) {
if (brush == null) {
brush = Brushes.Black;
}
var drawing = create_drawing(geometric_path_key, brush, pen, transform);
var dg = new DrawingGroup();
dg.Children.Add(drawing);
dg.Transform = new RotateTransform(degrees);
var rv = new DrawingImage(dg);
rv.Freeze();
return rv;
}
public static Drawing create_drawing(string geometric_path_key) =>
create_drawing(geometric_path_key, Brushes.Black);
+31 -80
View File
@@ -1,24 +1,18 @@
<ResourceDictionary
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mbts="clr-namespace:mbts.ui"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<sys:String x:Key="{x:Static mbts:Icons.AttachAlgosKey}">
M10.519881,14.945064C11.300896,14.955074,12.079896,15.102078,12.819896,15.386075L10.878893,17.326085C9.6458764,17.201085,8.3688531,17.591071,7.4258485,18.533091L3.5977991,22.360102C1.9327704,24.026093 1.9317786,26.736124 3.5977991,28.402117 5.2638197,30.068108 7.9748359,30.068108 9.6398644,28.402117L13.467914,24.575106C14.410919,23.632109,14.800938,22.355097,14.675937,21.121083L16.615963,19.180096C17.523962,21.546103,17.034943,24.325104,15.126931,26.234108L11.298882,30.061119C8.7138591,32.64612 4.5238051,32.64612 1.9387672,30.062127 -0.64625573,27.477126 -0.64625573,23.287106 1.9387672,20.701098L5.7668166,16.875065C7.077837,15.564055,8.8018727,14.923061,10.519881,14.945064z M19.825993,10.418039L21.580012,12.17305 12.089907,21.665091 10.333873,19.911087z M25.383069,0C27.077088,3.0538786E-08 28.771109,0.64599859 30.063117,1.9390028 32.647133,4.5230274 32.647133,8.7120385 30.062109,11.298047L26.235068,15.125058C24.327055,17.033085,21.547022,17.52207,19.181973,16.614077L21.121998,14.674067C22.356022,14.799068,23.633046,14.409051,24.57605,13.466054L28.403094,9.6400513C30.068121,7.9740295 30.069129,5.2630205 28.403094,3.5980362 26.738064,1.9320143 24.027033,1.9320143 22.362034,3.5980362L18.533986,7.4240398C17.591958,8.3670363,17.201968,9.643042,17.32697,10.877056L15.385937,12.817066C14.478913,10.452035,14.968939,7.6730337,16.875945,5.7660436L20.703019,1.9390028C21.995026,0.64599859,23.689047,3.0538786E-08,25.383069,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DetachAlgosKey}">
M19.522852,25.255011L20.920847,25.256994 20.920847,29.729012 19.522852,29.729012z M22.826874,21.952002L25.989905,25.113989 25.001887,26.101996 21.838856,22.941015z M25.269894,19.506992L29.742937,19.509007 29.742937,20.906012 25.269894,20.907995z M10.519729,14.945011C11.300744,14.954991,12.079745,15.103001,12.818766,15.386997L10.878742,17.327C9.6457264,17.200016,8.368704,17.592014,7.4257004,18.532993L3.597655,22.359015C1.9326275,24.025 1.9316508,26.734991 3.597655,28.40299 5.2636887,30.068001 7.9747178,30.066017 9.6397144,28.40299L13.468767,24.575018C14.410794,23.633001,14.800783,22.355993,14.674774,21.122015L16.615807,19.181004C17.523806,21.547003,17.034817,24.325995,15.126776,26.233008L11.298761,30.061012C8.713709,32.644997 4.5236599,32.644997 1.9396465,30.061012 -0.64638097,27.475012 -0.64638097,23.286017 1.9386395,20.702002L5.7666852,16.875004C7.0777046,15.562992,8.8017227,14.922001,10.519729,14.945011z M2.1536381,10.959994L6.6257035,10.959994 6.6266801,12.359988 2.1536381,12.359988z M6.7686803,5.894014L9.9307338,9.0549947 8.9427158,10.044008 5.7806932,6.8819901z M10.976735,2.1390082L12.374761,2.1390082 12.375768,6.6130081 10.975728,6.6130081z M25.382902,0C27.076922,-1.8170738E-08 28.770941,0.6470033 30.063955,1.9389956 32.646992,4.5220043 32.646992,8.7130147 30.061941,11.297L26.234902,15.126011C24.326891,17.032993,21.546862,17.521,19.181845,16.614994L21.121868,14.674015C22.35586,14.798008,23.63389,14.407992,24.575886,13.467013L28.403933,9.6390096C30.067953,7.972994 30.069967,5.2619945 28.403933,3.5960094 26.738905,1.9310002 24.0269,1.9320073 22.361872,3.5960094L18.533827,7.4249893C17.591829,8.3659993,17.201811,9.6440145,17.32782,10.87601L15.385781,12.817996C14.477781,10.453005,14.968784,7.6730062,16.875818,5.7659928L20.702858,1.9379885C21.994864,0.64599622,23.688883,-1.8170738E-08,25.382902,0z
</sys:String>
<!--AddBoxes (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/gregor-cresnar
Ttile: Add free icon
Credit: Icon made by Gregor Cresnar from www.flaticon.com-->
<sys:String x:Key="{x:Static mbts:Icons.AddBoxesGeometryKey}">
M 87.5 10 C 80.313542 10 74.400391 15.913151 74.400391 23.099609 V 397.90039 C 74.400391 405.08685 80.313541 411 87.5 411 h 374.80078 c 7.18646 0 13.09961 -5.91315 13.09961 -13.09961 V 23.099609 C 475.40039 15.913151 469.48724 10 462.30078 10 Z m 13 26.199219 H 449.19922 V 384.90039 H 100.5 Z M 21.099609 76.400391 C 13.912943 76.400391 8 82.313333 8 89.5 v 374.80078 c 0 7.18667 5.912943 13.09961 13.099609 13.09961 H 395.90039 c 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 0 -7.18667 -5.91294 -13.10156 -13.09961 -13.10156 H 34.199219 V 89.5 c 0 -7.186667 -5.912943 -13.099609 -13.09961 -13.099609 z M 274.90039 116.5 c -7.18667 0 -13.09961 5.91294 -13.09961 13.09961 V 197.5 h -67.90039 c -7.18667 0 -13.09961 5.91294 -13.09961 13.09961 0 7.18667 5.91294 13.09961 13.09961 13.09961 h 67.90039 v 67.90039 c 0 7.18667 5.91294 13.09961 13.09961 13.09961 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 v -67.90039 h 67.90039 c 7.18667 0 13.09961 -5.91294 13.09961 -13.09961 v -0.0391 -0.0391 C 368.85982 203.37217 363.03372 197.5 355.90039 197.5 H 288 V 129.59961 C 288 122.41294 282.08706 116.5 274.90039 116.5 Z
M393,480.3c0-5-4.1-9.1-9.1-9.1H18.2V105.5c0-5-4.1-9.1-9.1-9.1S0,100.5,0,105.5v374.8c0,5,4.1,9.1,9.1,9.1h374.8 C388.9,489.4,393,485.3,393,480.3z
M373.9,187.5H302v-71.9c0-5-4.1-9.1-9.1-9.1s-9.1,4.1-9.1,9.1v71.9h-71.9c-5,0-9.1,4.1-9.1,9.1s4.1,9.1,9.1,9.1h71.9v71.9 c0,5,4.1,9.1,9.1,9.1s9.1-4.1,9.1-9.1v-71.9h71.9c5,0,9.1-4.1,9.1-9.1C382.9,191.5,378.9,187.5,373.9,187.5z
M105.5,393h374.8c5,0,9.1-4.1,9.1-9.1V9.1c0-5-4.1-9.1-9.1-9.1H105.5c-5,0-9.1,4.1-9.1,9.1v374.8 C96.4,388.9,100.5,393,105.5,393z M114.5,18.2h356.7v356.7H114.5V18.2z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
@@ -46,25 +40,6 @@
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.CopyWFGeometryKey}">
M14.733902,12.084991L9.751783,17.388992 12.982842,17.436005C13.569886,17.462997 14.5229,17.350998 14.876848,17.005005 14.954852,16.929993 15.031879,16.824005 15.02486,16.593002 14.986895,15.457993 14.838883,13.438004 14.733902,12.084991z M22.507753,11.134453C22.480678,11.134323,22.467085,11.135494,22.469961,11.136993L20.64694,11.145004 16.291904,11.192993C16.388891,12.405991 16.596901,15.100998 16.647928,16.539001 16.668924,17.177994 16.448888,17.742004 16.007904,18.168991 15.210897,18.947998 13.893864,19.065002 13.179865,19.065002L9.2818066,19.005005 9.3258129,25.522003 9.3728109,25.528 9.3278271,25.882996 9.3317944,26.395996 9.3107983,26.397995C9.3167797,26.789993,9.3378369,27.552994,9.3958212,29.065994L9.4068075,29.393005C9.4207855,29.819992 9.5648295,29.994995 9.6707884,30.089005 9.9807898,30.365997 10.539817,30.390991 10.717799,30.369003L10.717799,30.375C10.88284,30.354996 11.280855,30.356003 12.823844,30.365997 13.867863,30.371002 15.206869,30.376999 16.594886,30.376999 20.757965,30.376999 23.270998,30.354996 23.862007,30.311005 24.454971,30.268005 24.661029,29.154999 24.690021,28.729996 24.690021,28.729996 24.677997,27.876999 24.661029,26.582993 24.612016,22.671005 24.51198,14.697998 24.472002,12.802002 24.461013,12.302994 24.316969,11.929993 24.034008,11.662003 23.547475,11.200003 22.697284,11.13536 22.507753,11.134453z M8.8867823,2.8410034L2.5417312,10.296997 6.4037791,10.356995C6.8917614,10.390991 8.3258005,10.341003 8.9567896,9.7299957 9.1738335,9.5189972 9.2667919,9.2659912 9.2558046,8.9089966 9.2067934,7.3889923 9.0258213,4.7480011 8.8867823,2.8410034z M10.424826,1.6239929C10.551842,3.302002 10.816799,6.951004 10.876797,8.8560028 10.903836,9.6750031 10.636804,10.360992 10.083818,10.897995 9.0667769,11.882004 7.3527657,11.987991 6.6627546,11.987991 6.4657918,11.987991 6.3367616,11.979004 6.3177796,11.977005L1.6477377,11.905991C1.6387044,14.278992 1.6236895,20.283997 1.6496909,22.358994 1.651705,22.569992 1.6437093,22.780991 1.6366904,22.992004 1.6117265,23.593994 1.5926833,24.112991 1.8537338,24.386002 2.0716933,24.612991 2.6497034,24.886002 4.1457549,24.886002 6.0127832,24.886002 7.0867711,24.876999 7.6967641,24.867004L7.6457993,17.259995 14.853899,9.5849915 16.681924,9.5650024 16.681924,9.5429993 19.454908,9.5289917 19.406934,2.6210022C19.323925,2.3549957,18.960945,1.6239929,17.387928,1.6239929z M9.1738335,0L17.387928,0C19.94692,0,20.874971,1.5299988,21.022983,2.4329987L21.077976,9.5209961 22.446951,9.5139923C22.43395,9.5130005 24.031017,9.4250031 25.148035,10.481995 25.753999,11.054001 26.071996,11.822998 26.092992,12.766998 26.142006,15.065994 26.281044,26.268997 26.307047,28.412003L26.311014,28.764999C26.279029,29.817993 25.750032,31.802002 23.979013,31.931 23.342958,31.977005 20.887972,32 16.683878,32 13.798893,32 11.107818,31.988998 10.808803,31.987991 10.789821,31.990997 9.5147806,32.112 8.6008288,31.309006 8.0858074,30.854996 7.8107797,30.229004 7.7838017,29.444992L7.7728153,29.126999C7.721789,27.759995 7.6978022,26.970993 7.6898061,26.490005 7.0547882,26.5 5.9867817,26.509003 4.1457549,26.509003 2.4377257,26.509003 1.3387128,26.192001 0.68367504,25.509003 -0.055286743,24.742004 -0.017322397,23.742004 0.012707477,22.938004 0.020702986,22.744003 0.028698692,22.559998 0.026684528,22.378998 -0.00029337304,20.240997 0.016674615,13.856995 0.026684528,11.212997L0.026684528,10.867004 0.14167619,10.610992 8.7147823,0.54100037 8.7088009,0.45899963 8.8018194,0.43800354z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DocumentAddGeometryKey}">
M10.830011,12.05L13.334007,12.05 13.334007,16.535997 17.819998,16.535997 17.819998,19.03902 13.334007,19.03902 13.334007,23.525016 10.830988,23.525016 10.830988,19.03902 6.3460046,19.03902 6.3449975,16.53499 10.830988,16.535997z M2.2799975,2.4149805L2.2799975,29.580015 22.121998,29.580015 22.121998,8.500008 15.83599,8.500008 15.83599,2.4149805z M0,0L15.83599,0 17.125998,0 22.681996,5.439977 24.407,7.125007 24.407,32.000001 0,32.000001z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/chanut
Ttile: Cancel free icon
Credit: Icon made by Chanut from www.flaticon.com-->
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
C59.518,47.514,47.514,59.518,32.759,59.518z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/smashicons
Ttile: File free icon
@@ -113,9 +88,6 @@
s1-0.448,1-1v-5h5c0.552,0,1-0.448,1-1S52.052,46,51.5,46z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DataEditGeometryKey}">
M 22.589844 14.470703 H 0.82421875 C 0.36896661 14.470586 -1.1747368e-4 14.83967 0 15.294922 v 4.669922 c -1.1747368e-4 0.455252 0.36896661 0.824336 0.82421875 0.824218 H 16.940231 l 0.425003 -1.09375 0.554688 -0.554687 H 1.6484375 V 16.119141 H 20.941406 Z M 27.462891 9.5976562 V 8.0742188 A 0.8240064 0.8240064 0 0 0 26.638672 7.25 H 0.82421875 A 0.8240064 0.8240064 0 0 0 0 8.0742188 v 4.6699222 a 0.8240064 0.8240064 0 0 0 0.82421875 0.824218 H 23.492188 l 1.648437 -1.648437 H 1.6484375 V 8.8984375 H 25.814453 V 11.246094 Z M 0.82421878 2.736881e-8 A 0.8240064 0.8240064 0 0 0 2.7433281e-8 0.82421879 V 5.4941406 A 0.8240064 0.8240064 0 0 0 0.82421878 6.3183594 H 26.638672 A 0.8240064 0.8240064 0 0 0 27.46289 5.4941406 V 0.82421879 A 0.8240064 0.8240064 0 0 0 26.638672 2.736881e-8 Z M 1.6484375 1.6484375 H 25.814453 V 4.6699219 H 1.6484375 Z M 31.628051 7.2558423 19.165575 19.718635 23.70324 24.256618 36.166032 11.793823 Z m -0.0013 3.5965607 0.941422 0.94142 -8.866233 8.866232 -0.94142 -0.94142 z m -13.240122 9.708458 -1.636234 6.110706 6.109329 -1.637606 z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.DiskGeometryKey}">
M403.213,20H0v472h512V128.787L403.213,20z M131,50h180v100h10h20h10V50h30v130H131V50z M381,462H131V315
c0-8.271,6.729-15,15-15h220c8.271,0,15,6.729,15,15V462z M482,462h-71V315c0-24.813-20.187-45-45-45H146
@@ -272,22 +244,10 @@
M16.000015,9.6000061C12.500012,9.6000061 9.5999844,12.5 9.5999844,16 9.5999844,19.5 12.500012,22.399994 16.000015,22.399994 19.500019,22.399994 22.400015,19.5 22.400015,16 22.400015,12.5 19.500019,9.6000061 16.000015,9.6000061z M14.199995,0L17.800005,0C18.199999,0,18.599993,0.29998779,18.599993,0.79998779L18.599993,4.7999878C19.900013,5.1000061,21.000019,5.6000061,22.099997,6.2999878L25.000023,3.3999939C25.099999,3.2999878 25.300011,3.1999817 25.500025,3.1999817 25.700007,3.1999817 25.900019,3.2999878 26.000025,3.3999939L28.600033,6C28.900021,6.2999878,28.900021,6.7999878,28.600033,7.1000061L25.700007,10C26.400019,11.100006,26.900019,12.299988,27.200007,13.5L31.20001,13.5C31.600037,13.5,31.999998,13.799988,31.999998,14.299988L31.999998,17.899994C31.999998,18.299988,31.700012,18.699982,31.20001,18.699982L27.200007,18.699982C26.900019,20,26.400019,21.100006,25.700007,22.199982L28.600033,25.100006C28.700009,25.199982 28.799984,25.399994 28.799984,25.600006 28.799984,25.799988 28.700009,26 28.600033,26.100006L26.000025,28.600006C25.900019,28.699982 25.700007,28.799988 25.500025,28.799988 25.300011,28.799988 25.099999,28.699982 25.000023,28.600006L22.099997,25.699982C21.000019,26.399994,19.800007,26.899994,18.599993,27.199982L18.599993,31.199982C18.599993,31.600006,18.300005,32,17.800005,32L14.199995,32C13.8,32,13.400006,31.699982,13.400006,31.199982L13.400006,27.199982C12.099987,26.899994,11.000011,26.399994,9.9000027,25.699982L7.0000064,28.600006C6.9000003,28.699982 6.6999881,28.799988 6.5000059,28.799988 6.2999937,28.799988 6.099981,28.699982 6.0000054,28.600006L3.3999967,26C3.0999784,25.699982,3.0999784,25.199982,3.3999967,24.899994L6.2999937,22C5.5999805,20.899994,5.0999801,19.699982,4.7999923,18.5L0.79998828,18.5C0.39999409,18.5,-1.933513E-07,18.199982,0,17.699982L0,14.100006C-1.933513E-07,13.699982,0.2999879,13.299988,0.79998828,13.299988L4.7999923,13.299988C5.0999801,12,5.5999805,10.899994,6.2999937,9.7999878L3.3999967,7C3.2999908,6.8999939 3.1999845,6.6999817 3.1999845,6.5 3.1999845,6.2999878 3.2999908,6.1000061 3.3999967,6L6.0000054,3.3999939C6.099981,3.2999878 6.2999937,3.1999817 6.5000059,3.1999817 6.6999881,3.1999817 6.9000003,3.2999878 7.0000064,3.3999939L9.9000027,6.2999878C11.000011,5.6000061,12.199993,5.1000061,13.400006,4.7999878L13.400006,0.79998779C13.400006,0.29998779,13.8,0,14.199995,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.HornGeometryKey}">
M4.0499969,7.0870913C2.9519978,7.0870913 2.0249984,8.4780906 2.0249987,10.125091 2.0249984,11.771091 2.9519978,13.162091 4.0499969,13.162091 5.1479961,13.162091 6.0749954,11.771091 6.0749955,10.125091 6.0749954,8.4780906 5.1479961,7.0870913 4.0499969,7.0870913z M29.975016,3.316155C23.883831,8.2016557,11.829888,8.9691842,8.1184387,9.0896781L8.0151367,9.0928462 8.0175586,9.1059306C8.0716033,9.4352315 8.0999939,9.7760909 8.0999942,10.125091 8.0999939,10.473966 8.0716033,10.814716 8.0175586,11.143921L8.0098858,11.185364 8.193316,11.195448C12.163719,11.420399,23.994581,12.458921,29.975016,17.005057z M31.176758,0.00019051835C31.280359,-0.0022594665 31.391857,0.01889403 31.514975,0.065146674 31.908983,0.21413228 31.999985,0.59114302 31.999985,1.0121596L31.999985,19.236969C31.999985,19.653958 31.913988,20.028956 31.525991,20.179955 31.405998,20.226952 31.450981,20.250023 31.32699,20.250023 31.050991,20.250023 30.778992,20.13601 30.583986,19.925928 25.229074,14.140572 9.6001122,13.269665 7.2865356,13.172559L7.2813506,13.172345 7.1739591,13.343056C6.4304952,14.468533 5.3060585,15.187091 4.0499969,15.187091 1.8169985,15.187091 0,12.916091 0,10.125091 0,7.3330911 1.8169985,5.0620912 4.0499969,5.0620912 5.3060585,5.0620912 6.4304952,5.78065 7.1739591,6.9063042L7.2852713,7.0832756 7.3087114,7.0828188C9.6077337,7.036146 25.141474,6.4939421 30.567997,0.34315929 30.75913,0.12521249 30.948835,0.005580858 31.176758,0.00019051835z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.KeyGeometryKey}">
M22.399981,2.4000006C22.099982,2.4000006 21.899982,2.5 21.599982,2.6999998 21.199983,3.1000004 21.199983,3.8000002 21.599982,4.2000003L26.199979,8.9000001C26.599978,9.3000002 27.299977,9.3000002 27.699977,8.9000001 28.099977,8.5000002 28.099977,7.8000002 27.699977,7.4000006L23.099981,2.6999998C22.899981,2.5,22.699981,2.4000006,22.399981,2.4000006z M21.499982,0C22.299982,0,23.199981,0.30000019,23.79998,0.90000057L29.299976,6.4000001C30.599975,7.7000003,30.599975,9.7000003,29.299976,11L22.599981,17.7C21.299983,19,19.299984,19,17.999985,17.7L17.299985,17 12.699989,21.6 10.399991,20.900001 10.899991,23.400001 8.4999928,22.8 8.9999924,25.5 6.2999945,25.1 6.7999945,27.6 4.2999964,27 4.7999954,29.6 2.4999971,32 0,26.6 13.499989,13.1 16.999986,16.6 12.699989,12.3C11.399991,11,11.399991,9.0000002,12.699989,7.7000003L19.399984,1C19.899983,0.30000019,20.699983,0,21.499982,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.OrdersGeometryKey}">
M50.14375,33.389126C50.416863,33.394688,50.653496,33.587773,50.705998,33.864617L50.015995,45.114388C49.810295,46.291462,49.130394,45.097489,49.130394,45.097489L47.65659,42.640538C42.747776,49.635197 34.633152,54.221001 25.433924,54.221001 14.884193,54.221001 5.7437552,48.204125 1.2449617,39.419205 0.80087067,38.552021 0.21358696,37.705738 0.052086769,36.736958 -0.49735315,33.490925 3.4507786,32.901036 4.8049124,34.432404 5.3674143,35.067692 6.034036,36.16407 6.5079177,37.026051 10.106778,43.799416 17.2321,48.413819 25.433924,48.413819 32.656544,48.413819 39.026364,44.826695 42.903977,39.351506L40.229367,38.854115C40.229367,38.854115,38.677464,38.647118,39.742466,37.996033L50.024895,33.398526C50.064972,33.391365,50.104733,33.388333,50.14375,33.389126z M23.030291,9.0310004L27.889918,9.0310004 27.889918,13.028299C30.539786,13.205498,32.324932,13.958096,33.464161,14.528294L33.964176,14.775793 32.417435,20.038878 31.706417,19.68068C30.579987,19.11958 28.839241,18.244583 26.149073,18.244583 24.006916,18.244583 22.723183,19.122181 22.723183,20.590876 22.723183,21.894272 23.943314,22.804369 27.551308,24.187165 32.706444,26.124659 34.911,28.702853 34.911,32.817341 34.911,36.892929 32.090527,40.05172 27.673611,40.989117L27.673611,45.540001 22.767485,45.540001 22.767485,41.330414C20.252919,41.155815,17.695551,40.424118,16.101811,39.404721L15.683999,39.137619 17.221739,33.731438 17.969059,34.180634C19.088988,34.851235 21.402748,35.97873 24.340325,35.97873 26.734886,35.97873 28.276728,34.921635 28.276728,33.291336 28.276728,31.986641 27.547409,30.853846 23.986016,29.57255 20.083815,28.144253 16.095511,26.04406 16.095511,21.239174 16.095511,17.280985 18.737379,14.291395 23.030291,13.329098z M25.268765,0C35.819447,2.7347414E-07 44.961449,6.0155495 49.458798,14.804573 49.902695,15.669173 50.490094,16.516972 50.651287,17.48427 51.198281,20.734265 47.255726,21.318962 45.901341,19.789065 45.336346,19.153567 44.669552,18.051968 44.19566,17.19517 40.5957,10.421881 33.473275,5.8072398 25.268765,5.8072393 18.047545,5.8072398 11.678013,9.3918336 7.8003562,14.867073L10.474627,15.367072C10.474627,15.367072,12.026909,15.574172,10.962921,16.223872L0.67933281,20.826762C0.36037083,20.880062,0.057029126,20.669165,0.00099972012,20.354063L0.69093342,9.1118043C0.89552094,7.9296158,1.5738537,9.124924,1.5738539,9.124924L3.0452772,11.583379C7.9566038,4.584462,16.073666,2.7347414E-07,25.268765,0z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.PauseGeometryKey}">
M17.94,0L29.150001,0 29.150001,32 17.94,32z M0,0L11.210002,0 11.210002,32 0,32z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.PaymentGeometryKey}">
M28.000001,6.0000026L28.000001,15 6.0010006,15 6.0010006,17.000003 30.001,17.000003 30.001,6.0000026z M14.231,2.9540056L14.979999,2.9540056 14.979999,3.987005C15.704998,4.009005,16.200997,4.178005,16.550998,4.3600049L16.248998,5.0620046C15.994998,4.9340047 15.499998,4.7010047 14.725999,4.7010047 13.796,4.7010047 13.445001,5.1900046 13.445001,5.6160046 13.445001,6.1710043 13.893,6.4470041 14.943999,6.8310037 16.187998,7.2780037 16.816998,7.8320036 16.816998,8.7800031 16.816998,9.6210027 16.151998,10.409003 14.907999,10.591002L14.907999,11.698002 14.146,11.698002 14.146,10.622003C13.445001,10.611002,12.720001,10.420002,12.297002,10.154003L12.587001,9.4400029C13.022001,9.696003 13.651,9.8980026 14.328,9.8980026 15.185999,9.8980026 15.765998,9.4620028 15.765998,8.8540034 15.765998,8.2680035 15.293999,7.9060035 14.4,7.5870035 13.167001,7.1610038 12.406001,6.6710041 12.406001,5.7440041 12.406001,4.8600047 13.119,4.189005 14.231,4.0290052z M2,2.0000002L2,13 26.000001,13 26.000001,2.0000002z M0,0L28.000001,0 28.000001,4.0000031 32.001,4.0000031 32.001,19.000003 4.0010006,19.000003 4.0010006,15 0,15z
</sys:String>
@@ -311,10 +271,6 @@
M0,0L25.194,16 0,32z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.RubberStampGeometryKey}">
M1.8950181,29.607025L24.423994,29.607025 24.423994,31.103027C24.423994,31.601013,24.025009,32,23.526993,32L2.7920208,32C2.2929976,32,1.8950181,31.601013,1.8950181,31.103027z M0,19.838013L26.218001,19.838013 26.218001,27.813019 0,27.813019z M12.960014,0C16.549,0 19.538988,2.9909973 19.538988,6.57901 19.538988,8.0750122 19.041002,9.3710022 18.243,10.46701L18.043995,10.766022C17.345997,11.863007 16.647999,12.76001 16.647999,12.76001 15.052999,15.651001 15.253011,17.745026 15.253011,17.745026L10.667015,17.745026C10.667015,17.745026 10.866997,15.651001 9.2719967,12.76001 9.2719967,12.76001 8.5739996,11.863007 7.8760016,10.766022L7.6759898,10.46701 7.5769908,10.368011C6.7790176,9.3710022 6.3810074,7.9750061 6.3810074,6.57901 6.3810074,2.9909973 9.2719967,0 12.960014,0z
</sys:String>
<!--File (the icon is free to use if we include this information in our credit section)
Source: https://www.flaticon.com/authors/smashicons
Ttile: File free icon
@@ -337,11 +293,6 @@
M33.5,4h-6v10h6V4z M31.5,12h-2V6h2V12z
</sys:String>
</x:Array>
<sys:String x:Key="{x:Static mbts:Icons.SpeakerGeometryKey}">
M18,12.839666L18,19.159448 18.029955,19.131576C18.848108,18.314159 19.332083,17.192702 19.332083,15.999873 19.332083,14.806357 18.848108,13.684685 18.029955,12.867527z M21.738388,4.748316C21.868353,4.7515661 21.999973,4.7804414 22.126221,4.8376924 26.511095,6.8177201 29.344013,11.199782 29.344013,15.999849 29.344013,20.799915 26.511095,25.181977 22.126221,27.162004 21.992224,27.223004 21.853228,27.251006 21.715233,27.251006 21.333244,27.251006 20.970255,27.032003 20.803259,26.662997 20.575266,26.15899 20.799259,25.566981 21.302244,25.339978 24.973139,23.681956 27.34407,20.015905 27.34407,15.999849 27.34407,11.983792 24.973139,8.3177406 21.302244,6.6597177 20.799259,6.4327146 20.575266,5.8407065 20.803259,5.3366992 20.973503,4.9594442 21.348494,4.7385656 21.738388,4.748316z M16,3.1854247L6.6530151,11.257323C6.4719848,11.414303,6.2399902,11.500301,6,11.500301L1.9999998,11.500301 1.9999998,21.500148 6,21.500148C6.2219848,21.500148,6.4370117,21.573144,6.6110229,21.709129L16,28.964023z M17.000252,1.186163E-08C17.141373,-2.1778678E-05 17.283264,0.02997845 17.416016,0.091477852 17.770996,0.25450047 18,0.60843792 18,1.0004608L18,10.368904 18.170259,10.465408C20.111607,11.612996 21.331976,13.716092 21.331976,15.999873 21.331976,18.282748 20.111607,20.38576 18.170259,21.53334L18,21.629842 18,31.000003C18,31.382016 17.78302,31.728995 17.440002,31.896962 17.301025,31.965991 17.150024,31.99999 17,31.99999 16.78302,31.99999 16.567017,31.929982 16.388977,31.791007L5.65802,23.500117 0.99999966,23.500117C0.44702145,23.500117,-3.9348969E-08,23.053104,3.5527137E-15,22.500133L3.5527137E-15,10.500316C-3.9348969E-08,9.9473459,0.44702145,9.5003311,0.99999966,9.5003311L5.6279907,9.5003311 16.346985,0.24345344C16.531998,0.083467316,16.765053,3.6392792E-05,17.000252,1.186163E-08z
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.StarGeometryKey}">
M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z
</sys:String>
@@ -351,7 +302,7 @@
</sys:String>
<sys:String x:Key="{x:Static mbts:Icons.TrashGeometryKey}">
M15.000001,8.0000272L17,8.0000272 17,27.000028 15.000001,27.000028z M11.000001,8.0000272L13.000001,8.0000272 13.000001,27.000028 11.000001,27.000028z M7.0000005,8.0000272L9.000001,8.0000272 9.000001,27.000028 7.0000005,27.000028z M3.0790101,5.0000274L4.9959994,29.922999 19.000006,30.000026 20.918949,5.0000274z M13.771989,1.9959999L10.198989,2.0000001C10.158989,2.025,9.9989892,2.241,9.9989892,2.6000001L9.9989892,3.0000258 13.998989,3.0000258 13.998989,2.6000001C13.998989,2.241,13.838988,2.025,13.771989,1.9959999z M10.198989,0L13.798988,0C15.031989,0,15.998989,1.142,15.998989,2.6000001L15.998989,3.0000258 23.07898,3.0000258 24,3.0000272 24,5.0000274 22.925121,5.0000274 20.995976,30.076991C20.999027,31.102992,20.100956,32.000026,18.999029,32.000026L4.9990512,32.000026C3.8960255,32.000026,2.9990543,31.102992,2.999054,30.000026L1.073059,5.0000274 0,5.0000274 0,3.0000272 0.91897895,3.0000272 7.9989887,3.0000258 7.9989887,2.6000001C7.9989887,1.142,8.9659892,0,10.198989,0z
M0,12.568994L23.662999,12.568994 20.910989,31.999999 2.7520113,31.999999z M8.0689915,1.6269979L8.0689915,4.6679968 15.594984,4.6679968 15.594984,1.6269979z M6.4420097,0L17.221997,0 17.221997,4.6679968 23.662999,4.6679968 23.662999,8.7949938 0,8.7949938 0,4.6679968 6.4420097,4.6679968z
</sys:String>
<!--UndoCircular (the icon is free to use if we include this information in our credit/about section)
@@ -411,10 +362,10 @@
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
@@ -430,10 +381,10 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="Black" Geometry="
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M 32.868601,3.8869994 C31.026201,3.8869996 29.534,5.3791947 29.534,7.2190399 29.534,9.0588851 31.026201,10.553 32.868601,10.553 34.7085,10.553 36.202,9.0588851 36.202,7.2190399 36.202,5.3791947 34.7085,3.8869996 32.868601,3.8869994z
M 42.868601,3.8869994 C41.026201,3.8869996 39.534,5.3791947 39.534,7.2190399 39.534,9.0588851 41.026201,10.553 42.868601,10.553 44.7085,10.553 46.202,9.0588851 46.202,7.2190399 46.202,5.3791947 44.7085,3.8869996 42.868601,3.8869994z
M 52.868601,3.8869994 C51.026201,3.8869996 49.534,5.3791947 49.534,7.2190399 49.534,9.0588851 51.026201,10.553 52.868601,10.553 54.7085,10.553 56.202,9.0588851 56.202,7.2190399 56.202,5.3791947 54.7085,3.8869996 52.868601,3.8869994z
M5.2010002,14.812L5.2010002,43.573997 56.098,43.573997 56.098,14.812z
M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
@@ -849,7 +800,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M23,14C24.10498,14 25,14.89502 25,16 25,17.10498 24.10498,18 23,18 21.89502,18 21,17.10498 21,16 21,14.89502 21.89502,14 23,14z M16,14C17.105,14 18,14.895 18,16 18,17.105 17.105,18 16,18 14.895,18 14,17.105 14,16 14,14.895 14.895,14 16,14z M9,14C10.105,14 11,14.895 11,16 11,17.105 10.105,18 9,18 7.8950005,18 7,17.105 7,16 7,14.895 7.8950005,14 9,14z M16,2C8.2799997,2 2,8.2800002 2,16 2,23.72 8.2799997,30 16,30 23.72,30 30,23.72 30,16 30,8.2800002 23.72,2 16,2z M16,0C24.822,0 32,7.178 32,16 32,24.822 24.822,32 16,32 7.1780005,32 0,24.822 0,16 0,7.178 7.1780005,0 16,0z" />
</DrawingGroup.Children>
@@ -862,7 +813,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M299.152955025434,0L496.426178902388,0 268.890046089888,280.247772216797 198.190201729536,367.407897949219 199.87503144145,369.495361328125 271.502137154341,457.706451416016 498.999970406294,738 301.717621773481,738 101.234124153852,491.008697509766 99.5304498374462,488.936553955078 98.6409082114697,490.003173828125 0,368.488952636719 98.6409082114697,246.945526123047z" />
</DrawingGroup.Children>
@@ -874,7 +825,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.bid}"
Geometry="m 24.706673 31.196264 -3.086182 3.086182 5.965481 5.963348 -5.965481 5.96548 3.086182 3.084049 5.963348 -5.963348 5.96548 5.963348 3.084049 -3.084049 -5.963347 -5.96548 5.963347 -5.963348 -3.084049 -3.086182 -5.96548 5.965481 z M 5.3507926 32.045363 H 17.816449 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132994 v 2.401501 H 5.3507926 Z m 0 9.874466 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z M 10.662215 24.759265 V 5.1202697 h 7.148923 q 3.286862 0 5.05355 1.2051818 1.766687 1.2051838 1.766687 3.3964241 0 1.5886494 -1.081924 2.7801354 -1.068231 1.191488 -2.739053 1.657126 v 0.05479 q 2.095375 0.26021 3.341644 1.547564 1.259962 1.287354 1.259962 3.136214 0 2.697964 -1.93103 4.286613 -1.931032 1.574956 -5.272673 1.574956 z M 15.085783 8.3797408 v 4.6563852 h 1.944726 q 1.369526 0 2.150155 -0.657372 0.794325 -0.671068 0.794325 -1.835164 0 -2.1638492 -3.23208 -2.1638492 z m 0 7.9432472 v 5.176805 h 2.39667 q 1.533868 0 2.39667 -0.712152 0.876495 -0.712155 0.876495 -1.944728 0 -1.17779 -0.862802 -1.848858 -0.849105 -0.671067 -2.382974 -0.671067 z" />
</DrawingGroup.Children>
@@ -886,7 +837,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.ask}"
Geometry="m 19.060265 26.797642 v -2.144583 q 0.583058 0.489233 1.266644 0.737201 0.683586 0.241265 1.380576 0.241265 0.408811 0 0.710393 -0.07372 0.308284 -0.07372 0.509339 -0.201055 0.207756 -0.134036 0.308284 -0.308283 0.100527 -0.18095 0.100527 -0.388706 0 -0.281477 -0.160844 -0.502637 -0.160844 -0.22116 -0.44232 -0.408811 -0.274775 -0.187651 -0.656779 -0.361898 -0.382004 -0.174248 -0.824324 -0.355197 -1.125906 -0.469128 -1.682158 -1.146012 -0.549549 -0.676884 -0.549549 -1.635245 0 -0.750604 0.301582 -1.28675 0.301582 -0.542847 0.817622 -0.891342 0.522743 -0.348495 1.206328 -0.509339 0.683586 -0.167545 1.447594 -0.167545 0.750604 0 1.326961 0.09383 0.583059 0.08712 1.072292 0.274775 v 2.003845 q -0.241266 -0.167545 -0.529444 -0.29488 -0.281477 -0.127335 -0.583059 -0.207757 -0.301582 -0.08712 -0.603164 -0.127334 -0.29488 -0.04021 -0.562953 -0.04021 -0.3686 0 -0.670182 0.07372 -0.301582 0.06702 -0.509339 0.194353 -0.207756 0.127334 -0.321687 0.308284 -0.113931 0.174247 -0.113931 0.395407 0 0.241266 0.127334 0.435619 0.127335 0.187651 0.361899 0.361898 0.234564 0.167546 0.569655 0.335091 0.335091 0.160844 0.757306 0.335091 0.576356 0.241266 1.03208 0.516041 0.462426 0.268073 0.790816 0.609866 0.328389 0.341793 0.502636 0.784113 0.174248 0.435618 0.174248 1.018677 0 0.804219 -0.308284 1.353768 -0.301582 0.542848 -0.824324 0.884641 -0.522743 0.335091 -1.219732 0.482531 -0.690288 0.14744 -1.460998 0.14744 -0.790815 0 -1.50791 -0.134036 -0.710393 -0.134037 -1.233135 -0.40211 z m 6.620223 3.788289 -1.556212 1.556212 3.008102 3.007026 -3.008102 3.008101 1.556212 1.555136 3.007026 -3.007026 3.008101 3.007026 1.555136 -1.555136 -3.007026 -3.008101 3.007026 -3.007026 -1.555136 -1.556212 -3.008101 3.008102 z m -9.76023 2.917766 h 2.792621 v 1.21096 h -2.792621 z m 0 -2.489606 h 6.285825 v 1.21096 h -6.285825 z m 0 -2.489607 h 9.470951 v 1.210961 H 15.920258 Z M 13.914475 14.855173 a 0.69291063 0.69291063 0 0 0 -0.692361 0.692361 v 22.317061 a 0.69291063 0.69291063 0 0 0 0.692361 0.692361 h 7.806047 a 9.0291392 9.0291392 0 0 1 -0.02954 -0.692361 9.0291392 9.0291392 0 0 1 0.03447 -0.693345 H 14.60782 V 16.24088 h 15.419061 v 12.624011 a 9.0291392 9.0291392 0 0 1 0.693346 -0.02954 9.0291392 9.0291392 0 0 1 0.692361 0.03447 V 15.547534 a 0.69291063 0.69291063 0 0 0 -0.692361 -0.692361 z" />
</DrawingGroup.Children>
@@ -899,7 +850,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 5.3507926 32.045363 H 17.81645 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132995 v 2.401501 H 5.3507926 Z M 18.799228 22.222041 v 2.32929 h -1.567173 v -2.264886 q -2.179014 -0.01073 -4.057475 -0.987533 v -2.973336 q 0.622575 0.504501 1.856993 0.933864 1.234417 0.429362 2.200482 0.493766 v -3.907198 q -2.511771 -0.933863 -3.4349 -2.018003 -0.912395 -1.094874 -0.912395 -2.662047 0 -1.685248 1.191482 -2.8659947 1.19148 -1.1914807 3.155813 -1.3846939 V 4.918734 h 1.567173 v 1.9535992 q 2.264887 0.1073411 3.38123 0.7299162 V 10.500446 Q 20.677689 9.5880506 18.799228 9.3841034 v 4.0682086 q 2.35076 0.847992 3.370495 1.942866 1.019736 1.08414 1.019736 2.629845 0 1.781854 -1.137811 2.876728 -1.13781 1.08414 -3.25242 1.32029 z M 17.232055 12.829737 V 9.4270397 q -1.492034 0.2683516 -1.492034 1.5564393 0 1.105608 1.492034 1.846258 z m 1.567173 3.628113 v 3.25242 q 1.53497 -0.236149 1.53497 -1.53497 0 -1.051939 -1.53497 -1.71745 z m 13.258587 13.65074 h -3.954117 l 0.0014 7.641804 -7.643169 -10e-7 0.0014 3.952751 h 7.640439 l 0.0014 7.641803 h 3.951385 l 0.0014 -7.641802 7.640437 -10e-7 0.0014 -3.952751 -7.643169 10e-7 z M 5.3507926 36.982596 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z" />
</DrawingGroup.Children>
@@ -911,7 +862,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 26.463403 27.963318 h -2.359042 l -0.683586 -2.137882 h -3.417929 l -0.676884 2.137882 h -2.345638 l 3.498351 -9.610414 h 2.566798 z M 22.92484 24.163384 21.89276 20.933106 q -0.113931 -0.361899 -0.160844 -0.864535 h -0.05361 q -0.03351 0.422214 -0.167545 0.837727 l -1.045485 3.257086 z m 2.065112 7.055953 -1.51223 1.51223 2.923087 2.922042 -2.923087 2.923086 1.51223 1.511185 2.922042 -2.922042 2.923086 2.922042 1.511185 -1.511185 -2.922041 -2.923086 2.922041 -2.922042 -1.511185 -1.51223 -2.923086 2.923087 z m -9.484387 0.416058 h 6.108175 v 1.176737 h -6.108175 z m 0 -2.419244 h 9.203284 v 1.176735 h -9.203284 z m 0 4.83849 h 2.713696 v 1.176736 H 15.505565 Z M 13.556469 15.93316 a 0.67332766 0.67332766 0 0 0 -0.672794 0.672793 v 21.686339 a 0.67332766 0.67332766 0 0 0 0.672794 0.672793 h 7.585433 a 8.7739586 8.7739586 0 0 1 -0.02871 -0.672793 8.7739586 8.7739586 0 0 1 0.0335 -0.673751 H 14.230219 V 17.279703 h 14.983289 v 12.267233 a 8.7739586 8.7739586 0 0 1 0.67375 -0.02871 8.7739586 8.7739586 0 0 1 0.672794 0.0335 V 16.605953 A 0.67332766 0.67332766 0 0 0 29.887258 15.93316 Z" />
</DrawingGroup.Children>
@@ -923,7 +874,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 24.706673 31.196264 -3.086181 3.086182 5.96548 5.963348 -5.96548 5.96548 3.086181 3.084049 5.963348 -5.963348 5.96548 5.963348 3.084049 -3.084049 -5.963347 -5.96548 5.963347 -5.963348 -3.084049 -3.086182 -5.96548 5.965481 z M 5.3507926 32.045363 H 17.81645 v 2.401501 H 5.3507926 Z m 0 -4.937233 H 24.132994 v 2.401501 H 5.3507926 Z M 26.434166 6.0031559 14.993666 17.443656 9.6399578 12.089946 6.0100982 15.719802 c 2.9622322 2.959826 6.0772448 6.092263 8.9969908 8.970151 L 30.064121 9.6329208 Z M 5.3507926 36.982596 h 5.5381524 v 2.401501 H 5.3507926 Z M 1.3730469 0 A 1.3741374 1.3741374 0 0 0 0 1.3730469 V 45.630859 a 1.3741374 1.3741374 0 0 0 1.3730469 1.373047 H 16.853516 a 17.906029 17.906029 0 0 1 -0.05859 -1.373047 17.906029 17.906029 0 0 1 0.06836 -1.375 H 2.7480469 V 2.7480469 H 33.326172 V 27.783203 a 17.906029 17.906029 0 0 1 1.375 -0.05859 17.906029 17.906029 0 0 1 1.373047 0.06836 V 1.3730469 A 1.3741374 1.3741374 0 0 0 34.701172 0 Z" />
</DrawingGroup.Children>
@@ -946,7 +897,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.bid}"
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
</DrawingGroup.Children>
@@ -958,7 +909,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="{mbts:BTSColor MarketData.ask}"
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
</DrawingGroup.Children>
@@ -970,7 +921,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 9.9417804 -0.02293875 1.9154138 8.0034278 17.4301 23.512569 1.9154138 39.027256 9.9417804 47.048077 25.450922 31.538936 40.965607 47.048077 48.986428 39.027256 33.47729 23.512569 48.986428 8.0034278 40.965607 -0.02293875 25.450922 15.491746 Z" />
</DrawingGroup.Children>
@@ -983,7 +934,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 69.641861 87.129943 a 36.813842 36.813842 0 0 0 -36.813776 36.813767 36.813842 36.813842 0 0 0 31.044618 36.31355 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.828154 39.50509 41.092964 41.092964 0 0 0 41.09258 41.0931 41.092964 41.092964 0 0 0 41.093104 -41.0931 41.092964 41.092964 0 0 0 -0.10284 -2.9068 41.092964 41.092964 0 0 0 -38.048841 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150946 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.12557 2.49907 27.542416 27.542416 0 0 0 27.54251 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 l -31.44563 -14.89935 a 36.813842 36.813842 0 0 0 1.55701 -10.53786 36.813842 36.813842 0 0 0 -36.813769 -36.813767 z" />
</DrawingGroup.Children>
@@ -995,7 +946,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 36.50044 86.999995 A 36.813842 36.813842 0 0 0 -0.3133356 123.81377 36.813842 36.813842 0 0 0 30.731282 160.12732 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.8281546 39.50509 41.092964 41.092964 0 0 0 41.0925806 41.0931 41.092964 41.092964 0 0 0 41.093099 -41.0931 41.092964 41.092964 0 0 0 -0.102836 -2.9068 41.092964 41.092964 0 0 0 -38.04884 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150948 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.125574 2.49907 27.542416 27.542416 0 0 0 27.542513 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 L 71.757201 134.35163 A 36.813842 36.813842 0 0 0 73.314213 123.81377 36.813842 36.813842 0 0 0 36.50044 86.999995 Z m 116.60981 115.628085 0.007 37.48967 h -37.49716 l 0.007 19.39257 h 37.48344 L 153.11751 297 h 19.38493 l 0.007 -37.48968 h 37.48342 L 210 240.11775 h -37.49727 l 0.007 -37.48967 z" />
</DrawingGroup.Children>
@@ -1020,7 +971,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="M 37.03498 86.999995 A 36.813842 36.813842 0 0 0 0.2212037 123.81377 36.813842 36.813842 0 0 0 31.265822 160.12732 l 1.208194 21.35839 1.804543 31.90967 a 41.092964 41.092964 0 0 0 -29.8281553 39.50509 41.092964 41.092964 0 0 0 41.0925813 41.0931 41.092964 41.092964 0 0 0 41.093099 -41.0931 41.092964 41.092964 0 0 0 -0.102836 -2.9068 41.092964 41.092964 0 0 0 -38.04884 -38.03902 l -2.962092 -52.36321 a 36.813842 36.813842 0 0 0 20.093347 -12.61939 l 33.150948 15.70705 v 5.3e-4 a 27.542416 27.542416 0 0 0 -0.125574 2.49907 27.542416 27.542416 0 0 0 27.542503 27.54249 27.542416 27.542416 0 0 0 27.5425 -27.5425 27.542416 27.542416 0 0 0 -27.5425 -27.5425 27.542416 27.542416 0 0 0 -22.44618 11.61479 L 72.291741 134.35163 A 36.813842 36.813842 0 0 0 73.848753 123.81377 36.813842 36.813842 0 0 0 37.03498 86.999995 Z m 145.03189 90.486735 -76.71431 76.71435 27.93311 27.93293 L 210 205.41986 Z m -0.008 22.13769 5.79522 5.79544 -54.57645 54.57644 -5.7952 -5.79522 z m -81.50016 59.76108 -10.071854 37.61451 37.606034 -10.0803 z" />
</DrawingGroup.Children>
@@ -1032,7 +983,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing
<GeometryDrawing
Brush="Black"
Geometry="m 139.80129 180.78831 a 20.783495 20.783495 0 0 0 -20.78345 20.78346 20.783495 20.783495 0 0 0 17.52643 20.50105 l 0.68209 12.05803 1.01877 18.01482 a 23.1993 23.1993 0 0 0 -16.83968 22.30284 23.1993 23.1993 0 0 0 23.1991 23.19938 23.1993 23.1993 0 0 0 23.19935 -23.19938 23.1993 23.1993 0 0 0 -0.058 -1.64105 23.1993 23.1993 0 0 0 -21.4807 -21.47518 l -1.67225 -29.56198 a 20.783495 20.783495 0 0 0 11.34383 -7.12437 l 18.71558 8.86752 v 3.1e-4 a 15.549251 15.549251 0 0 0 -0.0708 1.41086 15.549251 15.549251 0 0 0 15.5493 15.54927 15.549251 15.549251 0 0 0 15.54931 -15.54927 15.549251 15.549251 0 0 0 -15.54931 -15.54932 15.549251 15.549251 0 0 0 -12.67213 6.5572 l -17.75282 -8.41152 a 20.783495 20.783495 0 0 0 0.87902 -5.94921 20.783495 20.783495 0 0 0 -20.78345 -20.78346 z M 31.983713 88.265691 A 30.676234 30.676234 0 0 0 1.3075339 118.94187 30.676234 30.676234 0 0 0 27.17639 149.20122 l 1.006764 17.79752 1.503689 26.58969 A 34.241941 34.241941 0 0 0 4.8316412 226.50722 34.241941 34.241941 0 0 0 39.073263 260.74928 34.241941 34.241941 0 0 0 73.315316 226.50722 34.241941 34.241941 0 0 0 73.229625 224.08505 34.241941 34.241941 0 0 0 41.524291 192.3879 L 39.05604 148.75469 A 30.676234 30.676234 0 0 0 55.799422 138.2392 l 27.624018 13.08837 v 4.4e-4 a 22.950541 22.950541 0 0 0 -0.104638 2.08242 22.950541 22.950541 0 0 0 22.950618 22.9506 22.950541 22.950541 0 0 0 22.95062 -22.9506 22.950541 22.950541 0 0 0 -22.95062 -22.95061 22.950541 22.950541 0 0 0 -18.703946 9.67837 L 61.362463 127.72286 A 30.676234 30.676234 0 0 0 62.65989 118.94187 30.676234 30.676234 0 0 0 31.983713 88.265691 Z" />
</DrawingGroup.Children>
@@ -1156,7 +1107,7 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
<DrawingGroup.Children>
<GeometryDrawing
Brush="Black"
Geometry="M 0.814453 2e-6 C 0.365092 -7.56e-4 3.21e-4 0.36314 0 0.812502 v 23.945313 c 0.788895 1.126154 1.626953 -0.02112 1.626953 -0.02112 V 1.625002 h 16.241843 c 0 0 0.80538 -0.934678 -0.124651 -1.625002 z M 3.750875 3.43624 C 3.301514 3.435482 2.936743 3.799378 2.936422 4.24874 v 23.945313 c -7.6e-4 0.450124 0.364329 0.815213 0.814453 0.814453 h 7.439453 C 11.042745 28.716852 10.90913 28.418337 10.789937 28.113975 10.699431 27.872971 10.617994 27.628659 10.545796 27.381553 H 4.563375 V 5.06124 h 15.304687 v 7.039063 c 0.173002 -0.01023 0.346234 -0.01609 0.519531 -0.01758 0.343731 -2.6e-4 0.68726 0.01669 1.029297 0.05078 0.02606 0.0038 0.0521 0.0077 0.07813 0.01172 V 4.248738 C 21.494699 3.799376 21.129928 3.43548 20.680567 3.436238 Z m 13.332031 6.884766 c -1.226661 0 -2.230469 1.002456 -2.230469 2.228516 v 9.572265 l -0.824219 -0.8125 v 0.002 c -0.991917 -0.9815 -2.60964 -0.982302 -3.603515 0 -0.48293 0.477155 -0.751953 1.119962 -0.751953 1.796875 0 0.67607 0.269215 1.317958 0.751953 1.794922 l 4.841797 4.78711 0.0078 0.0078 c 2.115591 1.851014 4.144077 2.322266 5.615234 2.322266 0.547268 0 1.017683 -0.06414 1.388672 -0.142578 1.701281 -0.360558 3.108561 -1.372782 3.751954 -2.75586 0.419048 -0.9011 0.978455 -2.062137 0.923828 -3.261718 4.09e-4 -0.01025 0.0011 -0.0075 0.002 -0.02344 0.0015 -0.02655 0.0039 -0.01047 0.0039 -0.07813 v -5.113281 c 0 -1.161114 -0.955476 -2.109375 -2.121094 -2.109375 -0.04803 0 -0.09044 0.0088 -0.136719 0.01172 -0.207784 -1.015547 -1.021531 -1.826172 -2.095703 -1.826172 -0.343265 0 -0.63914 0.145124 -0.93164 0.285156 -0.397916 -0.616533 -1.045784 -1.0625 -1.830078 -1.0625 -0.189487 0 -0.354735 0.07615 -0.53125 0.121094 v -3.515625 c 0 -1.22606 -1.003809 -2.228516 -2.230469 -2.228516 z m 0 1.699219 c 0.148476 0 0.278642 0.05997 0.375 0.15625 0.09685 0.09678 0.15625 0.225142 0.15625 0.373047 v 5.625 1.740234 c 0 0.464846 0.384763 0.849609 0.849609 0.849609 0.464527 0 0.84961 -0.383921 0.84961 -0.849609 v -1.740234 c 0 -0.148598 0.05896 -0.275762 0.15625 -0.373047 0.09729 -0.09729 0.228277 -0.158203 0.375 -0.158203 0.146722 0 0.277709 0.06091 0.375 0.158203 0.09729 0.09729 0.15625 0.224449 0.15625 0.373047 v 0.77539 0.4375 c 0 0.465373 0.384236 0.84961 0.849609 0.84961 0.465373 0 0.849609 -0.384239 0.849609 -0.84961 v -0.425781 l 0.0059 -0.06836 c 0.0286 -0.265146 0.249414 -0.472657 0.52539 -0.472657 0.148238 0 0.278481 0.05996 0.375 0.15625 0.09649 0.09627 0.15625 0.225145 0.15625 0.373047 v 1.935547 c 0 0.465371 0.384237 0.84961 0.84961 0.84961 0.465372 0 0.849609 -0.384237 0.849609 -0.84961 v -0.65039 c 0.0016 -9e-6 2.95e-4 0 0.002 0 0.238806 0 0.421875 0.185008 0.421875 0.410156 l 0.002 5.0625 c -4.64e-4 0.0084 -0.0023 0.0401 -0.0039 0.07227 -0.0015 0.03041 -0.0059 0.02687 -0.002 0.101563 0.03253 0.757506 -0.372777 1.677807 -0.765625 2.521484 h -0.002 c -0.194342 0.418116 -0.532821 0.810028 -0.976563 1.126953 -0.443595 0.316821 -0.990203 0.55925 -1.585937 0.685547 -0.515602 0.10823 -1.298166 0.179402 -2.248047 -0.03125 -0.47546 -0.105441 -0.991926 -0.281044 -1.539063 -0.560547 -0.547306 -0.279588 -1.127683 -0.66381 -1.724609 -1.185547 L 11.620061 23.69601 c -0.162198 -0.160763 -0.248047 -0.365899 -0.248047 -0.587891 0 -0.223284 0.08648 -0.426518 0.248047 -0.585937 0.168295 -0.166151 0.386826 -0.249911 0.607422 -0.25 0.220595 -9e-5 0.437432 0.08144 0.605469 0.248047 l 2.240234 2.207031 0.01367 0.02148 0.04687 0.03516 0.03711 0.0332 0.02539 0.01758 0.01563 0.01367 0.01563 0.0098 c 0.114637 0.07747 0.248043 0.126397 0.388672 0.140625 l -0.04883 -0.0078 0.04102 0.0098 0.08594 0.0039 h 0.0078 c 0.06844 0 0.126002 -0.0085 0.169922 -0.01758 l 0.0059 -0.002 0.01953 -0.0039 h 0.02539 l 0.06641 -0.02734 0.02734 -0.0098 0.0098 -0.0039 c 0.102105 -0.04223 0.198238 -0.105586 0.28125 -0.189454 h 0.002 c 0.163006 -0.165519 0.243191 -0.385955 0.242188 -0.599609 v -0.002 -0.01953 -11.580078 c 0 -0.147904 0.0594 -0.276271 0.15625 -0.373047 0.09636 -0.09628 0.226522 -0.15625 0.375 -0.15625 z"
Geometry="M17.740002,11.999007C17.254002,11.999007,16.859001,12.394008,16.859001,12.879008L16.859001,24.458791 16.860127,24.479386C16.861003,24.607261 16.813003,24.735511 16.716002,24.834012 16.667502,24.883012 16.611378,24.91995 16.551394,24.944762L16.521562,24.955436 16.507633,24.960536 16.506465,24.960837 16.50574,24.961097 16.49194,24.964574 16.459728,24.972863C16.42719,24.979527,16.393502,24.983027,16.359002,24.983027L16.316763,24.980893 16.315716,24.980851 16.307903,24.980445C16.223895,24.971904,16.146014,24.942538,16.07951,24.897597L16.067752,24.887895 16.047276,24.873078 16.015234,24.844558 16.005502,24.836528 16.001236,24.831357 13.737002,22.600011C13.267003,22.134011 12.502003,22.135011 12.031003,22.600011 11.805003,22.82301 11.680003,23.120011 11.680003,23.436011 11.680003,23.751012 11.805003,24.048011 12.031003,24.272012L16.842003,29.031013C19.311003,31.189013 21.546002,31.119014 22.656003,30.886014 23.939002,30.614013 25.015002,29.846013 25.464002,28.880013 25.857001,28.036012 26.301002,27.080012 26.263001,26.195012 26.262002,26.178012 26.268003,26.074012 26.269002,26.056012L26.268003,20.974011C26.268003,20.55601 25.922002,20.215011 25.496001,20.215011 25.335502,20.215011 25.239001,20.246511 25.179877,20.283135L25.144,20.309762 25.144,21.215013C25.144,21.491012 24.919999,21.715013 24.644,21.715013 24.367999,21.715013 24.144,21.491012 24.144,21.215013L24.144,19.278017C24.144,18.793018 23.749,18.399019 23.263,18.399019 22.808312,18.399019 22.432727,18.745308 22.387555,19.18822L22.383004,19.277944 22.383004,19.716007C22.383004,19.992006 22.159003,20.216004 21.883002,20.216004 21.607003,20.216004 21.383002,19.992006 21.383002,19.716007L21.383002,19.278054 21.383002,18.504013C21.383002,18.018016 20.988002,17.623017 20.502002,17.623017 20.016002,17.623017 19.621001,18.018016 19.621001,18.504013L19.621001,20.24402C19.621001,20.52102 19.397001,20.74402 19.121001,20.74402 18.844001,20.74402 18.621001,20.52102 18.621001,20.24402L18.621001,18.504035 18.621001,12.879008C18.621001,12.394008,18.226001,11.999007,17.740002,11.999007z M17.740002,10.999005C18.777001,10.999005,19.621001,11.843007,19.621001,12.879008L19.621001,16.842839 19.687315,16.808824C19.933955,16.689783 20.210345,16.623022 20.502002,16.623022 21.247346,16.623022 21.892987,17.059031 22.197202,17.689331L22.211675,17.721288 22.212884,17.720384C22.513,17.517568 22.874499,17.399021 23.263,17.399021 24.267594,17.399021 25.091062,18.190155 25.141547,19.181478L25.143222,19.247425 25.174827,19.241074C25.276639,19.223799 25.383876,19.21501 25.496001,19.21501 26.473002,19.21501 27.268003,20.00401 27.268003,20.974011L27.268003,26.087012C27.268003,26.098012 27.262002,26.194013 27.262002,26.205012 27.311002,27.281013 26.790002,28.401013 26.371001,29.302012 25.783002,30.566012 24.472001,31.523013 22.863002,31.864013 22.513001,31.938014 22.067002,31.999013 21.547002,31.999013 20.149003,31.999013 18.216002,31.561013 16.161002,29.763013L11.328003,24.983012C10.910003,24.570012 10.680003,24.020011 10.680003,23.436011 10.680003,22.85101 10.910003,22.302011 11.328003,21.88901 12.187003,21.040011 13.583003,21.041011 14.440002,21.88901L15.859002,23.287391 15.859002,12.879008C15.859002,11.843007,16.703002,10.999005,17.740002,10.999005z M6.8659983,4.0500388L19.083997,4.0500388C20.668996,4.0500388,21.957997,5.3460296,21.957997,6.9390478L21.957997,14.653042C21.957997,14.93005 21.734998,15.153042 21.457997,15.153042 21.181996,15.153042 20.957997,14.93005 20.957997,14.653042L20.957997,6.9390478C20.957997,5.8980318,20.116998,5.0500394,19.083997,5.0500394L6.8659983,5.0500394C5.8319979,5.0500394,4.9909983,5.8980318,4.9909984,6.9390478L4.9909984,26.116061C4.9909983,27.157046,5.8319979,28.00504,6.8659983,28.00504L11.696996,28.00504C11.972997,28.00504 12.196996,28.229039 12.196996,28.50504 12.196996,28.781041 11.972997,29.00504 11.696996,29.00504L6.8659983,29.00504C5.2809954,29.00504,3.9909983,27.70905,3.9909983,26.116061L3.9909983,6.9390478C3.9909983,5.3460296,5.2809954,4.0500388,6.8659983,4.0500388z M6.4360008,0L17.467001,0C17.743001,0 17.967001,0.22300029 17.967001,0.50000024 17.967001,0.77600098 17.743001,1.0000012 17.467001,1.0000012L6.4360008,1.0000012C3.4380004,1.0000012,1.0000002,3.4210042,1.0000002,6.3970079L1.0000002,20.617026C1.0000002,20.893025 0.77600026,21.117026 0.50000048,21.117026 0.22400022,21.117026 2.3841858E-07,20.893025 0,20.617026L0,6.3970079C2.3841858E-07,2.8700035,2.8870004,0,6.4360008,0z"
/>
</DrawingGroup.Children>
</DrawingGroup>
@@ -1164,10 +1115,10 @@ M0,0L63.999001,0 63.999001,48.792999 0,48.792999z">
</DrawingImage>
<!-- Pin
<!-- Pin
<Path Data="F1M3,11L3,7 0,7 0,6 1,6 1,1 1,0 6,0 6,1 6,6 7,6 7,7 4,7 4,11 3,11z M2,1L2,6 4,6 4,1 2,1z" />
Unpin
Unpin
<Path Data="F1M0,3L4,3 4,0 5,0 5,1 10,1 11,1 11,6 10,6 5,6 5,7 4,7 4,4 0,4 0,3z M10,2L5,2 5,4 10,4 10,2z" />
-->
</ResourceDictionary>
</ResourceDictionary>
@@ -5,7 +5,6 @@ using mbts.co2;
using mbts.persist;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
@@ -19,7 +18,7 @@ namespace bts.shell
class MarketShellModule : IShellModule
{
[ImportingConstructor]
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import(LoginContainer.Exports.UserName)]string user_name) {
public MarketShellModule(IBus bus, MDConsumer consumer, UserSettingsHub settings_hub, OrdersModel orders_model, [Import("username")]string user_name) {
_tsf = new TimeAndSalesWindowFactory(this, bus, consumer, settings_hub);
_mvf = new MarketViewWindowFactory(this, bus, settings_hub, orders_model);
}
@@ -1,7 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using mbts.co2;
using mbts.collections;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
@@ -36,7 +36,6 @@ namespace mbts.spark {
readonly MyOrderSummarizer _order_summarizer = new MyOrderSummarizer();
readonly MDDisplayHelper.SizeFormatter _size_formatter = MDDisplayHelper.get_formatter(MarketSizeDisplay.get_default());
Property<uint> _bid_direct_size, _bid_merged_size, _ask_direct_size, _ask_merged_size;
double _raw_day_position;
string _bid_merged_size_text, _ask_merged_size_text;
ulong _bbo_ref, _last_ref;
@@ -90,10 +89,6 @@ namespace mbts.spark {
_dispose = Disposable.Create(
_orders_model.on_security_root_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_orders_model.on_security_positions(security.Id).OnDispatcher().Subscribe(t => {
if (Set(ref _raw_day_position, t.state.Values.Sum(p => p.Item2.raw_day_pos()), nameof(raw_day_position)))
raise_invalidated();
}),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
@@ -107,7 +102,6 @@ namespace mbts.spark {
public bool inside_bids => _inside_bids.value;
public bool inside_asks => _inside_asks.value;
public int position => _position.value;
public double raw_day_position => _raw_day_position;
public double last_price => _last_price.Value;
public uint last_size => _last_size.value;
public double bid_price => _bid_price.Value;
@@ -210,15 +204,13 @@ namespace mbts.spark {
}
}
void _show_order_book(Side side) {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_order_book(Side side) =>
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.OrderBookWindowFactory.OrderBookStartupData(
new bts.shell.OrderBookWindowFactory.FilterStates {
statuses = new[] { OrderStatus.Working },
sides = new[] { side },
security_criteria = new SecurityCriteria(security)
}));
void _show_order_ticket(MarketViewView.Columns col) {
var order = _orders_model.factory.order(security);
@@ -1,6 +1,4 @@
using bts.api;
using bts.collections;
using bts.utils;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
@@ -9,7 +7,9 @@ using mbts.ui.utility;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
using System.Windows.Media;
using static bts.shell.MarketViewWindowFactory;
@@ -22,12 +22,13 @@ namespace mbts.spark {
readonly OrdersModel _orders_model;
IWindowSettingsManager<GridSettings> _settings_manager;
public MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IWindowSettingsManager<GridSettings> settings_manager )
: this(bus, orders_model, state, state.SelectManyInSeries(s => s.security_criteria.subscribe_md(bus)), settings_manager) { }
// securities_connectable should NOT be connected already.
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IObservable<IEnumerable<SecurityMD>> securities_source, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_source, SecurityDataHelper.EqualityComparer.MDComparer), null) {
MarketViewVM(IBus bus, OrdersModel orders_model, IObservable<StartupData> state, IConnectableObservable<IEnumerable<SecurityMD>> securities_connectable, IWindowSettingsManager<GridSettings> settings_manager)
: base(RXCollection.Create(securities_connectable, SecurityDataHelper.EqualityComparer.MDComparer), null) {
_bus = bus;
_orders_model = orders_model;
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
@@ -39,15 +40,14 @@ namespace mbts.spark {
add_col(Columns.Security, "Security", r => r.security.ToString(), nameof(Row.security), DefaultColumnWidths.Security, security_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.Volume, "Volume", r => r.volume.ToString(), nameof(Row.volume), DefaultColumnWidths.Volume, volume_filter, DataGridFilterKnownTemplates.ULongMinMax);
add_col(Columns.Last, "Last", r => r.format_price(r.last_price), nameof(Row.last_price), DefaultColumnWidths.Price, last_price_filter, DataGridFilterKnownTemplates.DoubleMinMax);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UIntMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.LastSize, merged_size_col_header, r => r.last_size.ToString(), nameof(Row.last_size), DefaultColumnWidths.Size, last_size_filter, DataGridFilterKnownTemplates.UShortMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.Position, "Pos", r => r.position.ToString(), nameof(Row.position), DefaultColumnWidths.Size, position_filter, DataGridFilterKnownTemplates.IntMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.RawDayPosition, "Raw Day Pos", r => SparkUtils.fmt_position(r.raw_day_position), nameof(Row.raw_day_position), DefaultColumnWidths.Size, raw_day_position_filter, DataGridFilterKnownTemplates.DoubleMinMax, true, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_working_orders_col(Columns.WorkingBids, working_col_header, nameof(Row.working_bids_text), nameof(Row.working_best_bid), nameof(Row.working_bids_click), working_bid_volume_filter, BTSColors.MarketData.bid, System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidImpliedSize, implied_size_col_header, r => r.bid_implied_size.ToString(), nameof(Row.bid_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.BidDirectSize, direct_size_col_header, r => r.bid_direct_size.ToString(), nameof(Row.bid_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, bid_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.BidMergedSize, merged_size_col_header, nameof(Row.bid_merged_size), nameof(Row.bid_merged_size_text), nameof(Row.bid_merged_size_click), bid_merged_size_filter, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Bid, "Bid", nameof(Row.bid_price), nameof(Row.bid_price_click), bid_price_filter, BTSColors.header_text, BTSColors.MarketData.bid_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_price_col(Columns.Ask, "Ask", nameof(Row.ask_price), nameof(Row.ask_price_click), ask_price_filter, BTSColors.header_text, BTSColors.MarketData.ask_brush, h_alignment: System.Windows.HorizontalAlignment.Right);
add_hl_merged_size_col(Columns.AskMergedSize, merged_size_col_header, nameof(Row.ask_merged_size), nameof(Row.ask_merged_size_text), nameof(Row.ask_merged_size_click), ask_merged_size_filter, BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskDirectSize, direct_size_col_header, r => r.ask_direct_size.ToString(), nameof(Row.ask_direct_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_direct_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.AskImpliedSize, implied_size_col_header, r => r.ask_implied_size.ToString(), nameof(Row.ask_implied_size), DefaultColumnWidths.ImpliedDirectMergedSize, ask_implied_size_filter, DataGridFilterKnownTemplates.UIntMinMax, font_color: BTSColors.MarketData.ask, h_alignment: System.Windows.HorizontalAlignment.Right);
@@ -58,12 +58,12 @@ namespace mbts.spark {
add_col(Columns.SessionHigh, "High", r => r.format_price(r.session_high), nameof(Row.session_high), DefaultColumnWidths.Price, session_high_filter, DataGridFilterKnownTemplates.DoubleMinMax, h_alignment: System.Windows.HorizontalAlignment.Right);
add_col(Columns.SecurityKey, "Security Key", r => r.security_key.ToString(), nameof(Row.security_key), DefaultColumnWidths.SecurityKey, security_key_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.security.market_description(), nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.security.cleared_alias(), nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ProductDescription, "Product Description", r => r.security.product_name(), nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.HubAlias, "Hub Alias", r => r.security.hub_alias(), nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.security.strip_name(), nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.security.mic_code(), nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MarketDescription, "Market Description", r => r.md.security.HasIce ? r.md.security.Ice.MarketDescription : string.Empty, nameof(Row.market_description), DefaultColumnWidths.SecurityKey, market_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ClearedAlias, "Cleared Alias", r => r.md.security.HasIce ? r.md.security.Ice.ClearedAlias : string.Empty, nameof(Row.cleared_alias), DefaultColumnWidths.SecurityKey, cleared_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.ProductDescription, "Product Description", r => r.md.security.HasIce ? r.md.security.Ice.ProductName : string.Empty, nameof(Row.product_description), DefaultColumnWidths.SecurityKey, product_description_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.HubAlias, "Hub Alias", r => r.md.security.HasIce ? r.md.security.Ice.HubAlias : string.Empty, nameof(Row.hub_alias), DefaultColumnWidths.SecurityKey, hub_alias_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.StripName, "Strip Name", r => r.md.security.HasIce ? r.md.security.Ice.StripName : string.Empty, nameof(Row.strip_name), DefaultColumnWidths.SecurityKey, strip_name_filter, DataGridFilterKnownTemplates.Regex);
add_col(Columns.MicCode, "Mic Code", r => r.md.security.HasIce ? r.md.security.Ice.MicCode : string.Empty, nameof(Row.mic_code), DefaultColumnWidths.SecurityKey, mic_code_filter, DataGridFilterKnownTemplates.Regex);
//
void add_col(Columns col_key, string header, Func<Row, string> get_text, string prop, double width, IDataGridColumnFilter filter, string filter_template, bool is_animatable = false, Color? font_color = null, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, Columns>(col_key, get_text, filter, filter_template) {
@@ -77,18 +77,18 @@ namespace mbts.spark {
horizontal_alignment = h_alignment
});
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, h_alignment);
void add_hl_price_col(Columns col_key, string header, string prop, string command_prop, IDataGridColumnFilter filter, Color foreground, Brush background, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, price_binding(prop), prop, command_prop, filter, DataGridFilterKnownTemplates.DoubleMinMax, DefaultColumnWidths.MViewPrice, foreground, background, DefaultSelectionHighlight, h_alignment);
void add_hl_merged_size_col(Columns col_key, string header, string sort_prop, string text_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UIntMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, h_alignment);
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.UShortMinMax, DefaultColumnWidths.ImpliedDirectMergedSize, foreground, null, null, h_alignment);
void add_hl_working_orders_col(Columns col_key, string header, string text_prop, string sort_prop, string command_prop, IDataGridColumnFilter filter, Color foreground, System.Windows.HorizontalAlignment h_alignment) =>
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, h_alignment);
add_hl_col(col_key, header, new Binding(text_prop), sort_prop, command_prop, filter, DataGridFilterKnownTemplates.IntMinMax, working_orders_col_width, foreground, null, null, h_alignment);
void add_hl_col(
Columns col_key, string header, BindingBase binding, string prop, string command_prop,
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Color? background, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
IDataGridColumnFilter filter, string filter_template, double width, Color foreground, Brush background, Brush background_overlay, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new HyperlinkElementColumn<Row, Columns>(
col_key, binding, new Binding(command_prop), filter, filter_template) {
// it's necessary to have this id populated for grid settings
@@ -96,7 +96,8 @@ namespace mbts.spark {
Header = header,
sort_member_path = prop,
font_color_override = foreground,
background_color_override = background,
background = background,
background_overlay = background_overlay,
Width = DefaultColumnWidths.Price,
horizontal_alignment = h_alignment
});
@@ -110,8 +111,14 @@ namespace mbts.spark {
//
// make font-color immutable for Side, Size, (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
var col_settings_flags = new Dictionary<string, GridColumnSettingsFeatureFlags> {
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor },
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor | GridColumnSettingsFeatureFlags.ImmutableBackgroundColor},
{ Columns.Bid.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.Ask.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.BidMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskImpliedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskDirectSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.AskMergedSize.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingAsks.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor },
{ Columns.WorkingBids.ToString(), GridColumnSettingsFeatureFlags.ImmutableFontColor }
};
@@ -144,7 +151,6 @@ namespace mbts.spark {
session_low_filter.load_state(filters.session_low);
session_high_filter.load_state(filters.session_high);
position_filter.load_state(filters.position);
raw_day_position_filter.load_state(filters.raw_day_position);
working_bid_volume_filter.load_state(filters.working_bid_volume);
working_ask_volume_filter.load_state(filters.working_ask_volume);
volume_filter.load_state(filters.volume);
@@ -156,7 +162,8 @@ namespace mbts.spark {
bid_price_filter, bid_merged_size_filter, bid_direct_size_filter, bid_implied_size_filter,
ask_price_filter, ask_merged_size_filter, ask_direct_size_filter, ask_implied_size_filter,
last_price_filter, last_size_filter, day_change_filter, settlement_filter,
session_low_filter, session_high_filter, position_filter, raw_day_position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
session_low_filter, session_high_filter, position_filter, working_bid_volume_filter, working_ask_volume_filter, volume_filter, security_key_filter),
securities_connectable.Connect(),
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
WorkLoop.Start("MarketView", _refresh)
);
@@ -173,40 +180,6 @@ namespace mbts.spark {
return grid_settings_manager.create();
}
public static IComparer<Row> get_column_comparer(Columns column) {
switch (column) {
case Columns.Security: return wrap(r => r.security);
case Columns.SecurityKey: return wrap(r => r.security.Id);
case Columns.Volume: return wrap(r => r.volume);
case Columns.Last: return wrap(r => r.last_price);
case Columns.LastSize: return wrap(r => r.last_size);
case Columns.Position: return wrap(r => r.position);
case Columns.WorkingBids: return wrap(r => r.working_bid_volume);
case Columns.BidImpliedSize: return wrap(r => r.bid_implied_size);
case Columns.BidDirectSize: return wrap(r => r.bid_direct_size);
case Columns.BidMergedSize: return wrap(r => r.bid_merged_size);
case Columns.Bid: return wrap(r => r.bid_price);
case Columns.Ask: return wrap(r => r.ask_price);
case Columns.AskMergedSize: return wrap(r => r.ask_merged_size);
case Columns.AskDirectSize: return wrap(r => r.ask_direct_size);
case Columns.AskImpliedSize: return wrap(r => r.ask_implied_size);
case Columns.WorkingAsks: return wrap(r => r.working_ask_volume);
case Columns.Settlement: return wrap(r => r.settlement);
case Columns.DayChange: return wrap(r => r.day_change);
case Columns.SessionLow: return wrap(r => r.session_low);
case Columns.SessionHigh: return wrap(r => r.session_high);
case Columns.MarketDescription: return wrap(r => r.market_description);
case Columns.ClearedAlias: return wrap(r => r.security.cleared_alias());
case Columns.ProductDescription: return wrap(r => r.security.product_name());
case Columns.HubAlias: return wrap(r => r.security.hub_alias());
case Columns.StripName: return wrap(r => r.security.strip_name());
case Columns.MicCode: return wrap(r => r.security.mic_code());
default: return null;
}
IComparer<Row> wrap<T>(Func<Row, T> selector) =>
Comparer<T>.Default.wrap(selector);
}
#region Filters
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
@@ -224,7 +197,6 @@ namespace mbts.spark {
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_low_filter { get; } = new DoubleMinMax<Row>(r => r.session_low, nameof(Row.session_low));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> session_high_filter { get; } = new DoubleMinMax<Row>(r => r.session_high, nameof(Row.session_high));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> position_filter { get; } = new IntMinMax<Row>(r => r.position, nameof(Row.position));
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> raw_day_position_filter { get; } = new DoubleMinMax<Row>(r => r.raw_day_position, nameof(Row.raw_day_position));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_bid_volume_filter { get; } = new IntMinMax<Row>(r => r.working_bid_volume, nameof(Row.working_bid_volume));
public IDataGridColumnPersistentFilter<Row, IntMinMaxState> working_ask_volume_filter { get; } = new IntMinMax<Row>(r => r.working_ask_volume, nameof(Row.working_ask_volume));
public IDataGridColumnPersistentFilter<Row, ULongMinMaxState> volume_filter { get; } = new ULongMinMax<Row>(r => r.volume, nameof(Row.volume));
@@ -256,7 +228,6 @@ namespace mbts.spark {
session_low = session_low_filter.get_state(),
session_high = session_high_filter.get_state(),
position = position_filter.get_state(),
raw_day_position = raw_day_position_filter.get_state(),
working_bid_volume = working_bid_volume_filter.get_state(),
working_ask_volume = working_ask_volume_filter.get_state(),
volume = volume_filter.get_state(),
@@ -8,34 +8,8 @@ namespace mbts.ui.spark {
/// Interaction logic for MarketSheetsView.xaml
/// </summary>
partial class MarketViewView : UserControl {
public enum Columns {
Security,
Volume,
Last,
LastSize,
Position,
RawDayPosition,
WorkingBids,
Bid,
BidMergedSize,
BidDirectSize,
BidImpliedSize,
Ask,
AskMergedSize,
AskDirectSize,
AskImpliedSize,
WorkingAsks,
Settlement,
DayChange,
SessionLow,
SessionHigh,
SecurityKey,
MarketDescription,
ClearedAlias,
ProductDescription,
HubAlias,
StripName,
MicCode
public enum Columns { Security, Volume, Last, LastSize, Position, WorkingBids, Bid, BidMergedSize, BidDirectSize, BidImpliedSize, Ask, AskMergedSize, AskDirectSize, AskImpliedSize, WorkingAsks, Settlement, DayChange, SessionLow, SessionHigh, SecurityKey,
MarketDescription, ClearedAlias, ProductDescription, HubAlias, StripName, MicCode
}
public MarketViewView() {
@@ -1,26 +1,22 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows;
namespace mbts.spark {
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
const string _window_type = "MarketView", _window_type_display_name = "Market View";
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
readonly OrdersModel _orders_model;
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
readonly IWindowSettingsManager<GridSettings> _settings_manager;
@@ -29,17 +25,13 @@ namespace mbts.spark {
MarketViewWindowFactory.StartupData _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
SecurityCriteria
_selected_security_criteria_backing,
_sorted_selected_security_criteria_backing;
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, IWindowSettingsManager<GridSettings> settings_manager)
public MarketViewWindow(MarketViewWindowFactory factory, IBus bus, OrdersModel orders_model, MarketViewWindowFactory.StartupData state, WindowSettingsManager<GridSettings> settings_manager)
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
_orders_model = orders_model;
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
}
event Action<MarketViewWindowFactory.StartupData> _state_changed;
@@ -50,53 +42,12 @@ namespace mbts.spark {
public override MarketViewWindowFactory.StartupData SaveState =>
_state = new MarketViewWindowFactory.StartupData(_state.security_criteria, _vm.get_filter_states(), _vm.get_grid_save_state(), _vm.rows_view.get_sorting_criteria(_vm.columns));
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
public override double MinWidth => MarketViewWindowFactory.StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
SecurityCriteria _selected_security_criteria {
get {
if (_selected_security_criteria_backing == null) {
if (_sorted_selected_security_criteria_backing != null) {
_selected_security_criteria_backing = _sorted_selected_security_criteria_backing;
} else if (_vm != null)
_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Select(r => r.security));
}
return _selected_security_criteria_backing;
}
}
SecurityCriteria _sorted_selected_security_criteria {
get {
if (_sorted_selected_security_criteria_backing == null) {
if (_vm != null) {
var sorting_criteria = _vm.rows_view.get_sorting_criteria(_vm.columns);
var comp_builder = new ComparerBuilder<MarketViewVM.Row>();
foreach (var sc in sorting_criteria) {
if (_vm.columns.TryGetFirst(col => string.Equals(col.id, sc.col_name, StringComparison.CurrentCultureIgnoreCase), out var match)) {
if (match is ui.controls.DIYGrid.IKeyedColumn<MarketViewView.Columns> kcol) {
var col_comp = MarketViewVM.get_column_comparer(kcol.key);
if (col_comp != null) {
comp_builder.add(sc.is_ascending ? col_comp : col_comp.reverse());
} else {
Log.Ui.warn($"{_window_type} is unable to find the row comparer for the '{sc.col_name}' column.");
}
} else {
Log.Ui.warn($"{_window_type} is unable find a column with id '{sc.col_name}.'");
}
}
}
var comp = comp_builder.to_comparer();
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
}
}
return _sorted_selected_security_criteria_backing;
}
}
bool _has_selected_rows() => _vm != null && _vm.selected_rows.Count > 0;
public string thread_id => _thread_id;
public void thread_init() {
_init_guard.check_access();
@@ -104,85 +55,39 @@ namespace mbts.spark {
var state_src = Observable.FromEvent<Action<MarketViewWindowFactory.StartupData>, MarketViewWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new MarketViewVM(_bus, _orders_model, state_src, _settings_manager);
_view = new MarketViewView { DataContext = _vm };
_view._grid.Sorting += on_grid_sorting;
/// The 'CanExecute' plumbing for the cmds below is designed to avoid potentially costly .ToList() operations on the selected_rows/securities.
/// Some of the cmds post ITags with data that is, or has, a SecurityCriteria member; the SecurityCriteria constructor creates a new List of the specified securities.
/// For these commands, the objective is to evaluate 'CanExecute' without instantiating the SecurityCriteria (or more importantly, without creating a new List).
// show time and sales
var show_tas_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
if (sc.is_defined())
_bus.post(SparkTags.ShowTimeAndSalesTag, sc);
}, _has_selected_rows);
var show_tas_actual_cmd = SparkCommands.ShowTimeAndSales;
var show_tas_cmd_wrapper = new SparkCommandCommand(show_tas_actual_cmd.title, show_tas_actual_cmd.icon, show_tas_cmd);
// show fills
var show_fills_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
new FillsFilterStates {
security = sc.is_defined() ? sc : _state.security_criteria
}));
});
var show_fills_actual_cmd = SparkCommands.ShowFills();
var show_fills_cmd_wrapper = new SparkCommandCommand(show_fills_actual_cmd.title, show_fills_actual_cmd.icon, show_fills_cmd, SparkCommands.KeyGestures.ShowFills);
// show synthetic create
var show_synth_create_cmd = new DelegateCommand(() => {
var sc = _sorted_selected_security_criteria;
if (sc.is_defined())
_bus.post(SparkTags.ShowSyntheticCreateTag, sc);
}, () => _vm.selected_rows.Count > 0 && _vm.selected_rows.Count <= 24);
var show_synth_create_actual_cmd = SparkCommands.ShowSyntheticCreate();
var show_synth_create_cmd_wrapper = new SparkCommandCommand(show_synth_create_actual_cmd.title, show_synth_create_actual_cmd.icon, show_synth_create_cmd, SparkCommands.KeyGestures.ShowSyntheticCreate);
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
// commands
_commands = new List<SparkCommand> {
SparkCommands.ShowLadders(SparkCommands.KeyGestures.ShowLadder).complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
show_fills_cmd_wrapper,
show_tas_cmd_wrapper,
show_synth_create_cmd_wrapper,
SparkCommands.ShowLadders.complete(_vm.selected_security_mds_observable(), smds => smds.Count > 0),
SparkCommands.ShowOrderTicketFromSecurity.complete(_vm.current_security_md, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills.complete(_vm.selected_securities_observable(), _ => true,
secs => new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
})
),
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate.complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
SparkCommands.change_securities_command(() => (update_securities_cmd, _state.security_criteria)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.market_view_grid,
UserSettings.FileExtensions.GRID,
s => _vm.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.columns.Select(c => c.create()))
s.with(_vm.columns.Select( c => c.create())),
this.observe_title()
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Market View Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
};
//
_vm.selection_changed += on_selection_changed;
_subscriptions = new CompositeDisposable(_bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())))) {
() => {
_vm.selection_changed -= on_selection_changed;
_view._grid.Sorting -= on_grid_sorting;
}
};
void on_selection_changed((IEnumerable<MarketViewVM.Row> removed, IEnumerable<MarketViewVM.Row> added) change) {
// invalidate selected security criteria
_selected_security_criteria_backing = null;
_sorted_selected_security_criteria_backing = null;
show_tas_cmd.RaiseCanExecuteChanged();
show_fills_cmd.RaiseCanExecuteChanged();
show_synth_create_cmd.RaiseCanExecuteChanged();
}
void on_grid_sorting(object sender, ui.controls.DIYGridSortingEventArgs e) =>
_sorted_selected_security_criteria_backing = null;
_subscriptions = _bus.subscribe(update_securities_cmd.tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.get_filter_states(), _vm.grid_settings_manager.create())));
}
public override void Dispose() {
@@ -1,6 +1,4 @@
using bts.api;
using bts.proto;
using bts.settings;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -9,7 +7,6 @@ using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@@ -38,8 +35,13 @@ namespace bts.shell {
void _show(SecurityCriteria criteria) =>
_bus.post(SparkTags.ShowWindow, _create(StartupData.Default(criteria)));
IShellWindow _create(StartupData state) =>
new MarketViewWindow(this, _bus, _orders_model, state, _settings_hub.create_mview_settings_manager(state.grid));
IShellWindow _create(StartupData state) {
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
get: us => us.market_view_grid,
set: (us, gs) => us.with(provided_market_view_gridsettings: gs));
return new MarketViewWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
@@ -66,29 +68,7 @@ namespace bts.shell {
_sorting_criteria = sort;
}
public static GridSettings sanitize(GridSettings s) {
if (s == null) {
return null;
}
// column upgrade
var columns = s.columns.ToList();
var bid_col_index = columns.FindIndex(x => x.id == col_id(Columns.Bid));
var ask_col_index = columns.FindIndex(x => x.id == col_id(Columns.Ask));
if (bid_col_index > -1 && ask_col_index > -1) {
if (!columns[bid_col_index].background_color_override.HasValue) {
columns[bid_col_index] = columns[bid_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.bid.ToUint()));
}
if (!columns[ask_col_index].background_color_override.HasValue) {
columns[ask_col_index] = columns[ask_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.ask.ToUint()));
}
}
// and enable background color upgrade
return s.with(columns, enable_background_color: true);
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion, enable_background_color: true);
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
@@ -102,13 +82,12 @@ namespace bts.shell {
public static GridColumnSettings last => new GridColumnSettings(col_id(Columns.Last), DefaultColumnHeaders.Last, width: DefaultColumnWidths.Price);
public static GridColumnSettings last_size => new GridColumnSettings(col_id(Columns.LastSize), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings position => new GridColumnSettings(col_id(Columns.Position), DefaultColumnHeaders.Pos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings raw_day_position => new GridColumnSettings(col_id(Columns.RawDayPosition), DefaultColumnHeaders.RawDayPos, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right, is_visible: false);
public static GridColumnSettings working_bids => new GridColumnSettings(col_id(Columns.WorkingBids), DefaultColumnHeaders.Working, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.Working, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_implied_size => new GridColumnSettings(col_id(Columns.BidImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_direct_size => new GridColumnSettings(col_id(Columns.BidDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid_merged_size => new GridColumnSettings(col_id(Columns.BidMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.bid, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right, background_color_override: BTSColors.MarketData.bid);
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right, background_color_override: BTSColors.MarketData.ask);
public static GridColumnSettings bid => new GridColumnSettings(col_id(Columns.Bid), DefaultColumnHeaders.Bid, font_color_override: BTSColors.header_text, width:DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask => new GridColumnSettings(col_id(Columns.Ask), DefaultColumnHeaders.Ask, font_color_override: BTSColors.header_text, width: DefaultColumnWidths.MViewPrice, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_merged_size => new GridColumnSettings(col_id(Columns.AskMergedSize), DefaultColumnHeaders.MergedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_direct_size => new GridColumnSettings(col_id(Columns.AskDirectSize), DefaultColumnHeaders.DirectSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings ask_implied_size => new GridColumnSettings(col_id(Columns.AskImpliedSize), DefaultColumnHeaders.ImpliedSize, font_color_override: BTSColors.MarketData.ask, width: DefaultColumnWidths.ImpliedDirectMergedSize, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
@@ -140,7 +119,6 @@ namespace bts.shell {
yield return last;
yield return last_size;
yield return position;
yield return raw_day_position;
yield return working_bids;
yield return bid_implied_size;
yield return bid_direct_size;
@@ -195,8 +173,6 @@ namespace bts.shell {
public DoubleMinMaxState session_high;
[ProtoMember(12)]
public IntMinMaxState position;
[ProtoMember(27)]
public DoubleMinMaxState raw_day_position;
[ProtoMember(13)]
public IntMinMaxState working_bid_volume;
[ProtoMember(14)]
@@ -226,46 +202,5 @@ namespace bts.shell {
[ProtoMember(26)]
public string mic_code;
}
// migration rules
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class AddMissingColumnsMigrationRule : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) =>
settings.add_missing_columns(StartupData.DefaultGridSettings.columns, out new_settings);
}
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class InitBidAskBackgroundOverrideMigrationRule : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings s, out GridSettings new_settings) {
var columns = s.columns.ToList();
var bid_col_index = columns.FindIndex(x => x.id == col_id(Columns.Bid));
var ask_col_index = columns.FindIndex(x => x.id == col_id(Columns.Ask));
bool applied = false;
if (bid_col_index > -1 && ask_col_index > -1) {
if (!columns[bid_col_index].background_color_override.HasValue) {
columns[bid_col_index] = columns[bid_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.bid.ToUint()));
applied = true;
}
if (!columns[ask_col_index].background_color_override.HasValue) {
columns[ask_col_index] = columns[ask_col_index].with(background_color_override: new NullableColorProto(BTSColors.MarketData.ask.ToUint()));
applied = true;
}
}
new_settings = applied ? s.with(columns) : default;
return applied;
}
}
[Export(typeof(IMarketViewGridSettingsMigrationRule))]
class InitBackgroundOverrideMigrationRule : IMarketViewGridSettingsMigrationRule {
public bool apply(GridSettings settings, out GridSettings new_settings) {
if (!settings.enable_background_color) {
new_settings = settings.with(enable_background_color: true);
return true;
}
new_settings = default;
return false;
}
}
}
}
}
@@ -1,12 +1,23 @@
using bts.api;
using bts.co2;
using mbts.co2;
using mbts.collections.extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
namespace mbts.spark {
struct TradeInfo {
public readonly SecurityImmutable security;
public readonly TradeMessageImmutable trade;
public TradeInfo(SecurityImmutable security, TradeMessageImmutable trade) {
this.security = security;
this.trade = trade;
}
}
class TimeAndSalesModel : IDisposable {
internal readonly SecurityCriteria security_criteria;
@@ -1,23 +1,22 @@
using bts.api;
using bts.co2;
using bts.collections;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using mbts.ui.spark;
using mbts.ui.utility;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
using mbts.persist;
using mbts.ui.settings;
using System.Reactive.Linq;
namespace mbts.spark {
class TimeAndSalesVM : SecurityGrid<TimeAndSalesVM.Row, TradeInfo> {
@@ -49,8 +48,13 @@ namespace mbts.spark {
add_column(TimeAndSalesView.Columns.Aggressor, DefaultColumnHeaders.Aggressor, DefaultColumnWidths.Aggressor, nameof(Row.aggressor_side), aggressor_filter, DataGridFilterKnownTemplates.EnumValues);
add_column(TimeAndSalesView.Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, DefaultColumnWidths.SecurityKey, nameof(Row.security_key), security_key_filter, DataGridFilterKnownTemplates.Regex);
void add_column(TimeAndSalesView.Columns col_key, string header, double width, string sort_member_path, IDataGridColumnFilter filter, string filter_template, System.Windows.HorizontalAlignment h_alignment = System.Windows.HorizontalAlignment.Left) =>
columns.Add(new RenderTextColumn<Row, TimeAndSalesView.Columns>(col_key, r => r[col_key], filter, filter_template, r => _get_aggressor_brush(r))
{ id = col_id(col_key), Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
columns.Add(new TimeAndSalesColumn(
col_key
, (r) => _get_aggressor_brush(r)
, filter
, filter_template
)
{ Header = header, Width = width, sort_member_path = sort_member_path, horizontal_alignment = h_alignment }
);
// be sure to load GridSettings AFTER the columns collection has been populated, the grid settings manager looks columns by id to apply the appropriate settings,
@@ -137,7 +141,7 @@ namespace mbts.spark {
public Row(TimeAndSalesVM parent, SecurityImmutable security, TradeMessageImmutable trade) : base(security) {
_trade_key = trade.Key;
time = ((ulong)trade.Time).lldatetime().ToLocalTime();
price = trade.price();
price = trade.Mantissa * Math.Pow(10, trade.Exponent);
size = trade.Size;
aggressor_side = trade.AggressorSide;
show_ladder = new DelegateCommand(() => parent._show_ladder_async(this).ignore());
@@ -1,13 +1,11 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using mbts.co2;
using mbts.persist;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using mbts.ui.spark;
using mbts.ui.utility;
using mbts.utils;
@@ -18,9 +16,10 @@ using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media;
using static bts.shell.TimeAndSalesWindowFactory;
using static mbts.ui.controls.DIYGrid;
namespace mbts.spark {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
readonly MDConsumer _consumer;
readonly TimeAndSalesWindowFactory _factory;
@@ -49,7 +48,7 @@ namespace mbts.spark {
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public override StartupData SaveState => _state = _vm.value.create();
public override void Dispose() =>
@@ -63,25 +62,26 @@ 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(SparkCommands.KeyGestures.ShowLadder).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowOrderTicketFromSecurity(SparkCommands.KeyGestures.ShowOrderTicket).complete(current_smd_source, md => md.HasValue, ConvertUtils.ValueOrDefault),
SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.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)),
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
s => s.time_and_sales_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
s.with(_vm.value.columns.Select( c => c.create())),
this.observe_title()
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { DataContext = vm };
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Time and Sales Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -92,8 +92,8 @@ namespace mbts.spark {
_initialize();
bool always_true<T>(T _) => true;
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsSettings(new FillsFilterStates {
FillsWindowFactory.StartupData get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
});
void update_criteria(SecurityCriteria c) {
@@ -26,10 +26,7 @@ namespace bts.ui {
public SettingsShellModule(IBus bus, UserSettingsHub settings_hub, WorkspacesModel ws) {
// todo: settings dialog
ws_model = ws;
var actions_list = new List<SparkCommand>(2) {
SparkCommands.ShowGlobalAlerts,
SparkCommands.ShowWorkspaces
};
var actions_list = new List<SparkCommand>(2) { SparkCommands.ShowWorkspaces };
if (Configuration.get<bts.config.toggles.ToastNotifications>().enabled) {
actions_list.Add(SparkCommands.ShowSettings);
}
@@ -59,7 +59,6 @@
MarketDataTypeTemplate="{StaticResource SimpleValueTextTemplate}"
MarketDataCalculationTemplate="{StaticResource SimpleValueTextTemplate}"
ConditionalPriceTypeTemplate="{StaticResource SimpleValueTextTemplate}"
ShowQuantityTemplate="{StaticResource SimpleValueTextTemplate}"
>
<usp:DataGridParameterCellTemplateConverter.BooleanTemplate>
<DataTemplate>
@@ -116,16 +115,6 @@
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}"/>
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.UIntTemplate>
<usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
<DataTemplate>
<!-- Note: the motivation for this Template was to enforce a Minimum via the TickUpDownUInt -->
<!-- As of 2019/06/17, we believe such a Minimum/Maximum is not actually getting enforced and -->
<!-- furthermore is not the appropriate way to go about enforcing said value. It is near the end -->
<!-- of the cycle though so that is why we are leaving this Template in here for now until we -->
<!-- can come up with a better solution. -->
<c:TickUpDownUInt Value="{Binding value}" IsEnabled="{Binding enabled}" />
</DataTemplate>
</usp:DataGridParameterCellTemplateConverter.ShowQuantityTemplate>
<usp:DataGridParameterCellTemplateConverter.MarketDataTypeTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding value}" ItemsSource="{u:EnumBindingSource {u:Nested sp:LegSpreaderParams.Types.MarketDataType}}" ItemTemplate="{StaticResource EnumDescriptionTemplate}"/>
@@ -374,72 +363,6 @@
Height="{TemplateBinding Height}"
Padding="{TemplateBinding Padding}"
>
<Border.Resources>
<!--Leg Count States-->
<sys:String x:Key="LegCountStates">LegCountStates</sys:String>
<sys:String x:Key="LegCountStates.TwoOrFewer">TwoOrFewerLegsState</sys:String>
<sys:String x:Key="LegCountStates.ThreeOrMore">ThreeOrMoreLegsState</sys:String>
<cv:ValueConverterPipe x:Key="LegCountStateConverter">
<cv:IntGreaterThanConverter Value="2"/>
<cv:BooleanToObjectConverter ValueForFalse="{StaticResource LegCountStates.TwoOrFewer}" ValueForTrue="{StaticResource LegCountStates.ThreeOrMore}"/>
</cv:ValueConverterPipe>
<!--Persistance States-->
<sys:String x:Key="PersistanceStates">PersistanceStates</sys:String>
<sys:String x:Key="PersistanceStates.Unpersisted">UnpersistedState</sys:String>
<sys:String x:Key="PersistanceStates.Persisted">PersistedState</sys:String>
<cv:BooleanToObjectConverter x:Key="PersistanceStateConverter" ValueForFalse="{StaticResource PersistanceStates.Persisted}" ValueForTrue="{StaticResource PersistanceStates.Unpersisted}"/>
</Border.Resources>
<u:StateManager.Groups>
<u:StateGroupsManager AttachToMembers="True">
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.LegTuples.Count, RelativeSource={RelativeSource Self}, Converter={StaticResource LegCountStateConverter}}"/>
<u:StateDefinition State="{Binding Group.Target.TemplatedParent.IsNew, RelativeSource={RelativeSource Self}, Converter={StaticResource PersistanceStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{StaticResource LegCountStates}">
<VisualState Name="{StaticResource LegCountStates.TwoOrFewer}"/>
<VisualState Name="{StaticResource LegCountStates.ThreeOrMore}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_Label" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgePricing_DropDown" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{StaticResource PersistanceStates}">
<VisualState Name="{StaticResource PersistanceStates.Unpersisted}"/>
<VisualState Name="{StaticResource PersistanceStates.Persisted}">
<Storyboard>
<!--Name-->
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Name_TextBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<!--Tick Factors-->
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickFactors_Picker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<!--Pricing-->
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_Label" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Pricing_DropDown" Storyboard.TargetProperty="(UIElement.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -454,9 +377,8 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="PART_Name_Label" VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
<TextBlock VerticalAlignment="Center" IsEnabled="{TemplateBinding IsNew}">Name</TextBlock>
<TextBox
x:Name="PART_Name_TextBox"
Grid.Column="1"
@@ -475,37 +397,20 @@
</u:Behavior.BindingUpdateGroups>
</TextBox>
<TextBlock
x:Name="PART_TickFactors_Label"
Grid.Column="2"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Ticks"
IsEnabled="True"
Grid.Column="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" Text="Ticks" IsEnabled="{TemplateBinding IsNew}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<c:TickFactorsPicker
x:Name="PART_TickFactors_Picker"
Grid.Column="3"
Margin="{StaticResource Margin.LHeaderPanelItem}"
HorizontalAlignment="Stretch"
IsEnabled="True"
x:Name="PART_TickFactorsPicker" Grid.Column="3" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" HorizontalAlignment="Stretch"
SelectedTickFactors="{Binding TickFactors, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Visibility="{Binding LegTuples.Count, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource IntNotZeroVisibilityConverter}}"
/>
<TextBlock
x:Name="PART_Pricing_Label"
Grid.Column="4"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
IsEnabled="True"
Text="Pricing"
/>
<TextBlock Grid.Column="4" VerticalAlignment="Center" Margin="{StaticResource Margin.LHeaderPanelItem}" IsEnabled="{TemplateBinding IsNew}" >Pricing</TextBlock>
<ComboBox
x:Name="PART_Pricing_DropDown"
Grid.Column="5"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Width="90"
IsEnabled="True"
IsEnabled="{TemplateBinding IsNew}"
ItemsSource="{x:Static usp:ExecutionStrategyEditor.PriceCalcItems}"
SelectedItem="{Binding price_calc}"
>
@@ -515,24 +420,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Column="6"
x:Name="PART_HedgePricing_Label"
VerticalAlignment="Center"
Margin="{StaticResource Margin.LHeaderPanelItem}"
Text="Hedge"
Visibility="Collapsed"
/>
<ComboBox
x:Name="PART_HedgePricing_DropDown"
Grid.Column="7"
HorizontalAlignment="Stretch"
Margin="{StaticResource Margin.LHeaderPanelItem}"
ItemsSource="{TemplateBinding HedgePricingItemsSource}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
Visibility="Collapsed"
/>
<c:HelpButton Grid.Column="8" Margin="3,0,0,0" ManPage="bts_spark/spreader/#creating-a-synthetic" />
</Grid>
<c:DataGridEx
x:Name="PART_DataGrid"
@@ -1,5 +1,4 @@
using bts.api;
using bts.collections;
using bts.spark;
using mbts.spark;
using mbts.ui.adorners;
@@ -184,29 +183,6 @@ namespace mbts.ui.spark {
public mbts.spark.ContractPickerModel ContractPickerModel { get => GetValue(ContractPickerModelProperty) as mbts.spark.ContractPickerModel; set => SetValue(ContractPickerModelProperty, value); }
#endregion
#region HedgePricingItemsSource
public static readonly DependencyProperty HedgePricingItemsSourceProperty =
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
nameof(HedgePricingItemsSource));
public IEnumerable HedgePricingItemsSource {
get => GetValue(HedgePricingItemsSourceProperty) as IEnumerable;
set => SetValue(HedgePricingItemsSourceProperty, value);
}
#endregion
#region HedgePricing
public static readonly DependencyProperty HedgePricingProperty =
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
nameof(HedgePricing),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public object HedgePricing {
get => GetValue(HedgePricingProperty);
set => SetValue(HedgePricingProperty, value);
}
#endregion
public override void OnApplyTemplate() =>
_connect_leg_tuples();
@@ -264,7 +240,7 @@ namespace mbts.ui.spark {
FrameworkElement _generate_element(IValueConverter cell_template_converter) {
var rv = new ContentControl();
rv.SetBinding(ContentControl.ContentProperty, new Binding());
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding() { Converter = cell_template_converter });
rv.SetBinding(ContentControl.ContentTemplateProperty, new Binding(nameof(ICell.data_type)) { Converter = cell_template_converter });
return rv;
}
}
@@ -275,7 +251,7 @@ namespace mbts.ui.spark {
public ExecutionStrategyErrorAdorner(ExecutionStrategyEditor editor) : base(editor) {
_name_text_box = child_property<TextBox>("PART_Name_TextBox");
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactors_Picker");
_tick_factors_picker = child_property<TickFactorsPicker>("PART_TickFactorsPicker");
}
protected override void OnRender(DrawingContext drawing_context) {
@@ -330,23 +306,17 @@ namespace mbts.ui.spark {
public DataTemplate MarketDataTypeTemplate { get; set; }
public DataTemplate MarketDataCalculationTemplate { get; set; }
public DataTemplate ConditionalPriceTypeTemplate { get; set; }
public DataTemplate ShowQuantityTemplate { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is ICell cell) {
if (cell.row is ShowQuantityQuantityModel) {
return ShowQuantityTemplate;
}
Type t = cell.data_type;
if (t == typeof(double)) return DoubleTemplate;
else if (t == typeof(bool)) return BooleanTemplate;
else if (t == typeof(int)) return IntTemplate;
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
}
return null;
Type t = (Type)value;
if (t == typeof(double)) return DoubleTemplate;
else if (t == typeof(bool)) return BooleanTemplate;
else if (t == typeof(int)) return IntTemplate;
else if (t == typeof(uint)) return UIntTemplate;
else if (t == typeof(QuantityMeasure)) return QuantityMeasureTemplate;
else if (t == typeof(LegSpreaderParams.Types.MarketDataType)) return MarketDataTypeTemplate;
else if (t == typeof(ConditionalQuoting.Types.PriceType)) return ConditionalPriceTypeTemplate;
else return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
@@ -1,7 +1,6 @@
using bts.api;
using bts.api.synthetic;
using bts.spark;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
@@ -15,14 +14,12 @@ namespace mbts.spark {
readonly IBus _bus;
readonly SyntheticSecurityService _service;
readonly ErrorReporter _error;
readonly uint _user_id;
[ImportingConstructor]
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error, [Import(LoginContainer.Exports.UserID)]uint user_id) {
public SpreaderModel(IBus bus, SyntheticSecurityService service, ErrorReporter error) {
_bus = bus;
_service = service;
_error = error;
_user_id = user_id;
}
public IObservable<IReadOnlyDictionary<uint, ExecutionStrategyImmutable>> strategy => _bus.subscribe(SparkTags.SyntheticstrategiesTag);
@@ -42,40 +39,29 @@ namespace mbts.spark {
return (result, success);
});
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
lookup_strategy_async(security.Id);
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(SecurityImmutable security) =>
await lookup_strategy_async(security.Id);
public Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) =>
_error.safe_execute(
() => _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } }),
mdc => mdc.Data.Count == 1 && mdc.Data[0].SecurityKey == id,
mdc => $"did not find strategy for security id '{id}.'",
(mdc, success) => success ? mdc.Data[0].Strategy : (ExecutionStrategyImmutable?)null,
"lookup strategy");
public Task<uint?> create_synthetic(SecurityImmutable synthetic) {
if (synthetic.ContractCase != Security.ContractOneofCase.Synthetic)
return null;
if (synthetic.Synthetic.UserId != _user_id) {
var msynth = synthetic.CloneToMutable();
msynth.Synthetic.UserId = _user_id;
synthetic = new SecurityImmutable(msynth);
public async Task<ExecutionStrategyImmutable?> lookup_strategy_async(uint id) {
var result = await _service.query_security_metadata_async(new MetadataQuery { SecurityKey = { id } });
if (result.success) {
var metadata = result.value.Data.FirstOrDefault(i => i.SecurityKey == id);
if (metadata != null) {
return metadata.Strategy;
}
}
return _error.safe_execute(
() => _service.create_synthetic_async(synthetic),
r => r.Error == CreateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success ? r.Key : (uint?)null,
"creating synthetic");
return null;
}
public Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) =>
_error.safe_execute(
() => _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() }),
r => r.Error == UpdateResult.Types.Error.Ok,
r => r.Error.ToString(),
(r, success) => success,
"saving strategy");
public async Task<uint?> create_synthetic(SecurityImmutable? synthetic) {
var result = await _service.create_synthetic_async(synthetic.Value);
return _error.handle_error(result, "Error creating synthetic: {0}", r => r.Error, e => e == CreateResult.Types.Error.Ok, r => new uint?(r.Key), () => null).result;
}
public async Task<bool> save_strategy(uint synthetic, ExecutionStrategyImmutable strategy) {
var result = await _service.update_security_metadata_async(new Metadata { SecurityKey = synthetic, Strategy = strategy.CloneToMutable() });
return _error.handle_error(result, "Error saving execution strategy: {0}", r => r.Error, e => e == UpdateResult.Types.Error.Ok);
}
public async Task<bool> verify_name_is_unique(string name) {
var (result, success) = await strategy_tuple.FirstAsyncTask(SparkUtils.DefaultTimeOut);
@@ -108,9 +108,6 @@ namespace bts.shell {
NTicks = 2,
PriceType = ConditionalQuoting.Types.PriceType.Leg
};
public static NullableQuantity ShowQuantity => new NullableQuantity {
Quantity = 1
};
public static QuoteInsideMarketDistanceWithTravel QuoteInsideMarketDistanceWithTravel => new QuoteInsideMarketDistanceWithTravel { Priority = RulePriority };
public static QuoteInsideMarketDistance QuoteInsideMarketDistance => new QuoteInsideMarketDistance { Priority = RulePriority };
#endregion
@@ -12,9 +12,6 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="ScrollViewer">
<Setter Property="u:ScrollViewerCorrector.FixNestedScrolling" Value="False"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
@@ -34,8 +31,6 @@
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
/>
<c:DialogSkirtOKCancel Grid.Row="1" OKCommand="{Binding commit}" CloseOnOK="False"/>
</Grid>
@@ -16,7 +16,7 @@ using System.Windows;
using static bts.shell.SyntheticManagementWindowFactory;
namespace mbts.spark {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
const string _window_type = "SyntheticManagement";
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
StartupData _state;
@@ -33,20 +33,8 @@ namespace mbts.spark {
_state = state;
_settings_manager = settings_manager;
Icon.Freeze();
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public string thread_id { get; }
public void thread_init() {
Log.notnull(_state);
@@ -66,27 +54,27 @@ namespace mbts.spark {
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
current_row_connectable_builder.create(observe_row_strategy),
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
), SparkCommands.ShowLadder.complete(
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
};
return ob_settings;
}), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
filters = new OrderBookWindowFactory.FilterStates {
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
}
}
), SparkCommands.window_settings_command(new DialogCommand(() => {
var vm = new WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
_settings_manager,
us => us.synthetic_management_grid,
UserSettings.FileExtensions.GRID,
s => _vm.value.grid_settings_manager.create_vm(
// we need to get the most up to date GridSettings when launching b/c users may
// have changed some settings on the grid directly, like column widths.
s.with(_vm.value.columns.Select(c => c.create()))
s.with(_vm.value.columns.Select( c => c.create())),
this.observe_title()
)
);
vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => vm.title = t));
var dialog = new ShellSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
// TODO: bind to main window's Title
var dialog = new SimpleGridSettingsDialog { Title = "Synthetic Management Grid Settings", DataContext = vm };
vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
return dialog;
}))
@@ -113,6 +101,16 @@ namespace mbts.spark {
else _view.DataContext = _vm.value;
}
public override string window_type => _window_type;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = _vm.value.create();
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
public override void Dispose() {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
@@ -34,8 +34,6 @@ namespace mbts.spark {
public virtual Type data_type { get; } = typeof(T);
public virtual IRowModel row { get => _model; }
public T value {
get => _model.get_value(_leg_tup);
set {
@@ -66,8 +64,6 @@ namespace mbts.spark {
public Type data_type { get; } = typeof(QuantityMeasure);
public virtual IRowModel row { get => _model; }
public double ratio {
get => _get_qm().Ratio;
set {
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,10 +1,12 @@
using bts.shell;
using mbts.ui.spark;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
using PriceType = bts.spark.ConditionalQuoting.Types.PriceType;
using mbts.collections.extensions;
using System.Collections.Generic;
using bts.shell;
namespace mbts.spark {
namespace mbts.spark
{
partial class SyntheticEditorVM
{
class ConditionalQuotingPriceTypeModel : RowModelBase<PriceType>
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,4 +1,5 @@
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
using System.Windows;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,44 +0,0 @@
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
using System.Windows;
namespace mbts.spark
{
partial class SyntheticEditorVM
{
class QuoteQueueHolderModel : RowModelBase<uint>, IHaveFontParams
{
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public QuoteQueueHolderModel()
{
}
public override string display_name { get; } = "Queue Holder Length";
public override string help_text { get; } = "How many levels of queue to hold";
public override string man_page { get; } = string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
public FontWeight font_weight => FontWeights.SemiBold;
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup).NQueueHolderOrders;
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value)
{
_value_store[leg_tup.SecurityKey] = value;
leg_tup.leg_params.QuoteParams.NQueueHolderOrders = value ;
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) != null;
QuoteParams _get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => 0u);
}
}
}
@@ -1,36 +0,0 @@
using bts.shell;
using mbts.ui.spark;
namespace mbts.spark {
partial class SyntheticEditorVM {
class ShowQuantityEnabledModel : RowModelBase<bool> {
readonly ShowQuantityQuantityModel _quantity;
public ShowQuantityEnabledModel(ShowQuantityQuantityModel quantity) {
_quantity = quantity;
}
public override string display_name => string.Empty;
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.ShowQuantity != null;
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
if (value) {
if (leg_tup.leg_params.QuoteParams.ShowQuantity == null) {
leg_tup.leg_params.QuoteParams.ShowQuantity = SyntheticDataHelper.Defaults.ShowQuantity;
_quantity.apply_value(leg_tup);
}
} else {
leg_tup.leg_params.QuoteParams.ShowQuantity = null;
}
_quantity.set_enabled(leg_tup, value);
}
}
}
}
@@ -1,43 +0,0 @@
using bts.shell;
using bts.spark;
using mbts.ui.spark;
using System.Collections.Generic;
namespace mbts.spark {
partial class SyntheticEditorVM {
internal class ShowQuantityQuantityModel : RowModelBase<uint> {
IDictionary<uint, uint> _value_store = new Dictionary<uint, uint>();
public override string display_name => "Quantity";
public override string help_text => string.Empty;
public override string man_page => string.Empty;
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote, Subsections.ShowQuantity };
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup).Quantity = _value_from_store(leg_tup);
public override uint get_value(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) is NullableQuantity show_quantity ? show_quantity.Quantity : _value_from_store(leg_tup);
public override void set_value(LegSpreaderParamsTuple leg_tup, uint value) {
_value_store[leg_tup.SecurityKey] = value;
if (_get_value(leg_tup) is NullableQuantity show_quantity) {
show_quantity.Quantity = value;
}
}
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
_get_value(leg_tup) != null;
NullableQuantity _get_value(LegSpreaderParamsTuple leg_tup) =>
leg_tup.leg_params.QuoteParams.ShowQuantity;
uint _value_from_store(LegSpreaderParamsTuple leg_tup) =>
_value_store.GetValueOrDefault(leg_tup.SecurityKey, _ => SyntheticDataHelper.Defaults.ShowQuantity.Quantity);
}
}
}
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -1,5 +1,6 @@
using bts.shell;
using bts.spark;
using mbts.collections.extensions;
using mbts.ui.spark;
using System.Collections.Generic;
@@ -32,7 +32,6 @@ namespace mbts.spark {
internal interface ICell {
event Action value_changed;
IRowModel row { get; }
Type data_type { get; }
bool enabled { get; }
}
@@ -41,7 +40,7 @@ namespace mbts.spark {
FontWeight font_weight { get; }
}
internal abstract class RowModelBase<T> : ViewModelBase, IRowModel {
abstract class RowModelBase<T> : ViewModelBase, IRowModel {
readonly Dictionary<uint, ReplaySubject<bool>> _enabled_store = new Dictionary<uint, ReplaySubject<bool>>();
readonly Dictionary<uint, Subject<T>> _override_value_store = new Dictionary<uint, Subject<T>>();
@@ -99,10 +98,6 @@ namespace mbts.spark {
}
private static class Subsections {
public static ParameterSection ShowQuantity =>
new ParameterSection("Show Quantity", true,
"The quantity shown when the order is placed.");
public static ParameterSection Throttle =>
new ParameterSection("Throttle", true,
"Reduces re-quoting updates by defining a region on either side " +
@@ -1,5 +1,4 @@
using bts.api;
using bts.collections;
using bts.shell;
using bts.spark;
using bts.utils;
@@ -17,7 +16,6 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
namespace mbts.spark {
/// <summary>
@@ -33,7 +31,6 @@ namespace mbts.spark {
bool _set_name_automatically, _set_ticks_automatically, _set_quantities_automatically, _set_price_mults_automatically, _is_setting_quantities, _is_setting_price_mults;
TickFactors _tick_factors;
ExecutionStrategyImmutable _strategy;
HedgePricing _hedge_pricing;
/// <summary>
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
@@ -75,11 +72,11 @@ namespace mbts.spark {
_has_working_orders = backing(nameof(has_working_orders), false);
contract_picker_model = new ContractPickerModel(bus, false, null, new SecurityCriteria(new[] {
new SecurityCriteriaClause() {
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
exchanges = EnumHelper.get_values_except(Venue.Bts).ToLArray()
}
}));
contract_picker_model.cancel_action += _close_new_leg_popup;
contract_picker_model.ok_action += () => {
contract_picker_model.cancel_action = _close_new_leg_popup;
contract_picker_model.ok_action = () => {
if (!leg_tuples.Any(l => l.leg.Security.Id == contract_picker_model.result.security.Id)) {
leg_tuples.Add(new LegSpreaderParamsTuple(
SyntheticDataHelper.Defaults.ContractLeg(contract_picker_model.result.security),
@@ -136,7 +133,7 @@ namespace mbts.spark {
_synthetic.ContractSpec.PriceBreak = double.MaxValue;
_synthetic.ContractSpec.MainFraction = (uint)MathUtils.factor_out(_tick_factors.tick_base, 10);
_synthetic.ContractSpec.PriceDisplayFormat = (uint)_tick_factors.display_digits();
uint? id = await model.create_synthetic(new SecurityImmutable(_synthetic));
uint? id = await model.create_synthetic(_synthetic);
if (id.HasValue && id != 0) {
if (await model.save_strategy(id.Value, get_strategy())) {
var (smd, success) = await SparkUtils.get_security_md_async(bus, id.Value);
@@ -179,8 +176,6 @@ namespace mbts.spark {
} else {
_can_add_leg = backing(nameof(can_add_leg), false);
}
var show_quantity_quantity = new ShowQuantityQuantityModel();
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
var quote_throttle_inside_ticks = new QuoteThrottleInsideTicksModel();
@@ -204,19 +199,15 @@ namespace mbts.spark {
var post_hedge_opposite_market_based_cross_threshold = new PostHedgeOppositeMarketVolumeBasedCrossThresholdModel();
var post_hedge_opposite_market_based_cross_cross_ticks = new PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel();
var is_quote_model = new IsQuoteModel();
rows = new IRowModel[] {
_quantity_model,
_price_mult_model,
is_quote_model,
new IsQuoteModel(),
//new EnableHedgeModel(), //TAHA_SPREADER
new HedgeRatioModel(),
//new MinLeanQtyModel(), //TAHA_SPREADER
//new QuoteHiddenModel(),
new QuoteQueueHolderModel(),
new MarketDataTypeModel(),
new ShowQuantityEnabledModel(show_quantity_quantity),
show_quantity_quantity,
new QuoteThrottleEnabledModel(quote_throttle_inside_ticks, quote_throttle_outside_ticks),
quote_throttle_inside_ticks,
quote_throttle_outside_ticks,
@@ -296,9 +287,6 @@ namespace mbts.spark {
public IRaiseCanExecuteChangedCommand commit { get; }
public ICommand remove_leg { get; }
public ContractPickerModel contract_picker_model { get; }
// hedge pricing
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
public ExecutionStrategyImmutable get_strategy() {
var mutable_strategy = _strategy.CloneToMutable();
@@ -306,7 +294,6 @@ namespace mbts.spark {
foreach (var lt in leg_tuples) {
mutable_strategy.LegParamsMap.Add(lt.leg.Security.Id, lt.leg_params);
}
mutable_strategy.HedgePricing = hedge_pricing;
return new ExecutionStrategyImmutable(mutable_strategy);
}
@@ -314,7 +301,6 @@ namespace mbts.spark {
_synthetic = synthetic.CloneToMutable();
_synthetic.Exchange = Venue.Bts;
_strategy = strategy;
hedge_pricing = strategy.HedgePricing;
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
RaisePropertyChanged(nameof(name), nameof(tick_factors));
}
@@ -332,12 +318,9 @@ namespace mbts.spark {
return string.Empty;
}
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) {
foreach (var l in synthetic.Synthetic.Legs) {
if (strategy.LegParamsMap.TryGetValue(l.Security.Id, out var p))
yield return new LegSpreaderParamsTuple(l, p);
}
}
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
synthetic.Synthetic.Legs
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
void _close_new_leg_popup() {
is_new_leg_popup_open = false;
@@ -1,9 +1,10 @@
using bts.api;
using bts.collections;
using bts.shell;
using bts.spark;
using mbts.persist;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.settings;
using System;
using System.Collections.Generic;
@@ -168,13 +169,12 @@ namespace mbts.spark {
void _show_ladder() =>
_bus.post(SparkTags.ShowLadderTag, md);
void _show_order_book() {
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
ob_settings.filters = new bts.shell.orderbook.FilterStates {
security_criteria = new SecurityCriteria(security)
};
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
}
void _show_order_book() =>
_bus.post(SparkTags.ShowOrderBookTag, new OrderBookWindowFactory.OrderBookStartupData {
filters = new OrderBookWindowFactory.FilterStates {
security_criteria = new SecurityCriteria(security)
}
});
}
}
}
@@ -1,9 +1,14 @@
using mbts;
using bts.api;
using mbts.config;
using mbts.spark;
using mbts.ui;
using mbts.ui.shell;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using System.Windows.Controls;
using mbts;
namespace bts.shell {
public class BrowserWindow : IShellWindow {
@@ -70,15 +75,12 @@ namespace bts.shell {
public string window_type => _window_type;
public IShellModule Owner {get; set; }
public ShellWindowUI GetContent() { return new ShellWindowUI(_browser); }
public void GetState(System.IO.Stream save_here) {
_isw.save(new BrowserWindowState() { uri = uri.ToString() }, save_here);
}
public void on_shell_window_vm(ShellWindowVM shell_window_vm) { }
public void Dispose() {
if (_browser != null) {
_browser.Dispose();
@@ -100,4 +102,5 @@ namespace bts.shell {
}
#endregion
}
}
}
@@ -0,0 +1,112 @@
using bts.api.core;
using bts.collections;
using mbts.collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
namespace mbts.spark {
/// <summary>
/// This class's primary purpose is to handle async AlgoService persistence calls for the AlgoStudioVM/OrderPropertiesEditorVM.
/// When a new definition/template item is persisted for the first time, it's corresponding viewmodel (<see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>)
/// must be removed and replaced with a new one that has the brand new persisted id (<see cref="AlgoKey"/>). As part of its duty, this class makes sure that the new persisted items are inserted into the same index as their unpersisted predecessor.
/// </summary>
static class AlgoPersistenceHelper {
public static (int? def_index, int? tmp_index) selection_snapshot(IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM selected_definition, AlgoDefinitionTemplateVM selected_template) {
var def = selected_definition;
var tmp = selected_template;
int? def_index = def == null ? (int?)null : definitions.IndexOf(def);
int? tmp_index = tmp == null ? (int?)null : tmp.owner.templates.IndexOf(tmp);
return (def_index, tmp_index);
}
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_definition(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionVM definition) {
int def_index = definitions.IndexOf(definition);
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, definition.templates.AsList()), t => t.def));
}
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_template(OrdersModel model, IList<AlgoDefinitionVM> definitions, AlgoDefinitionTemplateVM template) {
switch (template.owner.key.mode) {
case AlgoKey.Mode.Unpersisted: return await persist_definition(model, definitions, template.owner);
case AlgoKey.Mode.Persisted:
int def_index = definitions.IndexOf(template.owner);
int tmp_index = template.owner.templates.IndexOf(template);
return await persist(model, definitions.WrapSubRange(def_index, 1).WrapSelector(def => (def, def.templates.WrapSubRange(tmp_index, 1)), t => t.def));
default: return DictionaryHelper.empty<AlgoKey, AlgoKey>();
}
}
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist_all(OrdersModel model, ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> definitions) =>
await persist(model, definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
/// <summary>
/// Persists each pair of definition/templates, then returns a map of non-persisted keys to persisted ones (<see cref="AlgoKey"/>, <see cref="AlgoDefinitionVM.key"/>, <see cref="AlgoDefinitionTemplateVM.key"/>).
/// The key-map gives callers the ability to replace unpersisted items with persisted ones before the next update arrives.
/// <para>Note: use a <see cref="mbts.utils.TableUpdateBuffer{T, TKey}"/> to suspend updates while awaiting this task; don't resume them until all unpersisted items have been replaced.
/// <see cref="AlgoDefinitionCollection"/> caters to this pre-emptive adding behavior, in it's <see cref="TableUpdate{T, TKey}"/> callback, it checks if added items are already present, and if so, treats them as changes instead.</para>
/// <para>PS: the reason for all this madness is so newly persisted items are inserted in the same index as their placeholder counterpart. The only way to connect the non-persisted keys with their persisted counterparts
/// is to await each save-op individually, and build a map with the results..hence the returned key-map.</para>
/// </summary>
/// <param name="model"></param>
/// <param name="definition_templates"></param>
/// <returns>An AlgoKey->AlgoKey map of old placeholder ids to newly persisted ones.</returns>
public static async Task<IReadOnlyDictionary<AlgoKey, AlgoKey>> persist(OrdersModel model, IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
var placeholder_to_persisted_keys = new Dictionary<AlgoKey, AlgoKey>();
for (int i = 0; i < definition_templates.Count; i++) {
(var def, var templates) = definition_templates[i];
var persisted_def = def;
if (def.is_dirty) {
var def_record = await model.algo_model.save_definition(def.create_algo_def());
if (def_record.HasValue) {
switch (def.key.mode) {
case AlgoKey.Mode.Factory:
case AlgoKey.Mode.Unpersisted:
persisted_def = def.to_persisted(def_record.Value);
foreach (var tmp in templates) {
AlgoDefinitionTemplateVM persisted_tmp;
var tmp_record = await model.algo_model.save_template(tmp.create(def_record.Value));
if (tmp_record.HasValue) {
persisted_def.templates.Add(persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key)));
} else {
/// If the save-op fails, the unpersisted template is cloned and added to the persisted def's template items, so the user may try to save it again without having to re-enter all the templated-values.
/// Q: "why don't we just re-add the same template to the persisted def, the save-op failed, so nothing changed...why do we need a new one?"
/// A: "A new template vm must be created because its owner has changed (it's a readonly prop, <see cref="AlgoDefinitionTemplateVM.owner"/>)."
persisted_def.templates.Add(persisted_tmp = new AlgoDefinitionTemplateVM(persisted_def, model, tmp.create_def_data()));
}
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
}
persisted_def.connect();
if (def.key.mode == AlgoKey.Mode.Unpersisted) {
definition_templates[i] = (persisted_def, persisted_def.templates);
} else {
definition_templates.Add((persisted_def, persisted_def.templates));
}
placeholder_to_persisted_keys[def.key] = persisted_def.key;
break;
}
}
} else {
// so the def isn't dirty, but it still might have dirty templates
using (persisted_def.buffer_updates()) {
for (int j = 0; j < templates.Count; j++) {
var tmp = templates[j];
if (!tmp.is_dirty)
continue;
var tmp_record = await model.algo_model.save_template(tmp.create());
if (tmp_record.HasValue) {
switch (tmp.key.mode) {
case AlgoKey.Mode.Unpersisted:
var persisted_tmp = tmp.to_persisted(persisted_def, new AlgoData(tmp_record.Value.value, tmp_record.Value.name, tmp_record.Value.key));
templates[j] = persisted_tmp;
placeholder_to_persisted_keys[tmp.key] = persisted_tmp.key;
break;
}
}
}
}
}
}
return placeholder_to_persisted_keys;
}
}
}
@@ -12,11 +12,16 @@
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
u:StateManager.VisualState="{Binding selected_definition.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
u:StateManager.VisualState="{Binding content_source.algo_root, Converter={x:Static usp:AlgoStudioView.EditPaneVisualStateConverter}}"
Background="{StaticResource CommonBackground}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
@@ -30,6 +35,9 @@
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
<!--Converters-->
<cv:ValueMap x:Key="AlgoTemplateFavoriteToggleButtonToolTipConverter" Default="Add to Favorites">
<cv:KeyValue Key="{u:True}" Value="Remove from Favorites"/>
</cv:ValueMap>
<sys:String x:Key="AlgoAbbreviationActiveState">AbbreviationActive</sys:String>
<sys:String x:Key="AlgoAbbreviationInactiveState">AbbreviationInactive</sys:String>
<cv:ValueMap x:Key="AlgoAbbreviationVisualStateConverter" Default="{StaticResource AlgoAbbreviationActiveState}">
@@ -124,8 +132,70 @@
</Setter>
</Style>
<Style x:Key="AlgoTemplateFavoriteToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="IsThreeState" Value="False"/>
<Setter Property="IsChecked" Value="{Binding is_favorite}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="ToolTip" Value="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static uag:AlgoTemplateToolBar.FavoriteToggleButtonToolTipConverter}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
<Ellipse x:Name="ellipse" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}" Width="0" Height="0"/>
<Image SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}" MaxHeight="13" MaxWidth="13">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Geometry="{Binding Source={StaticResource ResourceKey={x:Static u:Icons.StarGeometryKey}}}" Brush="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" >
<GeometryDrawing.Pen>
<Pen
Brush="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"
Thickness="{Binding BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Foreground" Value="Gold"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="16" Duration="0:0:0.03" />
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="16" Duration="0:0:0.03" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Width" To="0" Duration="0:0:0.0" />
<DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.0" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
<Setter Property="DropDownStyleKey" Value="{x:Static usp:AlgoContentTemplateSelector.DefinitionDropDownStyleKey}"/>
<Setter Property="DropDownStyleKey" Value="{Binding DataContext.content_source, RelativeSource={RelativeSource AncestorType=usp:AlgoStudioView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
@@ -153,26 +223,94 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox
<TreeView
x:Name="_tree_view"
u:TreeViewEx.SelectedItemChangedHandler="{Binding}"
ItemsSource="{Binding definitions_view}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinWidth="180"
Background="Transparent"
SelectedItem="{Binding selected_definition}"
Style="{StaticResource AlgoTemplateTreeViewStyle}"
>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type sp:AlgoDefinitionVM}">
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
<DataTemplate.Triggers>
<TreeView.CommandBindings>
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.AddNewItemCommand}" Executed="{x:Static u:EditableCollectionViewEx.AddNewItemExecuted}" CanExecute="{x:Static u:EditableCollectionViewEx.AddNewItemCanExecute}"/>
<CommandBinding Command="{x:Static u:EditableCollectionViewEx.CommitAnyCommand}" Executed="{x:Static u:EditableCollectionViewEx.CommitAnyExecuted}"/>
</TreeView.CommandBindings>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type sp:AlgoDefinitionVM}" ItemsSource="{Binding templates_view}">
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.TreeViewBadge}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="badge" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle
x:Name="badge"
Fill="{Binding badge_fill}"
Style="{StaticResource RectangleBadgeStyle}"
Visibility="{DynamicResource ResourceKey={x:Static usp:AlgoStudioView.BadgesInTreeViewVisibilityKey}}"
/>
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}"/>
</Grid>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding is_dirty}" Value="True">
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sp:AlgoDefinitionTemplateVM}">
<Grid u:StateManager.VisualStateCollection="{Binding tree_view_visual_states}" Margin="-6,0,0,0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}"/>
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoStudioView.VisualStateNames.FavoriteToggleButton}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="favorite_toggle" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ToggleButton x:Name="favorite_toggle" Grid.Column="1" Margin="4,0,0,0" Style="{StaticResource AlgoTemplateFavoriteToggleButtonStyle}"/>
</Grid>
<TextBlock x:Name="text_view" Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding name}" ToolTip="Double click to set as default"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Value="{u:True}">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource OrConverter}">
<Binding Path="is_dirty"/>
<Binding Path="is_persisted" Converter="{StaticResource NotConverter}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="text_view" Property="Text" Value="{Binding name, StringFormat='{}{0}*'}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</TreeView.Resources>
</TreeView>
<ScrollViewer Grid.Column="1" Grid.IsSharedSizeScope="True" Margin="8,4">
<Grid x:Name="edit_pane">
<Grid.RowDefinitions>
@@ -294,7 +432,7 @@
Margin="{StaticResource Margin.LP1}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding selected_definition.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
Text="{Binding content_source.name, UpdateSourceTrigger=PropertyChanged, Delay=25}"
/>
<Grid x:Name="abbreviation_root" Grid.Column="2">
<Grid.ToolTip>
@@ -316,7 +454,7 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="abbreviation_label" FontStyle="Italic" Margin="{StaticResource Margin.LP1}" VerticalAlignment="Center">Abbrev.</TextBlock>
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding selected_definition.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
<TextBox Grid.Column="1" Margin="{StaticResource Margin.LP1}" Text="{Binding content_source.abbreviation, UpdateSourceTrigger=PropertyChanged, Delay=25}"/>
</Grid>
</Grid>
<Grid x:Name="badge_selector_radio_button_root" Grid.Row="1" Margin="{StaticResource Margin.TP2}">
@@ -342,8 +480,8 @@
<TextBlock>Hint: the default color is that of the algo-root (</TextBlock>
<usp:OrderTypeItem
Margin="2,0"
BadgeFill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
DisplayName="{Binding selected_definition.algo_root.display_name}"
BadgeFill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
DisplayName="{Binding content_source.algo_root.display_name}"
/>
<TextBlock>).</TextBlock>
</StackPanel>
@@ -360,7 +498,7 @@
x:Name="default_badge"
Grid.Column="1"
Margin="{StaticResource Margin.LP1}"
Fill="{Binding selected_definition.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Fill="{Binding content_source.algo_root.meta.Color, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
</Grid>
@@ -370,7 +508,7 @@
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="{StaticResource Margin.LP2}"
IsChecked="{Binding selected_definition.custom_badge_color.enabled}"
IsChecked="{Binding content_source.custom_badge_color.enabled}"
GroupName="Badge"
>
<RadioButton.ToolTip>
@@ -390,7 +528,7 @@
Grid.Row="1"
Grid.Column="1"
Margin="3,0"
Fill="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Fill="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Style="{StaticResource RectangleBadgeStyle}"
/>
<TextBlock Grid.Row="1" Grid.Column="2" FontStyle="Italic">to open the color picker popup.</TextBlock>
@@ -405,7 +543,7 @@
<Button
x:Name="custom_badge_color_picker_button"
Grid.Column="1"
Background="{Binding selected_definition.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Background="{Binding content_source.custom_badge_color.value, Converter={StaticResource ColorToBrushConverter}}"
Margin="{StaticResource Margin.LP1}"
Style="{StaticResource CustomBadgeColorPickerButtonStyle}"
/>
@@ -413,33 +551,91 @@
<Border Style="{StaticResource CustomBadgeColorPickerPopupRootStyle}">
<cp:ColorPickerControl
Background="Transparent"
Palettes="{Binding DataContext.selected_definition.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
SelectedColor="{Binding DataContext.selected_definition.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
Palettes="{Binding DataContext.content_source.custom_badge_color_palettes, RelativeSource={RelativeSource AncestorType=Popup}}"
SelectedColor="{Binding DataContext.content_source.custom_badge_color.value, RelativeSource={RelativeSource AncestorType=Popup}, Mode=TwoWay}"
/>
</Border>
</Popup>
</Grid>
</RadioButton>
</Grid>
<usp:AlgoEditorView
<GroupBox
x:Name="content_host"
Grid.Row="2"
DataContext="{Binding selected_definition}"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
/>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Orientation="Horizontal">
<StackPanel.Margin>
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource VerticalPush2}" Right="{StaticResource DialogSkirtButtonPadding.Right}" Bottom="{StaticResource DialogSkirtPadding.Bottom}"/>
</StackPanel.Margin>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding save_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding revert_cmd}" CommandParameter="{Binding selected_definition}" Width="100" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</StackPanel>
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
Grid.Column="3"
ToolTipService.ShowOnDisabled="True"
SelectedItem="{Binding selected_definition.algo_root}"
ItemsSource="{Binding selected_definition.algos_view}"
Margin="{StaticResource Margin.LP1}"
Width="140"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding selected_definition.algo_root}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
<c:DialogSkirt.RightItems>
<x:ArrayExtension Type="{x:Type Button}">
<Button ToolTipService.ShowOnDisabled="True" Command="{Binding save_cmd}" CommandParameter="{Binding content_source}" Width="100" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}" ToolTip="{Binding save_cmd_tooltip}" IsDefault="True">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Save {0}'}"/>
</Button>
<Button ToolTipService.ShowOnDisabled="True" Command="{Binding revert_cmd}" CommandParameter="{Binding content_source}" Width="100" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}" ToolTip="{Binding revert_cmd_tooltip}">
<TextBlock Text="{Binding selected_definition.name, StringFormat='{}Revert {0}'}"/>
</Button>
</x:ArrayExtension>
</c:DialogSkirt.RightItems>
</c:DialogSkirt>
</Grid>
</ScrollViewer>
</Grid>
@@ -1,24 +1,60 @@
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.extensions;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoStudioView : UserControl {
public const string BadgesInTreeViewVisibilityKey = nameof(BadgesInTreeViewVisibilityKey);
public static readonly IValueConverter EditPaneVisualStateConverter =
ValueConverter.create(obj => obj == null ? VisualStates.Inactive : VisualStates.Active);
IAlgoStudioViewController _controller;
public AlgoStudioView() {
InitializeComponent();
PopupViewController.attach(custom_badge_color_picker_popup, custom_badge_color_picker_button);
this.ConnectDataContext<AlgoStudioVM>(vm => vm.request_view_focus_name += focus_name, vm => vm.request_view_focus_name -= focus_name);
void focus_name(AlgoDefinitionVM target) =>
Dispatcher.InvokeAsync(() => Keyboard.Focus(name_text_box));
DataContextChanged += (s, e) => set_controller();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
set_controller();
// local methods
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
public new AlgoStudioVM DataContext {
get => base.DataContext as AlgoStudioVM;
set => base.DataContext = value;
}
public void add_definition() => _controller.add_definition();
public void add_definition_clone(AlgoDefinitionVM src_definition) => _controller.add_definition_clone(src_definition);
public void add_template(AlgoDefinitionVM definition) => _controller.add_template(definition);
public void rename_definition(AlgoDefinitionVM definition) => _controller.rename_definition(definition);
public void rename_template(AlgoDefinitionTemplateVM template) => _controller.rename_template(template);
public void select_definition(AlgoDefinitionVM definition) => _controller.select_definition(definition);
public void select_template(AlgoDefinitionTemplateVM template) => _controller.select_template(template);
public IDisposable preserve_selected_definition() => _controller.preserve_selected_definition();
void _end_edit_executed(object s, ExecutedRoutedEventArgs e) =>
_controller.end_edit(e);
public static class VisualStateNames {
public static class EditPane {
public const string GroupName = nameof(EditPane);
@@ -56,6 +92,14 @@ namespace mbts.ui.spark {
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class TreeViewBadge {
public const string GroupName = nameof(TreeViewBadge);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
public static class FavoriteToggleButton {
public const string GroupName = nameof(FavoriteToggleButton);
@@ -0,0 +1,133 @@
using mbts.spark;
using mbts.ui.controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.spark {
interface IAlgoStudioViewController : IDisposable {
void add_definition();
void add_definition_clone(AlgoDefinitionVM src_definition);
void add_template(AlgoDefinitionVM definition);
void rename_definition(AlgoDefinitionVM definition);
void rename_template(AlgoDefinitionTemplateVM template);
void end_edit(ExecutedRoutedEventArgs e);
void select_definition(AlgoDefinitionVM definition);
void select_template(AlgoDefinitionTemplateVM template);
IDisposable preserve_selected_definition();
}
class AlgoStudioViewControllerFactory {
static readonly IAlgoStudioViewController _empty = new EmptyController();
public static IAlgoStudioViewController create(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) =>
vm == null || tree_view == null || name_text_box == null || color_picker_button == null || color_picker_popup == null ?
_empty : new Controller(vm, tree_view, name_text_box, color_picker_button, color_picker_popup);
/// <summary>
/// This wrapper class is returned by <see cref="create(AlgoStudioVM, TreeView)"/> when the vm or tree_view is null.
/// This further separates the view from the controller-logic; it doesn't even need to check for null when accessing <see cref="IAlgoStudioViewController"/> methods.
/// </summary>
class EmptyController : IAlgoStudioViewController {
public void add_definition() { }
public void add_definition_clone(AlgoDefinitionVM src_definition) { }
public void add_template(AlgoDefinitionVM definition) { }
public void rename_definition(AlgoDefinitionVM definition) { }
public void rename_template(AlgoDefinitionTemplateVM template) { }
public void end_edit(ExecutedRoutedEventArgs e) { }
public void select_definition(AlgoDefinitionVM definition) { }
public void select_template(AlgoDefinitionTemplateVM template) { }
public IDisposable preserve_selected_definition() => Disposable.empty;
public void Dispose() { }
}
class Controller : IAlgoStudioViewController {
readonly AlgoStudioVM _vm;
readonly TreeView _tree_view;
readonly TextBox _name_text_box;
IDisposable _dispose;
public Controller(AlgoStudioVM vm, TreeView tree_view, TextBox name_text_box, Button color_picker_button, Popup color_picker_popup) {
_vm = vm;
_tree_view = tree_view;
_name_text_box = name_text_box;
_dispose = PopupViewController.attach(color_picker_popup, color_picker_button);
}
public void add_definition() {
_vm.add_new_definition();
_begin_edit();
}
public void add_definition_clone(AlgoDefinitionVM src_definition) {
_vm.add_new_definition_clone(src_definition);
_begin_edit();
}
public void add_template(AlgoDefinitionVM def) {
var new_tmp = def.template_factory();
def.templates.Add(new_tmp);
_begin_edit();
}
public void end_edit(ExecutedRoutedEventArgs e) {
if (e.OriginalSource is EditableTextBlock etb) {
etb.end_edit();
_get_container(etb.DataContext)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
}
}
public void rename_definition(AlgoDefinitionVM definition) {
if (_tree_view.ItemContainerGenerator.ContainerFromItem(definition) is TreeViewItem tvi) {
var etb = tvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
}
}
public void rename_template(AlgoDefinitionTemplateVM template) {
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem tvi) {
if (tvi.ItemContainerGenerator.ContainerFromItem(template) is TreeViewItem ttvi) {
var etb = ttvi.descendants_of_type<EditableTextBlock>().FirstOrDefault();
etb.SetCurrentValue(EditableTextBlock.IsEditingProperty, true);
}
}
}
public void select_definition(AlgoDefinitionVM definition) =>
_tree_view.ItemContainerGenerator.ContainerFromItem(definition)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
public void select_template(AlgoDefinitionTemplateVM template) {
if (_tree_view.ItemContainerGenerator.ContainerFromItem(template.owner) is TreeViewItem def_view) {
def_view.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
def_view.ItemContainerGenerator.ContainerFromItem(template)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
}
}
public IDisposable preserve_selected_definition() =>
Disposable.Create(() => {
var name = _vm.selected_definition.name;
if (_vm.definitions.TryGetFirst(d => d.name == name, out AlgoDefinitionVM def))
_tree_view.ItemContainerGenerator.ContainerFromItem(def)?.SetCurrentValue(TreeViewItem.IsSelectedProperty, true);
});
public void Dispose() =>
Disposable.dispose(ref _dispose);
void _begin_edit() =>
WPFHelper.push_frame_until(() => Keyboard.Focus(_name_text_box) == _name_text_box);
TreeViewItem _get_container(object obj) =>
obj is AlgoDefinitionTemplateVM t ? _get_container(t) :
obj is AlgoDefinitionVM d ? _get_container(d) : null;
TreeViewItem _get_container(AlgoDefinitionVM d) =>
_tree_view.ItemContainerGenerator.ContainerFromItem(d) as TreeViewItem;
TreeViewItem _get_container(AlgoDefinitionTemplateVM t) =>
_get_container(t.owner) is TreeViewItem dview ? dview.ItemContainerGenerator.ContainerFromItem(t) as TreeViewItem : null;
}
}
}
@@ -13,7 +13,7 @@ using System.Windows;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
namespace mbts.spark {
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
public const string
NAME = "Algo Studio",
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
@@ -37,7 +37,7 @@ namespace mbts.spark {
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
_model = model;
_state = state;
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
thread_init();
}
public override StartupData SaveState => _state = _vm.create();
@@ -45,12 +45,12 @@ namespace mbts.spark {
public override string window_type => NAME;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public string thread_id => NAME;
public override double MinWidth =>
SPARK_MENU_COLLAPSED_WIDTH +
LAUNCHER_BUTTON_BORDER_WIDTH +
DEFAULT_CONTENT_WIDTH;
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
public string thread_id { get; }
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
@@ -62,19 +62,35 @@ namespace mbts.spark {
_vm = new AlgoStudioVM(_model);
_vm.load_from(_state);
_view = new AlgoStudioView { DataContext = _vm };
_view.Resources[AlgoStudioView.BadgesInTreeViewVisibilityKey] = _ui_configuration.show_badges_in_tree_view ? Visibility.Visible : Visibility.Collapsed;
_resources = new CompositeDisposable();
// observable sources
var content_src = _vm.observe_selected().Select(_ => _vm.content_source);
// add definition
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, _vm.add_definition_cmd));
// clone definition
var clone_selected_definition_spark_cmd = new SparkCommandCommand(string.Empty, Icons.DocumentCopy, _vm.clone_selected_definition_cmd);
_commands.Add(clone_selected_definition_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(
def => clone_selected_definition_spark_cmd.title = def == null ? $"Clone (None Selected)" : $"Clone '{def.name}'"));
_commands.Add(new SparkCommandCommand("Add Algo", Icons.AddBoxes, new DelegateCommand(_view.add_definition)));
// add definition clone
var add_definition_clone_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.DocumentCopy, d => $"Clone '{d?.name}'", d => d != null, _view.add_definition_clone);
_commands.Add(add_definition_clone_spark_cmd);
// add template
var add_template_spark_cmd = new ObservantSparkCommandCommand<AlgoDefinitionVM>(Icons.AddFile, d => $"Add '{d?.name}' Template", d => d != null, _view.add_template) {
show_in_toolbar = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar) == bts.config.AlgoStudioOptions.AddTemplateButton.MainToolBar,
show_in_context_menu = (_ui_configuration.add_template_button & bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu) == bts.config.AlgoStudioOptions.AddTemplateButton.DefinitionContextMenu
};
_commands.Add(add_template_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(add_definition_clone_spark_cmd, add_template_spark_cmd));
// save
if (_ui_configuration.can_save_specific) {
var content_is_dirty_src = _vm.subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false));
_commands.Add(new ReactiveSparkCommand(_vm.subscribe_definition().OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_definition is AlgoDefinitionVM d)
var content_is_dirty_src = _vm.observe_selected().SelectManyInSeries(t => {
if (t.template != null) {
return t.template.observe_is_dirty();
} else if (t.definition != null) {
return t.definition.subscribe_is_definition_dirty();
} else return Observable.Return(false);
}).RefCount();
_commands.Add(new ReactiveSparkCommand(content_src.OnDispatcher().Select(c => $"Save '{c?.name}'"), Icons.DiskFile, () => {
if (_vm.selected_template is AlgoDefinitionTemplateVM t)
_vm.save_template(t).ignore();
else if (_vm.selected_definition is AlgoDefinitionVM d)
_vm.save_definition(d).ignore();
}, content_is_dirty_src));
}
@@ -85,39 +101,50 @@ namespace mbts.spark {
// undo/redo
if (_ui_configuration.can_undo_redo) {
// undo
var undo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_undo());
var undo_cmd = new ObservantCommand(() => _vm.content_source?.try_undo());
var undo_spark_cmd = new SparkCommandCommand("Undo", Icons.UndoCircular, undo_cmd);
var can_undo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
def != null ?
var can_undo_source = _vm.observe_selected().SelectManyInSeries(tup => {
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
return content != null ?
Observable.Create<bool>(o => {
def.can_undo_changed += notify;
content.can_undo_changed += notify;
notify();
return Disposable.Create(() => def.can_undo_changed -= notify);
void notify() => o.OnNext(def.can_undo);
return Disposable.Create(() => content.can_undo_changed -= notify);
void notify() => o.OnNext(content.can_undo);
}) :
Observable.Return(false));
Observable.Return(false);
});
_commands.Add(undo_spark_cmd);
_resources.Add(can_undo_source.Subscribe(undo_cmd));
_resources.Add(can_undo_source.Connect());
// redo
var redo_cmd = new ObservantCommand(() => _vm.selected_definition?.try_redo());
var redo_cmd = new ObservantCommand(() => _vm.content_source?.try_redo());
var redo_spark_cmd = new SparkCommandCommand("Redo", Icons.RedoCircular, redo_cmd);
var can_redo_source = _vm.subscribe_definition().SelectManyInSeries(def =>
def != null ?
var can_redo_source = _vm.observe_selected().SelectManyInSeries(tup => {
IAlgoStudioContentVM content = (IAlgoStudioContentVM)tup.template ?? tup.definition;
return content != null ?
Observable.Create<bool>(o => {
def.can_redo_changed += notify;
content.can_redo_changed += notify;
notify();
return Disposable.Create(() => def.can_redo_changed -= notify);
void notify() => o.OnNext(def.can_redo);
return Disposable.Create(() => content.can_redo_changed -= notify);
void notify() => o.OnNext(content.can_redo);
}) :
Observable.Return(false));
Observable.Return(false);
});
_commands.Add(redo_spark_cmd);
_resources.Add(can_redo_source.Subscribe(redo_cmd));
_resources.Add(can_redo_source.Connect());
}
// remove
var remove_spark_cmd = new ObservantSparkCommandCommand<IAlgoStudioContentVM>(Icons.Trash, v => $"Remove '{v?.name}'", v => v != null, value => {
if (value is AlgoDefinitionVM d) {
if (value is AlgoDefinitionTemplateVM t) {
_vm.remove_template(t).ignore();
} else if (value is AlgoDefinitionVM d) {
string prompt; MessageBoxImage icon;
if (d.key.mode == AlgoKey.Mode.Persisted) {
if (_model.algo_model.templates_enabled() && d.templates.Count > 0) {
prompt = $"Deleting this definition ({d.name}) will also delete its templates ({d.templates.Count}).\n\nDo you wan to Continue?";
icon = MessageBoxImage.Warning;
} else if (d.key.mode == AlgoKey.Mode.Persisted) {
prompt = $"Are you sure you want to delete '{d.name}?'";
icon = MessageBoxImage.Question;
} else {
@@ -137,7 +164,7 @@ namespace mbts.spark {
}
});
_commands.Add(remove_spark_cmd);
_resources.Add(_vm.subscribe_definition().OnDispatcher().Subscribe(remove_spark_cmd));
_resources.Add(content_src.OnDispatcher().Subscribe(remove_spark_cmd));
}
void _dispose_core() {
@@ -1,59 +1,30 @@
using bts.api.orderman;
using bts.api.orderman.algo;
using bts.utils;
using mbts.spark.utilities;
using System;
using System.Windows.Media;
namespace mbts.spark {
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
struct AlgoData : IEquatable<AlgoData> {
public readonly CustomOrderDefinitionImmutable value;
public readonly string name;
public readonly AlgoKey key;
AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
public AlgoData(CustomOrderDefinitionImmutable value)
: this(value, value.display_name(), OrderDataHelper.get_shallow_factory_key(value)) { }
public AlgoData(CustomOrderDefinitionImmutable value, string name, AlgoKey key) {
this.value = value;
this.name = name;
this.key = key;
}
public static AlgoData empty => from_custom_def(new CustomOrderDefinitionImmutable(new CustomOrderDefinition()));
/// <summary>
/// This is the only <see cref="AlgoData"/> factory method that allows callers to explicitly specify a name.
/// It should only be used within the context of creating, or renaming, studio algos.
/// </summary>
public static AlgoData create(CustomOrderDefinitionImmutable def, string name, AlgoKey key) =>
new AlgoData(def, name, key);
/// <summary>
/// Reconstructs <see cref="AlgoData"/> from a <see cref="CustomOrderDefinitionImmutable"/>, with a special caveat in the case of <see cref="CompositeOrderDefinition"/>:
/// a fallback/default name is used instead of the composite algo's true name, which must be obtained from the <see cref="IAlgoStudioModel.definitions"/> stream.
/// </summary>
public static AlgoData from_custom_def(CustomOrderDefinitionImmutable def) {
switch (def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite:
string name = $"({OrderDataHelper.abbreviate_algo_type(def.CustomCase)}){OrderDataHelper.abbreviate_algo_type(def.Composite.Definition.CustomCase)}";
return new AlgoData(def.Composite.Definition, name, AlgoKey.from_composite_key(def.Composite.AlgoTemplateKey));
case CustomOrderDefinition.CustomOneofCase.Dynamic:
return new AlgoData(def,def.Dynamic.DynamicOrderType, AlgoKey.from_dynamic_type(def.Dynamic.DynamicOrderType));
default:
return new AlgoData(def, def.CustomCase.display_name(), AlgoKey.from_known_type(def.CustomCase));
}
}
public static AlgoData from_record(AlgoDefinitionRecordImmutable record) =>
new AlgoData(record.Algo.Definition, record.Algo.Name, AlgoKey.from_composite_key(record.Key));
public static AlgoData create_unpersisted(CustomOrderDefinitionImmutable custom_def, string name) =>
new AlgoData(custom_def, name, AlgoKey.create_unpersisted());
public AlgoData with(CustomOrderDefinitionImmutable? value = null, string name = null) =>
new AlgoData(value ?? this.value, name ?? this.name, key);
public bool Equals(AlgoData other) =>
key == other.key &&
name == other.name &&
OrderDataHelper.EqualsHelpers.custom_order_defi_comparer.Equals(value, other.value);
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.Equals(value, other.value);
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
@@ -65,33 +36,4 @@ namespace mbts.spark {
public static bool operator ==(AlgoData a, AlgoData b) => a.Equals(b);
public static bool operator !=(AlgoData a, AlgoData b) => !a.Equals(b);
}
struct AlgoDataVM : IEquatable<AlgoDataVM> {
public static readonly Func<AlgoDataVM, AlgoID> id_fn = vm => AlgoID.from_algo_key(vm.data.key);
public readonly AlgoData data;
public readonly AlgoDefinitionMetadata? meta;
public AlgoDataVM(AlgoData data, AlgoDefinitionMetadata? meta = null) {
this.data = data;
this.meta = meta;
}
public string name => data.name;
public Brush badge_fill => OrderUtils.badge_fill(meta, OrderDataHelper.get_metadata(data.value.CustomCase));
public AlgoDisplayGroup group => data.key.display_group();
public override string ToString() =>
data.name;
public static AlgoDataVM from_data(AlgoData data, AlgoDefinitionMetadata? meta) =>
new AlgoDataVM(data, meta);
public static AlgoDataVM from_data((AlgoData algo, AlgoDefinitionMetadata? meta) tup) =>
new AlgoDataVM(tup.algo, tup.meta);
public bool Equals(AlgoDataVM other) => data == other.data && meta == other.meta;
public override bool Equals(object obj) => obj is AlgoDataVM other && Equals(other);
public override int GetHashCode() => EqualityComparer.aggregate_hash(data.GetHashCode(), meta.GetHashCode());
}
}
@@ -1,177 +1,57 @@
using bts.api.orderman;
using bts.persist.algo;
using bts.utils;
using mbts.utils;
using ProtoBuf;
using System;
using System.Linq;
namespace mbts.spark {
// This is a core data type on the front end, so data validation is strict, as bad values may yield weird/difficult to track bugs.
// Descriptive factory methods are favored over public constructors, as they are less likely to be misused.
[ProtoContract]
struct AlgoID : IEquatable<AlgoID> {
[ProtoMember(1)]
readonly CustomOrderDefinition.CustomOneofCase? _factory_key;
[ProtoMember(2)]
readonly string _dynamic_key;
[ProtoMember(3)]
readonly uint? _persisted_key;
public static readonly AlgoID[] exchange_algo_keys = utilities.OrderDataHelper.outright_order_types
.Concat(new CustomOrderDefinition.CustomOneofCase[] { CustomOrderDefinition.CustomOneofCase.Spreader })
.Select(cooc => new AlgoID(cooc))
.ToArray();
public AlgoID(CustomOrderDefinition.CustomOneofCase factory_key) {
_factory_key = factory_key;
_dynamic_key = null;
_persisted_key = null;
}
public AlgoID(string dynamic_key) {
_factory_key = null;
_dynamic_key = dynamic_key;
_persisted_key = null;
}
public AlgoID(uint persisted_key) {
_factory_key = null;
_dynamic_key = null;
_persisted_key = persisted_key;
}
public static AlgoID from_custom_def(CustomOrderDefinitionImmutable custom_def) {
switch (custom_def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(custom_def.Composite.AlgoTemplateKey);
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(custom_def.Dynamic.DynamicOrderType);
default: return new AlgoID(custom_def.CustomCase);
}
}
public static AlgoID from_algo_key(AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Factory: return new AlgoID(key.factory);
case AlgoKey.Mode.Dynamic: return new AlgoID(key.dynamic_type);
case AlgoKey.Mode.Persisted: return new AlgoID(key.persisted);
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
}
}
public static AlgoKey to_algo_key(AlgoID id) {
if (id._factory_key.HasValue)
return AlgoKey.from_known_type(id._factory_key.Value);
if (id._persisted_key.HasValue)
return AlgoKey.from_composite_key(id._persisted_key.Value);
if (id._dynamic_key != null)
return AlgoKey.from_dynamic_type(id._dynamic_key);
throw new ArgumentException("ID is empty.", nameof(id));
}
public bool Equals(AlgoID other) =>
_factory_key == other._factory_key &&
_dynamic_key == other._dynamic_key &&
_persisted_key == other._persisted_key;
public override bool Equals(object obj) =>
obj is AlgoID other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
_factory_key?.GetHashCode() ?? 0,
_dynamic_key?.GetHashCode() ?? 0,
_persisted_key?.GetHashCode() ?? 0);
public static bool operator ==(AlgoID a, AlgoID b) => a.Equals(b);
public static bool operator !=(AlgoID a, AlgoID b) => !a.Equals(b);
}
struct AlgoKey : IEquatable<AlgoKey> {
readonly uint? _composite_key;
readonly AlgoPersistedKey? _persisted_key;
readonly AlgoNameStructureKey? _factory_key;
readonly Guid? _unpersisted;
readonly string _dynamic_type;
readonly CustomOrderDefinition.CustomOneofCase? _factory_type;
AlgoKey(Guid id_guid) {
_composite_key = null;
public AlgoKey(Guid id_guid) {
_persisted_key = null;
_factory_key = null;
_unpersisted = id_guid;
_dynamic_type = null;
_factory_type = null;
}
public AlgoKey(uint composite_key) {
_composite_key = composite_key;
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_root_algo_type)
: this(new AlgoNameStructureKey(factory_root_algo_type)) { }
public AlgoKey(AlgoNameStructureKey factory_key) {
_persisted_key = null;
_factory_key = factory_key;
_unpersisted = null;
_dynamic_type = null;
_factory_type = null;
}
public AlgoKey(string dynamic_type) {
_composite_key = null;
public AlgoKey(AlgoPersistedKey persisted_key) {
_persisted_key = persisted_key;
_factory_key = null;
_unpersisted = null;
_dynamic_type = dynamic_type;
_factory_type = null;
}
public AlgoKey(CustomOrderDefinition.CustomOneofCase factory_type) {
_composite_key = null;
_unpersisted = null;
_dynamic_type = null;
_factory_type = factory_type;
}
public enum Mode {
None,
Persisted,
Unpersisted,
Dynamic,
Factory
}
public bool is_persisted => mode == Mode.Persisted;
public uint persisted => _composite_key.Value;
public AlgoPersistedKey persisted => _persisted_key.Value;
public AlgoNameStructureKey factory => _factory_key ?? new AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase.None);
public Guid unpersisted => _unpersisted.Value;
public CustomOrderDefinition.CustomOneofCase factory => _factory_type.Value;
public string dynamic_type => _dynamic_type;
public Mode mode {
get {
if (_factory_type.HasValue) return Mode.Factory;
if (_composite_key.HasValue) return Mode.Persisted;
if (_dynamic_type != null) return Mode.Dynamic;
if (_persisted_key.HasValue) return Mode.Persisted;
if (_unpersisted.HasValue) return Mode.Unpersisted;
if (_factory_key.HasValue) return Mode.Factory;
return Mode.None;
}
}
public static AlgoKey create_unpersisted() =>
new AlgoKey(Guid.NewGuid());
public static AlgoKey from_custom_def(CustomOrderDefinitionImmutable custom_def) {
switch (custom_def.CustomCase) {
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoKey(custom_def.Dynamic.DynamicOrderType);
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoKey(custom_def.Composite.AlgoTemplateKey);
default: return new AlgoKey(custom_def.CustomCase);
}
}
public static AlgoKey from_known_type(CustomOrderDefinition.CustomOneofCase known_type) {
if (known_type == CustomOrderDefinition.CustomOneofCase.Dynamic)
throw new ArgumentException(nameof(known_type), "Potential loss of data: no dynamic type name specified.");
if (known_type == CustomOrderDefinition.CustomOneofCase.Composite)
throw new ArgumentException(nameof(known_type), "Potential loss of data: no composite id specified.");
return new AlgoKey(known_type);
}
public static AlgoKey from_dynamic_type(string dynamic_type) {
if (string.IsNullOrEmpty(dynamic_type))
throw ExceptionBuilder.Argument.null_or_empty(nameof(dynamic_type));
return new AlgoKey(dynamic_type);
}
public static AlgoKey from_composite_key(uint composite_key) =>
new AlgoKey(composite_key);
public bool Equals(AlgoKey other) {
Mode m = mode;
if (m == other.mode) {
@@ -179,7 +59,6 @@ namespace mbts.spark {
case Mode.Persisted: return persisted == other.persisted;
case Mode.Unpersisted: return unpersisted == other.unpersisted;
case Mode.Factory: return factory == other.factory;
case Mode.Dynamic: return dynamic_type == other.dynamic_type;
case Mode.None: return true;
}
}
@@ -194,7 +73,6 @@ namespace mbts.spark {
case Mode.Persisted: hash = persisted.GetHashCode(); phase_mult = 2; break;
case Mode.Unpersisted: hash = unpersisted.GetHashCode(); phase_mult = 4; break;
case Mode.Factory: hash = factory.GetHashCode(); phase_mult = 3; break;
case Mode.Dynamic: hash = dynamic_type.GetHashCode(); phase_mult = 5; break;
default: return 0;
}
return EqualityComparer.phase_hash(hash, phase_mult);
@@ -40,7 +40,7 @@ namespace bts.persist.algo {
public bool is_persisted => mode != Mode.None;
public mbts.spark.AlgoKey to_algo_key() =>
new mbts.spark.AlgoKey(_id_uint ?? 0);
new mbts.spark.AlgoKey(this);
public bool Equals(AlgoPersistedKey other) {
Mode m = mode;
@@ -0,0 +1,104 @@
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using ProtoBuf;
using System;
using System.Linq;
namespace mbts.spark {
struct AlgoNameStructureKey : IEquatable<AlgoNameStructureKey> {
public readonly string name;
public readonly AlgoStructureKey structure;
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
: this(definition.CustomCase.type_id(), definition) { }
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
: this(name, new AlgoStructureKey(definition)) { }
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
public AlgoNameStructureKey(string name, AlgoStructureKey structure) {
this.name = name;
this.structure = structure;
}
public bool Equals(AlgoNameStructureKey other) => name == other.name && structure == other.structure;
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() => EqualityComparer.aggregate_hash(EqualityComparer.safe_get_hash_code(name), structure.GetHashCode());
public static bool operator ==(AlgoNameStructureKey a, AlgoNameStructureKey b) => a.Equals(b);
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
}
[ProtoContract]
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
[ProtoMember(1)]
readonly int[] _structure;
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
: this(new[] { (int)base_type }) { }
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
int[] s = new[] { (int)definition.CustomCase };
_build_structure(definition, ref s, 1);
_structure = s;
}
public AlgoStructureKey(int[] structure) =>
_structure = structure;
public CustomOrderDefinitionImmutable build_def() {
int pos = 0;
var mdef = new CustomOrderDefinition();
_build_def(_structure, ref pos, ref mdef);
return new CustomOrderDefinitionImmutable(mdef);
}
public bool Equals(AlgoStructureKey other) =>
_structure.Length == other._structure.Length &&
_structure.SequenceEqual(other._structure);
public override bool Equals(object obj) =>
obj is AlgoStructureKey other && Equals(other);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(_structure);
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
_build_structure(definition.CloneToMutable(), ref structure, pos);
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
var cad = OrderDataHelper.get_child_algo_data(algo);
for (int di = 0; di < cad.Count; di++) {
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
ArrayHelper.ensure_capacity(ref structure, pos + 1);
int del_mask = 1 << (31 - di);
structure[pos++] = (int)child_algo.CustomCase | del_mask;
_build_structure(child_algo, ref structure, pos);
break;
}
}
ArrayHelper.trim(ref structure, pos);
}
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
for (int i = pos; i < structure.Length; i++) {
var cad = OrderDataHelper.get_child_algo_data(algo);
for (int di = 0; di < cad.Count; di++) {
int mask = 1 << (31 - di);
if ((structure[i] & mask) == mask) {
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
pos++;
_build_def(structure, ref pos, ref child_algo);
cad[di].assign_child(algo, child_algo);
}
}
}
}
}
}
@@ -0,0 +1,345 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.persist.algo;
using bts.spark;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
class AlgoStudioDefinitionTemplateModel : IAlgoStudioModel, IDisposable {
readonly AlgoService _service;
readonly AdminModel _admin_model;
readonly ErrorReporter _error_reporter;
readonly ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata> _metadata_demuxer = new ObservableDemuxer<AlgoKey, AlgoDefinitionMetadata>();
readonly TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey> _definition_templates_demuxer;
IReadOnlyDictionary<AlgoKey, AlgoData> _algos = DictionaryHelper.empty<AlgoKey, AlgoData>();
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
IDisposable _dispose;
public AlgoStudioDefinitionTemplateModel(AlgoService service, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
_service = service;
metadata_service = meta;
_admin_model = admin_model;
_error_reporter = error_reporter;
this.ui_configuration = ui_configuration;
_definition_templates_demuxer = new TableUpdateDemuxer<AlgoTemplateData, AlgoKey, AlgoKey>(t => t.key, t => t.definition_key);
definitions = _service.subscribei_algos()
.Select(u => new Update<AlgoData, AlgoKey>(
u.all.Select(_Utils.definition_data).ToList(),
u.added.Select(_Utils.definition_data).ToList(),
u.changes.Select(ch => new Change<AlgoData>(_Utils.definition_data(ch.Old), _Utils.definition_data(ch.New))).ToList(),
u.removed.Select(d => _Utils.definition_data(d)).ToList(),
def => def.key))
.Table()
.Cache();
templates = _service.subscribei_algo_templates()
.Select(u => new Update<AlgoTemplateData, AlgoKey>(
u.all.Select(_Utils.template_data).ToList(),
u.added.Select(_Utils.template_data).ToList(),
u.changes.Select(ch => new Change<AlgoTemplateData>(_Utils.template_data(ch.Old), _Utils.template_data(ch.New))).ToList(),
u.removed.Select(_Utils.template_data).ToList(),
def => def.key))
.Table()
.Cache();
_dispose = new CompositeDisposable(
definitions.OnDispatcher().Subscribe(t => _algos = t.state),
templates.OnDispatcher().Subscribe(t => _algo_templates = t.state),
templates.Subscribe(_definition_templates_demuxer),
metadata_service.subscribe_metadata().Subscribe(_metadata_demuxer));
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates { get; }
public IAlgoDefinitionMetadataService metadata_service { get; }
public bts.config.AlgoStudioOptions ui_configuration { get; }
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition) =>
_definition_templates_demuxer.subscribe(definition);
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
if (definition_data.key.is_persisted) {
// TODO: implement update op when backend ready
} else {
// create op
return await _create_definition(new CompositeAlgoDefinitionImmutable(_Utils.to_definition(definition_data)));
}
return null;
}
public async Task<AlgoTemplateData?> save_template(AlgoTemplateData template_data) {
if (template_data.key.is_persisted) {
// update op
return await _update_template(_Utils.to_recordi(template_data));
} else {
// create op
return await _create_template(_Utils.to_templatei(template_data));
}
}
/// <summary>
/// The api is not used here (there are some unresolved dependency issues with definitions on the backend).
/// The definition is marked as hidden in the AlgoMetadata stored on the server.
/// <para/>
/// For implementation details, see: <see cref="IAlgoDefinitionMetadataService"/>, <see cref="AlgoTemplateMetadataService"/>
/// </summary>
public async Task<bool> remove_definition(AlgoKey key) {
if (_algos.TryGetValue(key, out AlgoData curr)) {
if (_definition_templates_demuxer.try_get_values(key, out IReadOnlyDictionary<AlgoKey, AlgoTemplateData> tmps)) {
foreach (var t in tmps.Values)
await remove_template(t.key);
}
await metadata_service.hide_definition(key);
return true;
}
return false;
}
public async Task<bool> remove_template(AlgoKey template_key) {
var req = new AlgoServiceRemoveRequest();
_Utils.set_key(ref req, template_key);
var reply = await _safe_execute(() => _service.remove_template_async(req), ActionMetadata.remove_template);
return reply.success && reply.value.Success;
}
public void Dispose() =>
Disposable.dispose(ref _dispose);
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition) {
if (_try_get_user(out uint user_id)) {
return await _create_definition(definition, user_id);
} else {
_report_no_user_id(ActionMetadata.CreateDefinition);
}
return null;
}
async Task<AlgoTemplateData?> _create_template(AlgoTemplateImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.create_template_async(
new TemplateCreateRequest { UserId = user_id, NewTemplate = template.CloneToMutable() }),
r => r.Success, r => r.ErrMsg, ActionMetadata.CreateTemplate);
if (success)
return _Utils.template_data(value.Record);
} else {
_report_no_user_id(ActionMetadata.CreateTemplate);
}
return null;
}
async Task<AlgoTemplateData?> _update_template(AlgoTemplateRecordImmutable template) {
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.update_template_async(
new TemplateUpdateRequest { Key = template.Key, UpdatedTemplate = template.AlgoTemplate.CloneToMutable() }),
r => r.Success, r => r.ErrMsg.ToString(), ActionMetadata.UpdateTemplate);
if (success)
return _Utils.template_data(template);
} else {
_report_no_user_id(ActionMetadata.UpdateTemplate);
}
return null;
}
async Task<AlgoData?> _create_definition(CompositeAlgoDefinitionImmutable definition, uint user_id) {
(var result, var success) = await _error_reporter.safe_execute(() => _service.create_composite_algo_async(
new CompositeAlgoCreateRequest {
NewAlgo = definition.CloneToMutable(),
UserId = user_id
}), r => r.Success, r => r.ErrMsg, "create definition");
return success ? _Utils.definition_data(new CompositeAlgoRecordImmutable(result.Record)) : (AlgoData?)null;
}
async Task<Result<T>> _safe_execute<T>(Func<Task<Result<T>>> execute, IActionMetadata<T> am) {
try {
var result = await execute();
if (!result.success) {
_report_error(am.present_progressive_desc, result.error_message);
} else if (!am.get_success(result.value)) {
_report_error(am.present_progressive_desc, am.get_error_msg(result.value));
}
return result;
} catch (Exception ex) {
_report_error(am.present_progressive_desc, ex.ToString());
}
return new Result<T>(bts.api.ApiError.UnexpectedResponse);
}
void _report_error(string present_progressive_desc, string msg) =>
_error_reporter.report_error($"Error {present_progressive_desc}: {msg}", AlgoStudioWindow.NAME, System.Windows.MessageBoxImage.Warning);
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
// used for error handling/reporting
interface IActionMetadata {
string desc { get; }
string present_progressive_desc { get; }
}
interface IActionMetadata<TReply> : IActionMetadata {
bool get_success(TReply reply);
string get_error_msg(TReply reply);
}
static class ActionMetadata {
public const string
CreateDefinition = "create definition",
UpdateDefinition = "update definition",
CreateTemplate = "create template",
UpdateTemplate = "update template";
public static readonly IActionMetadata<AlgoServiceReply> update_template = new Impl<AlgoServiceReply>(_update, _template, r => r.Success, r => r.ErrMsg.ToString());
public static readonly IActionMetadata<AlgoServiceReply> remove_template = new Impl<AlgoServiceReply>(_delete, _template, r => r.Success, r => r.ErrMsg.ToString());
public static readonly IActionMetadata<TemplateCreateResult> create_template = new Impl<TemplateCreateResult>(_create, _template, r => r.Success, r => r.ErrMsg);
public static readonly IActionMetadata<CompositeAlgoCreateResult> create_definition = new Impl<CompositeAlgoCreateResult>(_create, _definition, r => r.Success, r => r.ErrMsg);
static readonly DescTuple _create = new DescTuple("create", "creating"), _update = new DescTuple("update", "updating"), _delete = new DescTuple("delete", "deleting");
static readonly string _definition = "definition", _template = "template";
class Impl<T> : IActionMetadata<T> {
readonly DescTuple _desc;
readonly string _subject;
readonly Func<T, bool> _get_success;
readonly Func<T, string> _get_error_msg;
public Impl(DescTuple desc, string subject, Func<T, bool> get_success, Func<T, string> get_error_msg) {
_desc = desc;
_subject = subject;
_get_success = get_success;
_get_error_msg = get_error_msg;
}
public string desc => _desc.desc;
public string present_progressive_desc => _desc.present_progressive_desc;
public bool get_success(T reply) => _get_success(reply);
public string get_error_msg(T reply) => _get_error_msg(reply);
}
struct DescTuple {
public readonly string desc, present_progressive_desc;
public DescTuple(string desc, string present_progressive_desc) {
this.desc = desc;
this.present_progressive_desc = present_progressive_desc;
}
}
}
/// <summary>
/// The AlgoService protobuf types have been extracted out of the viewmodels (<see cref="AlgoStudioVM"/>, <see cref="OrderPropertiesEditorVM"/>, <see cref="AlgoDefinitionVM"/>, <see cref="AlgoDefinitionTemplateVM"/>).
/// The viewmodels instead use these types to represent the data: <see cref="AlgoData"/> is used in place of <see cref="CompositeAlgoRecordImmutable"/>, and <see cref="AlgoTemplateData"/> is used in place of <see cref="AlgoTemplateRecordImmutable"/>.
/// This thin abstraction isolates the server-side from the client-side, so if one day the server side types change, we won't need to change any client-side code except the methods below which handle the conversion from server type to corresponding client type.
/// There are a couple other methods in here as well (this is an extensions class afterall), but [John]I am going out of town soon (1/6/18) and did not get a chance to organize these methods beyond containing them in <see cref="Utils"/>.
/// </summary>
static class _Utils {
// AlgoTemplateImmutable custom_order_definition(i)
public static CustomOrderDefinitionImmutable custom_order_definitioni(AlgoTemplateImmutable templatei) {
switch (templatei.AlgoParamsCase) {
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return templatei.CompositeAlgoParams.Algo;
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams: return templatei.SystemAlgoParams;
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
}
}
public static AlgoData definition_data(AlgoTemplateImmutable templatei) {
switch (templatei.AlgoParamsCase) {
case AlgoTemplate.AlgoParamsOneofCase.CompositeAlgoParams: return definition_data(templatei.CompositeAlgoParams);
case AlgoTemplate.AlgoParamsOneofCase.SystemAlgoParams:
return new AlgoData(
templatei.SystemAlgoParams,
templatei.SystemAlgoParams.CustomCase.display_name(),
get_key(templatei.SystemAlgoParams));
default: throw ExceptionBuilder.Argument.unexpected_case(templatei.AlgoParamsCase, nameof(templatei));
}
}
public static AlgoData definition_data(CompositeAlgoRecordImmutable recordi) => new AlgoData(recordi.AlgoDefinition.Algo, recordi.AlgoDefinition.Name, get_key(recordi));
public static AlgoData definition_data(CompositeAlgoDefinitionImmutable definitioni) =>
new AlgoData(definitioni.Algo, definitioni.Name, get_key(definitioni));
public static AlgoData? definition_data(Result<CompositeAlgoCreateResult> result) {
if (result.success && result.value.Success) {
var r = result.value.Record;
return new AlgoData(new CustomOrderDefinitionImmutable(r.AlgoDefinition.Algo), r.AlgoDefinition.Name, new AlgoKey(new AlgoPersistedKey(r.Key)));
} else return null;
}
// AlgoTemplateRecord(Immutable) template data
public static AlgoTemplateData template_data(AlgoTemplateRecord record) => template_data(new AlgoTemplateRecordImmutable(record));
public static AlgoTemplateData template_data(AlgoTemplateRecordImmutable recordi) => new AlgoTemplateData(custom_order_definitioni(recordi.AlgoTemplate), recordi.AlgoTemplate.Name, get_key(recordi), definition_data(recordi.AlgoTemplate));
public static CompositeAlgoDefinition to_definition(AlgoData definition_data) =>
new CompositeAlgoDefinition {
Name = definition_data.name,
Algo = definition_data.value.CloneToMutable()
};
// AlgoTemplateData to_record(i)
public static AlgoTemplateRecord to_record(AlgoTemplateData template) {
var record = new AlgoTemplateRecord {
AlgoTemplate = new AlgoTemplate {
Name = template.name,
CompositeAlgoParams = new CompositeAlgoDefinition {
Name = template.definition.name,
Algo = template.value.CloneToMutable()
}
}
};
set_key(ref record, template.key);
return record;
}
public static AlgoTemplateRecordImmutable to_recordi(AlgoTemplateData template) => new AlgoTemplateRecordImmutable(to_record(template));
// AlgoTemplateData to_template(i)
public static AlgoTemplate to_template(AlgoTemplateData template) =>
new AlgoTemplate {
Name = template.name,
CompositeAlgoParams = new CompositeAlgoDefinition {
Name = template.definition.name,
Algo = template.value.CloneToMutable()
}
};
public static AlgoTemplateImmutable to_templatei(AlgoTemplateData template) => new AlgoTemplateImmutable(to_template(template));
public static AlgoKey get_key(CompositeAlgoRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
public static AlgoKey get_key(AlgoTemplateRecordImmutable recordi) => new AlgoKey(new AlgoPersistedKey(recordi.Key));
public static AlgoKey get_key(CompositeAlgoDefinitionImmutable definitioni) => new AlgoKey(new AlgoPersistedKey(definitioni.Name));
public static AlgoKey get_key(CustomOrderDefinitionImmutable custom_order_definitioni) => new AlgoKey(custom_order_definitioni.CustomCase);
public static void set_key(ref AlgoTemplateRecord record, AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Persisted:
switch (key.persisted.mode) {
case AlgoPersistedKey.Mode.String:
record.Key = key.persisted.id_string; break;
}
break;
}
}
public static void set_key(ref AlgoServiceRemoveRequest request, AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Persisted:
switch (key.persisted.mode) {
case AlgoPersistedKey.Mode.String:
request.Key = key.persisted.id_string; break;
}
break;
}
}
}
}
}
@@ -1,16 +1,18 @@
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman.algo;
using bts.persist.algo;
using bts.spark;
using mbts.spark.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark {
[Export(typeof(IAlgoStudioModel))]
class AlgoStudioFlatDefinitionModel : IAlgoStudioModel, IDisposable {
readonly AlgoDefinitionService _service;
readonly AdminModel _admin_model;
@@ -19,34 +21,33 @@ namespace mbts.spark {
IReadOnlyDictionary<AlgoKey, AlgoTemplateData> _algo_templates = DictionaryHelper.empty<AlgoKey, AlgoTemplateData>();
IDisposable _dispose;
[ImportingConstructor]
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter) {
public AlgoStudioFlatDefinitionModel(AlgoDefinitionService service, IAlgoDefinitionMetadataService metadata_service, AdminModel admin_model, ErrorReporter error_reporter, bts.config.AlgoStudioOptions ui_configuration) {
_service = service;
this.metadata_service = metadata_service;
_admin_model = admin_model;
_error_reporter = error_reporter;
definitions =
service.subscribei_algo_definitions()
.Filter(r => r.Metadata.IsActive && r.Metadata.CreatorUserId == _admin_model.user.id)
.SelectKeyValue(_definition_data, d => d.key)
.Table()
.Cache();
this.ui_configuration = ui_configuration;
definitions = service.subscribei_algo_definitions().Filter(r => r.Metadata.IsActive).SelectKeyValue(_definition_data, d => d.key).Table().Cache();
}
public IObservable<TableUpdate<AlgoData, AlgoKey>> definitions { get; }
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> templates => Observable.Empty<TableUpdate<AlgoTemplateData, AlgoKey>>();
public IAlgoDefinitionMetadataService metadata_service { get; }
public bts.config.AlgoStudioOptions ui_configuration { get; } = config.Configuration.get<bts.config.AlgoStudioOptions>();
public bts.config.AlgoStudioOptions ui_configuration { get; }
public IObservable<TableUpdate<AlgoTemplateData, AlgoKey>> subscribe_definition_templates(AlgoKey definition_key) =>
Observable.Return(TableUpdate<AlgoTemplateData, AlgoKey>.mk_empty(t => t.key));
public async Task<AlgoData?> save_definition(AlgoData definition_data) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = _admin_model.user.id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
if (_try_get_user(out uint user_id)) {
var (value, success) = await _error_reporter.safe_execute(() => _service.save_algo_definition_async(
_with_key(new AlgoDefinitionUpdateRequest { UserId = user_id, Algo = _template(definition_data) }, definition_data.key)),
r => r.Success, r => r.ErrorMessage, "saving template");
if (success)
return _definition_data(new AlgoDefinitionRecordImmutable(value.Record));
} else {
_report_no_user_id("save template");
}
return null;
}
@@ -72,10 +73,10 @@ namespace mbts.spark {
new AlgoDefinitionImmutable(_template(definition_data));
static AlgoData _definition_data(AlgoDefinitionRecordImmutable recordi) =>
AlgoData.from_record(recordi);
new AlgoData(recordi.Algo.Definition, recordi.Algo.Name, _key(recordi));
static AlgoKey _key(AlgoDefinitionRecordImmutable recordi) =>
AlgoKey.from_composite_key(recordi.Key);
new AlgoKey(new AlgoPersistedKey(recordi.Key));
static AlgoDefinitionUpdateRequest _with_key(AlgoDefinitionUpdateRequest request, AlgoKey key) {
_set_key(ref request, key);
@@ -84,10 +85,9 @@ namespace mbts.spark {
static void _set_key(ref AlgoDefinitionUpdateRequest request, AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
case AlgoKey.Mode.Unpersisted:
case AlgoKey.Mode.Factory:
case AlgoKey.Mode.Dynamic:
case AlgoKey.Mode.None: request.Key = 0; break;
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
}
@@ -95,7 +95,7 @@ namespace mbts.spark {
static void _set_key(ref AlgoDefinitionRemoveRequest request, AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Persisted: request.Key = key.persisted; break;
case AlgoKey.Mode.Persisted: request.Key = key.persisted.id_uint; break;
default: throw ExceptionBuilder.Argument.unexpected_case(key.mode, nameof(key));
}
}
@@ -105,5 +105,16 @@ namespace mbts.spark {
void _report_no_user_id(string desc) =>
_error_reporter.report_error($"{desc} failed: did not find current user id");
bool _try_get_user(out uint id) {
var user = _admin_model.get_current_user();
if (user.HasValue) {
id = user.Value.id;
return true;
} else {
id = 0;
return false;
}
}
}
}
@@ -1,7 +1,10 @@
using bts;
using bts.api;
using bts.api.core;
using bts.spark;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
namespace mbts.spark {
@@ -17,13 +20,34 @@ namespace mbts.spark {
bts.config.AlgoStudioOptions ui_configuration { get; }
}
class AlgoStudioModelExporter {
[ImportingConstructor]
AlgoStudioModelExporter(Spark.AlgoServiceWrapper service_wrapper, IAlgoDefinitionMetadataService meta, AdminModel admin_model, ErrorReporter error_reporter) {
var ui_configuration = config.Configuration.get<bts.config.AlgoStudioOptions>();
switch (service_wrapper.mode) {
case Spark.AlgoServiceWrapper.Mode.Flat: model = new AlgoStudioFlatDefinitionModel(service_wrapper.flat, meta, admin_model, error_reporter, ui_configuration); break;
case Spark.AlgoServiceWrapper.Mode.Hierarchical: model = new AlgoStudioDefinitionTemplateModel(service_wrapper.hierarhical, meta, admin_model, error_reporter, ui_configuration); break;
}
}
[Export]
public IAlgoStudioModel model { get; }
}
static class AlgoStudioModelUtils {
public static bool templates_enabled(this IAlgoStudioModel model) =>
get_templates_enabled(model.ui_configuration);
public static bool get_templates_enabled(bts.config.AlgoStudioOptions ui_configuration) =>
ui_configuration.add_template_button != bts.config.AlgoStudioOptions.AddTemplateButton.None;
public static IReadOnlyDictionary<object, object> get_dynamic_resources(bts.config.AlgoStudioOptions ui_configuration) =>
new Dictionary<object, object> {
{ AlgoStudioWindow.ShowAlgoToolBarRenameDefinitionKey, ui_configuration.can_modify_definition_name && ui_configuration.show_algo_toolbar_rename },
{ AlgoStudioWindow.ShowAlgoToolBarSaveDefinitionKey, ui_configuration.can_modify_definition_values && ui_configuration.show_algo_toolbar_save },
{ AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey, ui_configuration.show_algo_toolbar_revert },
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo }
{ AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey, ui_configuration.show_algo_toolbar_undo_redo },
{ AlgoStudioWindow.TemplatesEnabledKey, get_templates_enabled(ui_configuration) }
};
}
}
@@ -35,9 +35,9 @@ namespace mbts.spark {
internal AlgoDefinitionPersistedMetadata to_persisted() =>
id.mode != AlgoKey.Mode.Persisted ?
throw new InvalidOperationException("Metadata does not have a persisted key.") :
new AlgoDefinitionPersistedMetadata(new AlgoPersistedKey(id.persisted),
default_template.mode == AlgoKey.Mode.Persisted ? new AlgoPersistedKey(default_template.persisted) : default,
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => new AlgoPersistedKey(t.persisted)).ToArray(),
new AlgoDefinitionPersistedMetadata(id.persisted,
default_template.mode == AlgoKey.Mode.Persisted ? default_template.persisted : default,
favorite_templates.Where(t => t.mode == AlgoKey.Mode.Persisted).Select(t => t.persisted).ToArray(),
is_favorite, is_hidden, abbreviation, new NullableColorProto(badge_color));
public AlgoDefinitionMetadata with(AlgoKey? id = null, AlgoKey? default_template = null, IEnumerable<AlgoKey> favorite_templates = null, bool? is_favorite = null, bool? is_hidden = null, string abbreviation = null, NullableColorProto? badge_color = null) =>
@@ -70,7 +70,7 @@ namespace mbts.spark {
is_hidden.GetHashCode(),
abbreviation == null ? 0 : abbreviation.GetHashCode(),
badge_color?.GetHashCode() ?? 0,
_favorite_templates == null ? 0 : EqualityComparer.aggregate_hash_limited(_favorite_templates));
EqualityComparer.aggregate_hash_limited(_favorite_templates));
public static bool operator ==(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => a.Equals(b);
public static bool operator !=(AlgoDefinitionMetadata a, AlgoDefinitionMetadata b) => !a.Equals(b);
@@ -1,37 +1,24 @@
using bts.api.core;
using bts.api.orderman;
using bts.utils;
using mbts.collections;
using mbts.ui.utilities;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark {
static class AlgoCollectionHelper {
public static readonly IComparer<AlgoDefinitionVM> algo_definition_vm_comparer =
new ComparerBuilder<AlgoDefinitionVM>(
new[] {
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_groups),
new [] {
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_modes),
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
public static readonly IComparer<AlgoData> algo_data_comparer =
new ComparerBuilder<AlgoData>(
new[] {
Comparer<AlgoData>.Create(_CompareUtils.compare_groups),
Comparer<AlgoData>.Create(_CompareUtils.compare_names)
}
).to_comparer();
public static readonly IComparer<AlgoDataVM> algo_data_vm_comparer =
algo_data_comparer.wrap<AlgoData, AlgoDataVM>(vm => vm.data);
public static readonly IComparer<IAlgoVM> algo_vm_comparer =
new ComparerBuilder<IAlgoVM>(
new[] {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_groups),
new [] {
Comparer<IAlgoVM>.Create(_CompareUtils.compare_modes),
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
}).to_comparer().to_safe();
@@ -47,76 +34,29 @@ namespace mbts.spark {
}
}
public static void update_persisted_definition_wrappers(IDictionary<AlgoKey, IAlgoVM> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
foreach (var d in update.removed)
target.Remove(d.key);
foreach (var d in update.changed) {
var vm = target[d.key];
if (vm is AlgoVMWrapper wrapper) {
wrapper.value.load(d);
} else vm.load_from(d.value);
}
foreach (var d in update.added) {
if (target.TryGetValue(d.key, out IAlgoVM def)) {
def.load_from(d.value);
} else target.Add(d.key, new AlgoVMWrapper(factory(d)));
}
}
public static void commit_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).commit_all();
public static void commit_all(this IEnumerable<IAlgoParamVM> algo_params) {
foreach (var p in algo_params)
p.mark_committed();
}
public static void revert_all(this IEnumerable<AlgoParamRowVM> algo_param_rows) =>
algo_param_rows.Select(r => r.param).revert_all();
public static void revert_all(this IEnumerable<IAlgoParamVM> algo_params) {
foreach (var p in algo_params)
p.revert();
}
public static AlgoDisplayGroup display_group(CustomOrderDefinition.CustomOneofCase order_type) => order_type.is_bts_algo_type() ? AlgoDisplayGroup.BTS : AlgoDisplayGroup.Exchange;
public static AlgoDisplayGroup display_group(this AlgoKey key) {
switch (key.mode) {
case AlgoKey.Mode.Factory: return display_group(key.factory);
case AlgoKey.Mode.Dynamic: return AlgoDisplayGroup.API;
case AlgoKey.Mode.Unpersisted:
case AlgoKey.Mode.Persisted:
return AlgoDisplayGroup.Studio;
}
return AlgoDisplayGroup.BTS;
}
static class _CompareUtils {
static AlgoDisplayGroup[] _sorted_groups = new[] {
AlgoDisplayGroup.Exchange,
AlgoDisplayGroup.BTS,
AlgoDisplayGroup.Studio,
AlgoDisplayGroup.API
};
public static int compare_modes(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
_compare_modes(a.key.mode, b.key.mode);
public static int compare_groups(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_groups(AlgoData a, AlgoData b) =>
Array.IndexOf(_sorted_groups, a.key.display_group()).CompareTo(Array.IndexOf(_sorted_groups, b.key.display_group()));
public static int compare_groups(IAlgoVM a, IAlgoVM b) =>
Array.IndexOf(_sorted_groups, a.group).CompareTo(Array.IndexOf(_sorted_groups, b.group));
public static int compare_modes(IAlgoVM a, IAlgoVM b) =>
_compare_modes(a.id.mode, b.id.mode);
public static int compare_names(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(AlgoData a, AlgoData b) =>
DisplayNameHelper.compare_alpha_numeric(a.name, b.name);
public static int compare_names(IAlgoVM a, IAlgoVM b) =>
DisplayNameHelper.compare_alpha_numeric(a.display_name, b.display_name);
static int _compare_modes(AlgoKey.Mode a, AlgoKey.Mode b) {
// Factory mode is 'lowest', consider all other modes equivalent.
if (a == AlgoKey.Mode.Factory) {
if (b != AlgoKey.Mode.Factory)
return -1;
} else if (b == AlgoKey.Mode.Factory) {
return 1;
}
return 0;
}
}
}
}
}
@@ -0,0 +1,243 @@
using bts;
using bts.api.orderman;
using bts.utils;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.shell;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Windows.Input;
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
namespace mbts.spark {
class AlgoDefinitionTemplateVM : DisposableViewModelBase, IAlgoStudioContentVM, IEquatable<AlgoDefinitionTemplateVM> {
/// <see cref="CustomType"/> is referenced by an x:Static extension in OrderDetailsPopupControl.xaml <see cref="mbts.ui.spark.algo.OrderDetailsPopupControl"/>.
/// In most cases, <see cref="mbts.ui.NestedExtension"/> works just like <see cref="System.Windows.Markup.TypeExtension"/>, but for nested types, such as <see cref="CustomTemplatePlaceholderVM"/>.
/// However, when the former is used to denote the DataType of a DataTemplate, a strange XamlReader exception is thrown at runtime.
/// After spending ~15 minutes investigating what was happening, [John]I decided it was more trouble than it was worth and opted for this workaround.
public static readonly Type CustomType = typeof(CustomTemplatePlaceholderVM);
readonly OrdersModel _model;
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
/// Note: the difference between <see cref="EditStack{T}"/> and <see cref="VersionContainer{T}"/> is that the former implements push/pop functionality so the user may undo/redo (like ctrl+z/ctrl+y in text editors),
/// while the latter is a lazy delegate-based container for the most recent value (<see cref="VersionContainer{T}.increment_version"/>).
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
readonly VersionContainer<Snapshot> _version;
readonly DeferrableActionWrapper _refresh_on_local_change;
readonly Property<IAlgoVM> _algo_root;
readonly DelegateProperty<bool> _can_undo, _can_redo, _is_dirty;
readonly Property<bool> _is_default, _is_favorite, _is_selected;
readonly Property<string> _name, _abbreviation;
// maintains the state of each algo-root, so the user may change the definition root back and forth without any loss of state amongst the templates
Snapshot _committed;
public AlgoDefinitionTemplateVM(AlgoDefinitionVM owner, OrdersModel model, AlgoData data, AlgoEditingPermissions editing_permissions = AlgoEditingPermissions.AllowEditValues) {
// init core data
this.owner = owner;
_model = model;
key = data.key;
_version = new VersionContainer<Snapshot>(_snapshot);
// maintain states for different algo roots, so they aren't lost when the user changes the root algo type
var algo_map = new Dictionary<AlgoKey, IAlgoVM> {
{ data.key, algo_vm_factory(data.value) }
};
// init backing props
_name = backing(nameof(this.name), name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
_is_default = backing(nameof(is_default), false);
_is_favorite = backing(nameof(is_favorite), false);
_is_selected = backing(nameof(is_selected), false);
_algo_root = backing(nameof(algo_root), get_algo_root(owner.algo_root));
_committed = _version.value;
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
_is_dirty = backing(nameof(is_dirty), () => !_committed.Equals(_version.value));
_can_redo.Changed += on_can_redo_changed;
_can_undo.Changed += on_can_undo_changed;
set_as_default_cmd = new DelegateCommand(() => {
if (!is_default && key.mode == AlgoKey.Mode.Persisted)
_model.algo_model.metadata_service.set_as_default(owner.key, key).ignore();
});
// init visual states
// TODO: implement these features in IAlgoMetadataService; until then, set the appropriate visual states.
_edit_pane_vs_model.set_state(VisualStateNames.Abbreviation.GroupName, VisualStateNames.Abbreviation.NotSupportedForTemplates);
_tree_view_vs_model.set_state(VisualStateNames.FavoriteToggleButton.GroupName, VisualStateNames.FavoriteToggleButton.Inactive);
switch (key.mode) {
case AlgoKey.Mode.Persisted:
_edit_stack.push(_committed);
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active);
break;
case AlgoKey.Mode.Unpersisted: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Active); break;
case AlgoKey.Mode.Factory: _edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive); break;
}
// set hooks/subscriptions
_refresh_on_local_change = new DeferrableActionWrapper(on_local_changed);
_dispose = new System.Reactive.Disposables.CompositeDisposable(
as_demuxer().subscribe_many(_refresh_on_local_change.execute, nameof(name), nameof(abbreviation), nameof(is_default), nameof(is_favorite)),
owner.observe_algo_root().Subscribe(r => _algo_root.set(get_algo_root(r))),
_algo_root.observe_current(true).SelectManyInSeries(r => r?.subscribe_definition()).RefCount().Subscribe(_ => _refresh_on_local_change.execute()),
Disposable.wrap_collection(algo_map.Values)) {
() => {
_can_redo.Changed -= on_can_redo_changed;
_can_undo.Changed -= on_can_undo_changed;
}
};
// local methods
IAlgoVM algo_vm_factory(CustomOrderDefinitionImmutable def) => OrderTypeVMFactory.create(new AlgoData(def, def.CustomCase.display_name(), OrderDataHelper.get_shallow_factory_key(def)), editing_permissions);
IAlgoVM get_algo_root(IAlgoVM owner_root) {
var key = new AlgoKey(owner_root.order_type);
if (algo_map.TryGetValue(key, out IAlgoVM vm)) {
vm.load_structure(owner_root.definition);
} else {
vm = algo_map.GetOrAdd(key, ot => algo_vm_factory(owner_root.definition));
}
return vm;
}
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_local_changed() {
_version.increment_version();
_edit_stack.push_distinct(_version.value);
_is_dirty.refresh();
_refresh_undo_redo();
}
}
public event Action can_redo_changed, can_undo_changed;
public event PropertyChange<IAlgoVM> algo_root_changed { add => _algo_root.Changed += value; remove => _algo_root.Changed -= value; }
public AlgoDefinitionVM owner { get; }
public AlgoKey owner_key => owner.key;
public AlgoKey key { get; }
public string name { get => _name.Value; set => _name.set(value); }
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
public bool is_dirty => _is_dirty.Value;
public bool can_redo => _can_redo.Value;
public bool can_undo => _can_undo.Value;
public bool is_default => _is_default.Value;
public bool is_favorite { get => _is_favorite.Value; set => _is_favorite.set(value); }
public IAlgoVM algo_root => _algo_root.Value;
public virtual bool is_custom_placeholder => false;
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
public bool is_expanded => false;
public bool is_persisted => key.mode == AlgoKey.Mode.Persisted;
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
public ICommand set_as_default_cmd { get; }
public IObservable<bool> observe_is_dirty() => _is_dirty.observe_current(true);
public void undo() => _restore(_edit_stack.try_undo);
public void redo() => _restore(_edit_stack.try_redo);
public void on_meta(bool is_default, bool is_favorite) {
_is_default.set(is_default);
_is_favorite.set(is_favorite);
}
public AlgoData create_def_data() =>
new AlgoData(algo_root.definition, name, key);
public AlgoTemplateData create() =>
create(owner.create_algo_def());
public AlgoTemplateData create(AlgoData record) {
if (!OrderDataHelper.EqualsHelpers.structure_equals(algo_root.definition, record.value))
throw new InvalidOperationException("AlgoTemplate's algo structure does not match that of its parent.");
return new AlgoTemplateData(algo_root.definition, name, key, record);
}
public AlgoDefinitionTemplateVM to_persisted(AlgoDefinitionVM persisted_owner, AlgoData template) =>
new AlgoDefinitionTemplateVM(persisted_owner, _model, template);
public void load_committed() => _load_from(_committed.name, _committed.definition, true);
public void load_from(AlgoTemplateData value) {
if (key.mode != AlgoKey.Mode.Persisted)
throw new InvalidOperationException("AlgoStructure has not been saved yet.");
else if (value.key != key)
throw new InvalidOperationException("Value id does not match this id.");
_load_from(value.name, value.value, true);
}
public bool Equals(AlgoDefinitionTemplateVM other) => other != null && key == other.key;
public override bool Equals(object obj) => Equals(obj as AlgoDefinitionTemplateVM);
public override int GetHashCode() => key.GetHashCode();
void _load_from(string new_name, CustomOrderDefinitionImmutable new_def, bool update_committed) {
using (_refresh_on_local_change.defer_execution()) {
name = new_name;
algo_root.load_from(new_def);
}
if (update_committed) {
_committed = _version.value;
_is_dirty.refresh();
}
}
void _refresh_undo_redo() {
_can_redo.refresh();
_can_undo.refresh();
}
void _restore(TryGetValue<Snapshot> try_get_snapshot) {
while (try_get_snapshot(out Snapshot next_state)) {
if (!_version.value.Equals(next_state)) {
_load_from(next_state.name, next_state.definition, false);
break;
}
}
}
Snapshot _snapshot() =>
new Snapshot(name, abbreviation, algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()), is_default, is_favorite);
struct Snapshot : IEquatable<Snapshot> {
public readonly string name, abbreviation;
public readonly CustomOrderDefinitionImmutable definition;
public readonly bool is_default, is_favorite;
public Snapshot(string name, string abbreviation, CustomOrderDefinitionImmutable definition, bool is_default, bool is_favorite) {
this.name = name;
this.abbreviation = abbreviation;
this.definition = definition;
this.is_default = is_default;
this.is_favorite = is_favorite;
}
public bool Equals(Snapshot other) =>
name == other.name &&
abbreviation == other.abbreviation &&
is_default == other.is_default &&
is_favorite == other.is_favorite &&
OrderDataHelper.EqualsHelpers.equals(definition, other.definition);
public override bool Equals(object obj) =>
EqualityComparer.equals_obj(this, obj);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
EqualityComparer.safe_aggregate_hash(name),
EqualityComparer.safe_aggregate_hash(abbreviation),
is_default.GetHashCode(),
is_favorite.GetHashCode(),
OrderDataHelper.EqualsHelpers.custom_order_def_compareri.GetHashCode(definition));
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
}
public class CustomTemplatePlaceholderVM : AlgoDefinitionTemplateVM {
const string _custom_name = "(Custom)";
public CustomTemplatePlaceholderVM(AlgoDefinitionVM owner, OrdersModel model, CustomOrderDefinitionImmutable definition)
: base(owner, model, new AlgoData(definition, _custom_name, new AlgoKey(Guid.NewGuid())), AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowEditValues) {
_edit_stack.reset(_snapshot());
}
public override bool is_custom_placeholder => true;
}
}
}
@@ -1,6 +1,5 @@
using bts;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.proto;
using bts.utils;
using mbts.collections;
@@ -17,6 +16,7 @@ using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows.Data;
using System.Windows.Media;
using VisualStateNames = mbts.ui.spark.AlgoStudioView.VisualStateNames;
@@ -25,8 +25,7 @@ namespace mbts.spark {
None = 0,
Exchange = 1,
Spreader = 1 << 1,
Attachable = 1 << 2,
DefaultPlaceholder = 1 << 3
DefaultPlaceholder = 1 << 2
}
class AlgoDefinitionVM : DisposableViewModelBase, IAlgoStudioContentVM, IAlgoSelector, IEquatable<AlgoDefinitionVM> {
@@ -34,123 +33,147 @@ namespace mbts.spark {
public static readonly CustomOrderDefinitionImmutable new_algo_def = OrderDataHelper.Defaults.CustomOrderDefinitionImmutable(CustomOrderDefinition.CustomOneofCase.Iceberg);
readonly OrdersModel _model;
readonly VisualStateModel _edit_pane_vs_model = new VisualStateModel(), _tree_view_vs_model = new VisualStateModel();
readonly ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey> _templates = new ObservableKeyedCollection<AlgoDefinitionTemplateVM, AlgoKey>(t => t.key);
readonly EditStack<Snapshot> _edit_stack = new EditStack<Snapshot>();
readonly VersionContainer<Snapshot> _version;
readonly Property<bool> _is_expanded, _is_selected;
readonly Property<IAlgoVM> _algo_root, _visible_algo_root;
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
readonly DelegateProperty<bool> _is_favorite, _is_dirty, _can_undo, _can_redo;
readonly Property<string> _name, _abbreviation;
readonly ObservableKeyedCollection<IAlgoVM, AlgoKey> _algo_roots = new ObservableKeyedCollection<IAlgoVM, AlgoKey>(vm => vm.id);
readonly ObservableAppendCollection<AlgoDefinitionTemplateVM> _templates_with_custom;
readonly DeferrableActionWrapper _refresh_on_local_changes;
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
readonly Property<AlgoFormats> _selected_format;
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
AlgoEditingPermissions _editing_permissions;
int? _favorite_index;
Brush _badge_fill;
Snapshot? _committed;
AlgoDefinitionMetadata _metadata;
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
bool _connected;
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) =>
new AlgoDefinitionVM(model, data, init_format, editing_permissions);
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
var rv = new AlgoDefinitionVM(model, data, init_format);
if (connect)
rv.connect();
return rv;
}
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
new AlgoDefinitionVM(model, AlgoData.create_unpersisted(def, name), init_format, (AlgoEditingPermissions)int.MaxValue);
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
// init core data
_model = model;
_permissions_mask.mask = init_permissions;
_permissions_mask.raw = init_permissions;
var resources = new CompositeDisposable();
if (!model.algo_model.ui_configuration.can_modify_definition_values)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
key = def_data.key;
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
_editing_permissions = _get_permissions();
_version = new VersionContainer<Snapshot>(_snapshot);
var conf = _model.algo_model.ui_configuration;
var resources = new CompositeDisposable();
// init backing props
_name = backing(nameof(name), def_data.name);
_abbreviation = backing(nameof(abbreviation), string.Empty);
_selected_format = backing(nameof(selected_format), init_format);
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
_is_favorite = backing(nameof(is_favorite), () => _favorite_index.HasValue);
_is_expanded = backing(nameof(is_expanded), true);
_is_selected = backing(nameof(is_selected), true);
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : AlgoData.from_custom_def(def_data.value);
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
// only composite/studio algos can change root
if (key.mode == AlgoKey.Mode.Factory || key.mode == AlgoKey.Mode.Dynamic) {
_algo_root.Changing += (oldval, newval) =>
Log.assert(newval != null && newval.id == key);
}
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
_can_redo.Changed += on_can_redo_changed;
_can_undo.Changed += on_can_undo_changed;
_refresh_on_local_changes = new DeferrableActionWrapper(on_local_changed);
// load initial state
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
// set hooks/subscriptions
using (_refresh_on_local_changes.supress_execution()) {
resources.AddRange(
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
_algo_root.observe_current(true).SubscribeEach(vm =>
vm == null ? null :
Disposable.Create(
vm.subscribe_is_dirty().Subscribe(v => {
if (!v) {
_version.increment_version();
_on_committed_changed();
}
}),
vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute()))),
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
Disposable.Create(() => {
_can_redo.Changed -= on_can_redo_changed;
_can_undo.Changed -= on_can_undo_changed;
}));
switch (key.mode) {
case AlgoKey.Mode.Persisted:
_metadata = new AlgoDefinitionMetadata(key);
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
break;
case AlgoKey.Mode.Factory:
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
break;
init_async();
async void init_async() {
// visible_algo_root is selected_template.algo_root if user can add templates, otherwise algo_root
// - this behavior is configurable in 'AlgoStudioOptions' in UI.proto (01/28/2018)
if (_model.algo_model.templates_enabled()) {
resources.Add(_selected_template.observe_current(true).Subscribe(t => _visible_algo_root.set(t?.algo_root)));
} else {
resources.Add(_algo_root.observe_current(true).Subscribe(_visible_algo_root.set));
}
var permission_src = _permissions_mask.observe_result();
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
.SubscribeEach(t => {
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(t.fmt);
var default_algo = AlgoKey.from_known_type(OrderDataHelper.get_default_algo(t.fmt));
return this.load_algo_data_recursive(algo_data, default_algo);
// init tree view item visual state
_tree_view_vs_model.set_state(VisualStateNames.TreeViewBadge.GroupName, conf.show_badges_in_tree_view ? VisualStateNames.TreeViewBadge.Active : VisualStateNames.TreeViewBadge.Inactive);
// set hooks/subscriptions
using (_refresh_on_local_changes.supress_execution()) {
resources.AddRange(
custom_badge_color.observe_nullable(true).Subscribe(v => Set(ref _badge_fill, (v ?? algo_root.safe_get_badge_color()).to_brush(), nameof(badge_fill))),
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
if (vm == null)
return Disposable.empty;
if (vm is ILazyAlgo la) {
bool synced_with_loaded = false;
return vm.subscribe_definition().Subscribe(_ => {
if (la.is_loaded) {
if (!synced_with_loaded) {
synced_with_loaded = true;
_version.increment_version();
_on_committed_changed();
} else {
_refresh_on_local_changes.execute();
}
} else {
synced_with_loaded = false;
}
});
}
return vm.subscribe_definition().Subscribe(_ => _refresh_on_local_changes.execute());
}),
ObservableAutoWrapper.connect_item_hooks(algo_roots, a => a as IDisposable ?? Disposable.empty, true),
ObservableAutoWrapper.connect_item_hooks(templates, t => new CompositeDisposable(_any_templates_dirty_monitor.join(t), t), true),
Disposable.Create(() => {
_can_redo.Changed -= on_can_redo_changed;
_can_undo.Changed -= on_can_undo_changed;
}));
switch (key.mode) {
case AlgoKey.Mode.Persisted:
_metadata = new AlgoDefinitionMetadata(key);
resources.Add(_template_update_buffer.OnDispatcher().Subscribe(_on_templates));
resources.Add(model.algo_model.metadata_service.subscribe_metadata(key).OnDispatcher().Subscribe(_on_metadata));
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, conf.can_modify_definition_name ? VisualStateNames.Name.Active : VisualStateNames.Name.CannotRenameDefinition);
break;
case AlgoKey.Mode.Factory:
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
break;
}
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
using (_refresh_on_local_changes.defer_execution()) {
var algo_data = model.factory.default_algos(f);
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
return this.load_algo_data_recursive(algo_data, default_algo);
}
return Disposable.empty;
}));
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
resources.Add(this.connect_data(permission_src));
resources.Add(this.connect_data(Observable.Return(model)));
resources.Add(_algo_root.Subscribe(_ => RaisePropertyChanged(nameof(IAlgoSelector.selected_algo), nameof(IAlgoParamVM.value), nameof(group))));
_dispose = resources;
algo_root.load_from(def_data.value);
_version.increment_version();
_edit_stack.reset(_version.value);
_on_committed_changed();
resources.Add(this.connect_data(Observable.Return(model)));
_dispose = resources;
on_editing_permissions(_get_permissions());
await algo_root.load_from_async(def_data.value);
_version.increment_version();
_edit_stack.reset(_version.value);
_on_committed_changed();
}
}
// local methods
void on_editing_permissions(AlgoEditingPermissions eps) {
_editing_permissions = eps;
foreach (var vm in algo_roots)
vm.set_permissions(eps);
}
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
void on_local_changed() {
@@ -171,10 +194,10 @@ namespace mbts.spark {
public string name { get => _name.Value; set => _name.set(value); }
public string abbreviation { get => _abbreviation.Value; set => _abbreviation.set(value); }
public ObservableCollection<IAlgoVM> algo_roots => _algo_roots;
public IDictionary<AlgoKey, IAlgoVM> algo_roots_index => _algo_roots;
public IReadOnlyObservableCollection<IAlgoVM> algos_view { get; }
public IAlgoVM algo_root { get => _algo_root.Value; set => _algo_root.set(value); }
public IAlgoVM visible_algo_root => _visible_algo_root.Value;
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
public int? favorite_index {
get => _favorite_index;
set {
@@ -187,63 +210,33 @@ namespace mbts.spark {
public bool is_selected { get => _is_selected.Value; set => _is_selected.set(value); }
public bool is_dirty => _is_dirty.Value;
public bool is_factory_definition => key.mode == AlgoKey.Mode.Factory;
public AlgoDisplayGroup group => key.display_group();
public bool can_redo => _can_redo.Value;
public bool can_undo => _can_undo.Value;
public EnabledValueVM<Color> custom_badge_color { get; } = new EnabledValueVM<Color>();
public ObservableCollection<ColorPaletteVM> custom_badge_color_palettes { get; } = new ObservableCollection<ColorPaletteVM>();
public Brush badge_fill => _badge_fill;
public ObservableCollection<AlgoDefinitionTemplateVM> templates => _templates;
/// <see cref="templates_with_custom"/> has the same elements as <see cref="templates"/>, plus the custom-placeholder-item, which is always at index 0.
/// The custom-placeholder supports legacy behavior in the ladder/ticket algo popup by allowing the user to manipulate the algo-structure (e.g. change an iceberg-limit order to iceberg-lean-cover-offset etc.).
/// However, the custom-placeholder item cannot be used to create/save algo-templates.
/// Algo-templates are just a set of values used to populate a concrete algo-structure; the 'structures' can be created in the AlgoStudio (<see cref="AlgoStudioWindow"/>).
/// If we were to strictly follow this paradigm, the user would no longer be able to freely change the algo-structure in the ladder/ticket popup (they would be forced to open up a new <see cref="AlgoStudioWindow"/>, and we know how much they hate more clicks!).
/// Some users might consider this a regression. The custom-placeholder-item addresses that concern.
public ObservableAppendCollection<AlgoDefinitionTemplateVM> templates_with_custom => _templates_with_custom;
public ListCollectionView templates_view { get; }
// format
public AlgoFormats selected_format { get => _selected_format.Value; set => _selected_format.set(value); }
public ObservableCollection<AlgoFormats> formats { get; } = new ObservableCollection<AlgoFormats> { AlgoFormats.Exchange, AlgoFormats.Spreader, AlgoFormats.Exchange | AlgoFormats.Spreader };
// visual states
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
// editing permissions
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
#region IAlgoSelector
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed {
add => algo_root_changed += value;
remove => algo_root_changed -= value;
}
event PropertyChange<IAlgoVM> IAlgoParamVM<IAlgoVM>.value_changed {
add => algo_root_changed += value;
remove => algo_root_changed -= value;
}
event PropertyChange<AlgoParamPermissions> IAlgoParamVM.permissions_changed { add { } remove { } }
event Action<bool> IAlgoParamVM.is_dirty_changed { add { } remove { } }
string IAlgoParamVM.id => key.ToString();
string IAlgoParamVM.display_name => "Algo";
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
ObservableKeyedCollection<IAlgoVM, AlgoKey> IAlgoSelector.algos => _algo_roots;
IAlgoVM IAlgoSelector.selected_algo {
get => algo_root;
set => algo_root = value;
}
IAlgoVM IAlgoParamVM<IAlgoVM>.value {
get => algo_root;
set => algo_root = value;
}
bool IAlgoParamVM.has_value => _algo_root != null;
bool IAlgoParamVM.is_enabled { get; set; } = true;
object IAlgoParamVM.value {
get => algo_root;
set => algo_root = (IAlgoVM)value;
}
AlgoParamPermissions _permissions = AlgoParamPermissions.ReadWrite;
AlgoParamPermissions IAlgoParamVM.permissions { get => _permissions; set => _permissions = value; }
IAlgoVM IAlgoSelector.selected_algo { get => algo_root; set => algo_root = value; }
void IAlgoSelector.load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) =>
_load_algo_data(algo_data, default_algo);
void IAlgoParamVM.revert() { }
void IAlgoParamVM.mark_committed() { }
IObservable<IAlgoVM> IAlgoSelector.subscribe_selected_algo() =>
this.observe_algo_root();
_load_algo_data(algo_data, default_algo);
#endregion
#region IObserver (algo data...)
@@ -255,7 +248,7 @@ namespace mbts.spark {
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
var orig_algo = algo_root?.id;
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
if (!try_select(orig_algo) && !try_select(default_algo))
algo_root = algo_roots.FirstOrDefault();
//
@@ -267,7 +260,24 @@ namespace mbts.spark {
}
}
// idempotent
public void prepend_custom_template() {
if (_templates_with_custom.appended_front.Count == 0)
_templates_with_custom.insert_front(new AlgoDefinitionTemplateVM.CustomTemplatePlaceholderVM(this, _model, algo_root.definition));
}
public AlgoDefinitionTemplateVM template_factory() =>
new AlgoDefinitionTemplateVM(this, _model, algo_root.create_def_data().with(name: new_template_name)) {
is_selected = true
};
public IObservable<bool> subscribe_is_definition_dirty() => _is_dirty.observe_current(true);
public IObservable<bool> subscribe_any_templates_dirty() => _any_templates_dirty_monitor;
public IObservable<bool> subscribe_is_definition_or_any_templates_dirty() =>
Observable.Merge(
subscribe_is_definition_dirty().Select(v => v || _any_templates_dirty_monitor.is_above_threshold),
subscribe_any_templates_dirty().Select(v => v || is_dirty))
.DistinctUntilChanged();
public void undo() => _restore(_edit_stack.try_undo);
public void redo() => _restore(_edit_stack.try_redo);
@@ -276,47 +286,17 @@ namespace mbts.spark {
_assign_from_data(_committed.Value);
_on_committed_changed();
}
public void load_def(CustomOrderDefinitionImmutable def) {
var key = AlgoKey.from_custom_def(def);
if (algo_root?.id == key) {
algo_root.load_from(def);
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
vm.load_from(def);
algo_root = vm;
} else {
algo_roots.Add(vm = _mk_algo_root(def));
algo_root = vm;
/// <summary>
/// Subscribes to the templates source for this definition if it has been persisted (subscription requires a persistant id); does nothing if this definition has not been persisted, or if it is already subscribed (idempotent).
/// </summary>
public void connect() {
if (!_connected) {
_connected = true;
concat_disposable(_model.algo_model.subscribe_definition_templates(key).OnDispatcher().Subscribe(_template_update_buffer.OnNext));
}
}
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> defs) {
/// don't load composite algos; we don't support recursive composites (<see cref="OrderUtils.actual(CustomOrderDefinitionImmutable)"/>)
if (defs is IList<CustomOrderDefinitionImmutable> def_list) {
def_list = def_list.WrapSelector(d => d.actual(), null);
} else {
def_list = defs.Select(d => d.actual()).ToList();
}
switch (def_list.Count) {
case 0: algo_root = null; break;
case 1: load_def(def_list[0]); break;
default:
var def = def_list[0];
var key = AlgoKey.from_custom_def(def);
if (algo_root?.id == key) {
algo_root.load_many(def_list);
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
vm.load_many(def_list);
algo_root = vm;
} else {
OrderTypeVMFactory.create(key, def.display_name(), def.CustomCase, is_expanded: true);
algo_roots.Add(vm = _mk_algo_root(def));
vm.load_many(def_list);
algo_root = vm;
}
break;
}
}
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
public void load(AlgoData data) {
_assert_can_load(data.key);
@@ -329,10 +309,10 @@ namespace mbts.spark {
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
public AlgoDefinitionVM to_persisted(AlgoData def) =>
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
new AlgoDefinitionVM(_model, def);
public AlgoData create_algo_def() =>
AlgoData.create(algo_root == null ? new CustomOrderDefinitionImmutable(new CustomOrderDefinition()) : algo_root.definition, name, key);
new AlgoData(algo_root.definition, name, key);
public AlgoDefinitionMetadata create_metadata() =>
_metadata.with(abbreviation: abbreviation, badge_color: new NullableColorProto(custom_badge_color.get_nullable()));
@@ -342,6 +322,16 @@ namespace mbts.spark {
public override int GetHashCode() => key.GetHashCode();
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
AlgoEditingPermissions _get_permissions() {
var permissions = AlgoEditingPermissions.None;
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
}
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
return permissions;
}
void _assert_can_load(AlgoKey data_key) {
if (key.mode != data_key.mode)
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
@@ -361,7 +351,7 @@ namespace mbts.spark {
void _assign_from_data(string name, CustomOrderDefinitionImmutable definition) {
this.name = name;
algo_root = _algo_roots.GetOrAdd(AlgoKey.from_custom_def(definition), _ => _mk_algo_root(definition));
algo_root = _algo_roots.GetOrAdd(OrderDataHelper.get_shallow_factory_key(definition), _ => _mk_algo_root(definition));
algo_root.load_from(definition);
}
@@ -369,19 +359,21 @@ namespace mbts.spark {
_committed = _version.value;
_is_dirty.refresh();
_refresh_undo_redo();
RaisePropertyChanged(nameof(committed_value), nameof(current_value));
}
public Snapshot? committed_value => _committed;
public Snapshot current_value => _version.value;
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
OrderTypeVMFactory.create(AlgoData.from_custom_def(definition), is_expanded: true);
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
void _on_metadata(AlgoDefinitionMetadata metadata) {
using (_refresh_on_local_changes.supress_execution()) {
_abbreviation.set(metadata.abbreviation);
custom_badge_color.set_nullable(metadata.badge_color);
foreach (var t in templates) {
if (t.key.mode == AlgoKey.Mode.Persisted)
t.on_meta(t.key == metadata.default_template, metadata.favorite_templates.Contains(t.key));
else
t.on_meta(false, false);
}
_version.increment_version();
if (_edit_stack.size == 1 && _edit_stack.pos == 1) {
// this is a hack to account for the metadata being part of the committed state,
@@ -394,6 +386,22 @@ namespace mbts.spark {
}
}
void _on_templates(TableUpdate<AlgoTemplateData, AlgoKey> table) {
foreach (var rec in table.removed)
_templates.Remove(rec.key);
foreach (var rec in table.changed)
_templates[rec.key].load_from(rec);
foreach (var rec in table.added) {
if (_templates.TryGetValue(rec.key, out AlgoDefinitionTemplateVM tmp)) {
tmp.load_from(rec);
} else {
tmp = new AlgoDefinitionTemplateVM(this, _model, new AlgoData(rec.value, rec.name, rec.key));
tmp.on_meta(tmp.key == _metadata.default_template, _metadata.favorite_templates.Contains(tmp.key));
_templates.Add(tmp);
}
}
}
void _refresh_undo_redo() {
_can_undo.refresh();
_can_redo.refresh();
@@ -455,17 +463,5 @@ namespace mbts.spark {
public static bool operator ==(Snapshot a, Snapshot b) => a.Equals(b);
public static bool operator !=(Snapshot a, Snapshot b) => !a.Equals(b);
}
struct SnapshotTemplateEqualityComparer : IEqualityComparer<Snapshot> {
public bool Equals(Snapshot x, Snapshot y) =>
x.abbreviation == y.abbreviation &&
x.custom_badge_color == y.custom_badge_color &&
x.format == y.format &&
x.data.key == y.data.key &&
x.data.name == y.data.name &&
x.data.value.Equals(y.data.value);
public int GetHashCode(Snapshot obj) => throw new NotImplementedException();
}
}
}
@@ -1,99 +0,0 @@
using bts.utils;
using mbts.collections;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace mbts.spark {
class AlgoParamRowVM : DisposableViewModelBase, DIYGrid.IRowVM, IEditableObject, ITemplateSelectorKey, IEquatable<AlgoParamRowVM> {
bool _is_enabled;
string _template_key;
ICommand _set_value_cmd;
public AlgoParamRowVM(IAlgoVM algo, IAlgoParamVM param, AlgoParamRowVM parent, string template_key = null) {
this.param = param;
this.algo = algo;
this.parent = parent;
_template_key = template_key;
param.permissions_changed += on_permissions_changed;
_dispose = Disposable.Create(() => param.permissions_changed -= on_permissions_changed);
void on_permissions_changed(AlgoParamPermissions oldval, AlgoParamPermissions newval) => RaisePropertyChanged(nameof(permissions));
}
public event Action invalidated;
public IAlgoVM algo { get; set; }
public IAlgoParamVM param { get; }
public AlgoParamPermissions permissions => param.permissions;
public AlgoParamRowVM parent { get; }
public int level => parent == null ? 0 : parent.level + 1;
public string path_disambiguation {
get {
if (parent == null)
return string.Empty;
string pd = parent.path_disambiguation;
if (parent.algo.order_type == bts.api.orderman.CustomOrderDefinition.CustomOneofCase.Cover) {
if (pd.Length > 0)
pd += ".";
pd += parent.param.display_name;
}
return pd;
}
}
public bool is_enabled { get => _is_enabled; set => Set(ref _is_enabled, value); }
public ICommand set_value_cmd {
get {
if (_set_value_cmd == null) {
var cmd = new DelegateCommand(v => {
var orig_val = param.value;
try {
param.value = v;
} catch (InvalidCastException) {
param.value = orig_val;
}
}, _ => param.permissions == AlgoParamPermissions.ReadWrite);
concated_disposable(param.subscribe_permissions().Subscribe(_ => cmd.RaiseCanExecuteChanged()));
_set_value_cmd = cmd;
}
return _set_value_cmd;
}
}
string ITemplateSelectorKey.key => _template_key;
public void raise_invalidated() => invalidated?.Invoke();
public bool Equals(AlgoParamRowVM other) =>
other != null &&
param.id == other.param.id &&
parent.safe_equals(other.parent);
public override bool Equals(object obj) =>
Equals(obj as AlgoParamRowVM);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(param.id.GetHashCode(), parent?.GetHashCode() ?? 0);
#region IEditableObject
object _committed_value;
void IEditableObject.BeginEdit() => _committed_value = param.value;
void IEditableObject.EndEdit() => _committed_value = null;
void IEditableObject.CancelEdit() {
if (_committed_value != null)
param.value = _committed_value;
_committed_value = null;
}
#endregion
}
class AlgoSelectorParamRowVM : AlgoParamRowVM {
readonly IAlgoSelector _selector;
public AlgoSelectorParamRowVM(IAlgoVM algo_vm, IAlgoSelector selector, AlgoParamRowVM parent) : base(algo_vm, selector, parent) =>
_selector = selector;
public ObservableKeyedCollection<IAlgoVM, AlgoKey> algos => _selector.algos;
}
}
@@ -1,5 +1,6 @@
using bts;
using bts.api.orderman;
using bts.collections;
using bts.utils;
using mbts.collections;
using mbts.ui;
@@ -13,7 +14,6 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
@@ -37,30 +37,29 @@ namespace mbts.spark {
});
}
class AlgoStudioVM : DisposableViewModelBase {
class AlgoStudioVM : DisposableViewModelBase, IHandleSelectedItemChanged {
static readonly AlgoFormats _studio_formats = AlgoFormats.Exchange;
readonly OrdersModel _model;
readonly Property<AlgoDefinitionVM> _selected_definition;
readonly Property<AlgoDefinitionTemplateVM> _selected_template;
readonly Property<IAlgoVM> _editable_content;
readonly DelegateProperty<string> _save_cmd_tooltip, _revert_cmd_tooltip;
readonly ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey> _definitions = new ObservableKeyedCollection<AlgoDefinitionVM, AlgoKey>(d => d.key);
readonly ListCollectionView<AlgoDefinitionVM> _definitions_view;
readonly TableUpdateBuffer<AlgoData, AlgoKey> _definitions_update_buffer;
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_dirty());
readonly ReactiveThresholdMonitor<AlgoDefinitionVM> _any_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionVM>(d => d.subscribe_is_definition_or_any_templates_dirty());
IReadOnlyDictionary<AlgoKey, AlgoDefinitionMetadata> _persisted_meta = new Dictionary<AlgoKey, AlgoDefinitionMetadata>();
public AlgoStudioVM(OrdersModel model) {
_model = model;
_selected_definition = backing<AlgoDefinitionVM>(nameof(selected_definition));
_editable_content = backing(nameof(editable_content), subscribe_definition().SelectManyInSeries(def => def?.observe_algo_root()).OnDispatcher());
_selected_template = backing<AlgoDefinitionTemplateVM>(nameof(selected_template));
_editable_content = backing(nameof(editable_content), observe_selected().SelectManyInSeries(tup => tup.template?.observe_algo_root() ?? tup.definition?.observe_algo_root()).RefCount().OnDispatcher());
_definitions_update_buffer = new TableUpdateBuffer<AlgoData, AlgoKey>(def => def.key);
_definitions_view = new ListCollectionView<AlgoDefinitionVM>(definitions) {
CustomSort = AlgoCollectionHelper.algo_definition_vm_comparer.to_non_generic()
};
definitions_view.Filter = d => !(d.key.mode == AlgoKey.Mode.Persisted && _persisted_meta.TryGetValue(d.key, out AlgoDefinitionMetadata meta) && meta.is_hidden);
// add/clone
add_definition_cmd = new DelegateCommand(add_definition);
clone_selected_definition_cmd = new DelegateCommand(clone_selected_definition);
// save
var save_cmd = new ObservantAsyncCommand<IAlgoStudioContentVM>(save_async);
this.save_cmd = save_cmd;
@@ -72,18 +71,13 @@ namespace mbts.spark {
//
_dispose = new CompositeDisposable(
_model.algo_model.definitions.Subscribe(_definitions_update_buffer),
_definitions_update_buffer.OnDispatcher().Subscribe(u => {
var orig_selected_def = selected_definition;
AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, _studio_formats));
if (selected_definition == null)
selected_definition = definitions.FirstOrDefault();
}),
_definitions_update_buffer.OnDispatcher().Subscribe(u => AlgoCollectionHelper.update_persisted_definitions(_definitions, u, d => AlgoDefinitionVM.create(model, d, true, _studio_formats))),
_model.algo_model.metadata_service.subscribe_metadata().OnDispatcher().Subscribe(meta => {
_persisted_meta = meta;
definitions_view.Refresh();
}),
ObservableAutoWrapper.connect(subscribe_definition().Select(def => def?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
subscribe_definition().SelectManyInSeries(def => def?.subscribe_is_definition_dirty() ?? Observable.Return(false)).OnDispatcher()
ObservableAutoWrapper.connect(observe_selected().Select(t => t.template?.edit_pane_visual_states ?? t.definition?.edit_pane_visual_states ?? new ObservableCollection<string>()), content_visual_states),
observe_selected().SelectManyInSeries(t => t.template?.observe_is_dirty() ?? t.definition?.subscribe_is_definition_or_any_templates_dirty() ?? Observable.Return(false)).RefCount().OnDispatcher()
.Subscribe(new CompositeObserver<bool>(save_cmd, revert_cmd) { val => {
_save_cmd_tooltip.refresh();
_revert_cmd_tooltip.refresh();
@@ -91,64 +85,77 @@ namespace mbts.spark {
ObservableAutoWrapper.connect_item_hooks(definitions, d => new CompositeDisposable(_any_dirty_monitor.join(d), d), true));
// local methods
string get_revert_tooltip() {
var def = selected_definition;
return def != null && def.key.mode == AlgoKey.Mode.Persisted ?
(def.is_dirty ? $"Revert changes to '{def.name}' templates" : "No changes to revert") :
null;
}
async Task save_async(IAlgoStudioContentVM content) {
if (content is AlgoDefinitionVM def)
await save_definition(def);
}
string get_save_tooltip() {
var def = selected_definition;
if (def != null) {
if (content_source is AlgoDefinitionTemplateVM tmp) {
switch (tmp.key.mode) {
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Revert changes to '{tmp.name}'" : "No changes to revert";
}
} else if (content_source is AlgoDefinitionVM def) {
switch (def.key.mode) {
case AlgoKey.Mode.Unpersisted: return def.is_dirty ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
case AlgoKey.Mode.Persisted: return def.is_dirty ? $"Save changes to '{def.name}' templates" : "No changes to save";
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Revert changes to '{def.name}' templates" : "No changes to revert";
}
}
return null;
}
void add_definition() =>
add_definition_common(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
void clone_selected_definition() {
if (selected_definition == null || selected_definition.algo_root == null) {
add_definition();
} else {
add_definition_common(
selected_definition.algo_root.definition,
string.IsNullOrEmpty(selected_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{selected_definition.name} - Copy");
async Task save_async(IAlgoStudioContentVM content) {
if (content is AlgoDefinitionTemplateVM tmp) {
await save_template(tmp);
} else if (content is AlgoDefinitionVM def) {
await save_definition(def);
}
}
void add_definition_common(CustomOrderDefinitionImmutable def, string name) {
var new_def = AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats);
_definitions.Add(new_def);
selected_definition = new_def;
request_view_focus_name?.Invoke(new_def);
string get_save_tooltip() {
if (content_source is AlgoDefinitionTemplateVM tmp) {
switch (tmp.key.mode) {
case AlgoKey.Mode.Unpersisted: return tmp.is_dirty ? $"Save '{tmp.name}'" : $"Cannot Save '{tmp.name}'";
case AlgoKey.Mode.Persisted: return tmp.is_dirty ? $"Save changes to '{tmp.name}'" : "No changes to save";
}
} else if (content_source is AlgoDefinitionVM def) {
switch (def.key.mode) {
case AlgoKey.Mode.Unpersisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save '{def.name}'" : $"Cannot Save '{def.name}'";
case AlgoKey.Mode.Persisted: return def.is_dirty || def.templates.Any(t => t.is_dirty) ? $"Save changes to '{def.name}' templates" : "No changes to save";
}
}
return null;
}
}
public event Action<AlgoDefinitionVM> request_view_focus_name;
event Action<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> _selected_changed;
public ObservableCollection<AlgoDefinitionVM> definitions => _definitions;
public ObservableCollection<string> content_visual_states { get; } = new ObservableCollection<string>();
public AlgoDefinitionVM selected_definition { get => _selected_definition.Value; set => _selected_definition.set(value); }
public AlgoDefinitionTemplateVM selected_template { get => _selected_template.Value; set => _selected_template.set(value); }
public IAlgoStudioContentVM content_source => (IAlgoStudioContentVM)selected_template ?? selected_definition;
public IAlgoVM editable_content => _editable_content.Value;
public ICollectionView<AlgoDefinitionVM> definitions_view => _definitions_view;
public ICommand add_definition_cmd { get; }
public ICommand clone_selected_definition_cmd { get; }
public ICommand save_cmd { get; }
public ICommand revert_cmd { get; }
public string save_cmd_tooltip => _save_cmd_tooltip.Value;
public string revert_cmd_tooltip => _revert_cmd_tooltip.Value;
public void add_new_definition() =>
_add_new_definition(AlgoDefinitionVM.new_algo_def, AlgoDefinitionVM.new_algo_name);
public void add_new_definition_clone(AlgoDefinitionVM src_definition) {
if (src_definition == null || src_definition.algo_root == null) {
add_new_definition();
} else {
_add_new_definition(src_definition.algo_root.definition, string.IsNullOrEmpty(src_definition.name) ? AlgoDefinitionVM.new_algo_name : $"{src_definition.name} - Copy");
}
}
void _add_new_definition(CustomOrderDefinitionImmutable def, string name) =>
_definitions.Add(AlgoDefinitionVM.create_placeholder(_model, def, name, _studio_formats));
public StartupData create() => new StartupData();
public void load_from(StartupData state) { }
public IObservable<AlgoDefinitionVM> subscribe_definition() => _selected_definition.observe_current(true);
public IObservable<AlgoDefinitionTemplateVM> subscribe_template() => _selected_template.observe_current(true);
public IObservable<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)> observe_selected() =>
Observable.Create<(AlgoDefinitionVM definition, AlgoDefinitionTemplateVM template)>(o => {
_selected_changed += o.OnNext;
return Disposable.Create(() => _selected_changed -= o.OnNext);
});
public IObservable<bool> subscribe_dirty() => _any_dirty_monitor;
public async Task remove_definition(AlgoDefinitionVM definition) {
@@ -156,43 +163,87 @@ namespace mbts.spark {
await _model.algo_model.remove_definition(definition.key);
} else {
// This is a new definition, which hasn't been saved/persisted yet. No need to call the server.
definition.templates.Clear();
definitions.Remove(definition);
}
}
public Task save_definition(AlgoDefinitionVM definition) =>
_save(new[] { definition });
public Task save_all() =>
_save(definitions);
async Task _save(IEnumerable<AlgoDefinitionVM> definitions) {
var orig_selected = selected_definition;
var orig_selected_index = orig_selected == null ? -1 : this.definitions.IndexOf(orig_selected);
using (_definitions_update_buffer.buffer_updates()) {
var def_list = definitions as IList<AlgoDefinitionVM> ?? definitions.ToList();
await Task.WhenAll(definitions.Select(async d => {
var def = await _model.algo_model.save_definition(d.create_algo_def());
if (def.HasValue) {
// don't forget to save metadata
await _model.algo_model.metadata_service.update_metadata(def.Value.key, _ => d.create_metadata().with(def.Value.key));
}
return def;
})).ContinueOnDispatcher(t => {
if (t.Status != TaskStatus.RanToCompletion) {
Log.Ui.debug_fatal($"Algo Studio error saving definitions: unexpected Task.Status '{t.Status}'.");
return;
}
var persisted_data = t.Result;
Log.assert(persisted_data.Length == def_list.Count);
for (int i = 0; i < persisted_data.Length; i++) {
if (!def_list[i].key.is_persisted && persisted_data[i].HasValue)
_definitions.Replace(def_list[i].key, def_list[i].to_persisted(persisted_data[i].Value));
}
if (selected_definition != orig_selected && orig_selected_index >= 0 && orig_selected_index < this.definitions.Count)
selected_definition = this.definitions[orig_selected_index];
});
public async Task remove_template(AlgoDefinitionTemplateVM template) {
if (template.key.mode == AlgoKey.Mode.Persisted) {
await _model.algo_model.remove_template(template.key);
} else {
// This is a new template, which hasn't been saved/persisted yet. No need to call the server.
template.owner.templates.Remove(template);
}
}
public async Task save_definition(AlgoDefinitionVM definition) {
using (_save_op()) {
var newly_persisted_keys = await AlgoPersistenceHelper.persist_definition(_model, _definitions, definition);
AlgoKey pkey;
switch (definition.key.mode) {
case AlgoKey.Mode.Unpersisted:
if (!newly_persisted_keys.TryGetValue(definition.key, out pkey))
return;
break;
case AlgoKey.Mode.Persisted:
pkey = definition.key;
break;
default: return;
}
await _model.algo_model.metadata_service.update_metadata(pkey, _ => definition.create_metadata().with(id: pkey));
}
}
public async Task save_template(AlgoDefinitionTemplateVM template) {
using (_save_op())
await AlgoPersistenceHelper.persist_template(_model, _definitions, template);
}
public async Task save_all() =>
await _save(definitions.WrapSelector(def => (def, def.templates.AsList()), t => t.def));
async Task _save(IList<(AlgoDefinitionVM definition, IList<AlgoDefinitionTemplateVM> templates)> definition_templates) {
using (_save_op())
await AlgoPersistenceHelper.persist(_model, definition_templates);
}
IDisposable _save_op() {
int? def_index = selected_definition == null ? (int?)null : _definitions.IndexOf(selected_definition);
int? tmp_index = selected_template == null ? (int?)null : selected_definition.templates.IndexOf(selected_template);
return new CompositeDisposable(_definitions_update_buffer.buffer_updates()) {
() => {
if (def_index.HasValue) {
var def = _definitions[def_index.Value];
if (tmp_index.HasValue) {
var tmp = def.templates[tmp_index.Value];
tmp.is_selected = true;
} else def.is_selected = true;
}
}
};
}
/// Despite the existence of these properties: <see cref="AlgoDefinitionVM.is_selected"/>, and <see cref="AlgoDefinitionTemplateVM.is_selected"/>,
/// <see cref="AlgoStudioVM"/> still implements <see cref="IHandleSelectedItemChanged"/> to update the properties that depend on the <see cref="System.Windows.Controls.TreeView.SelectedItem"/>.
/// [John]I am going to Uganda in 3 days (today is 1/3/2019), and decided against removing this legacy-dependency at the last minute.
/// Fwiw, <see cref="AlgoDefinitionVM.is_selected"/> and <see cref="AlgoDefinitionTemplateVM.is_selected"/> are bound to <see cref="System.Windows.Controls.TreeViewItem.IsSelected"/>.
/// They serve to prevent a quirky default behavior in TreeView: double clicking any TreeViewItem collapses the entire TreeView.
/// This behavior wasn't just quirky either, it actually conflicted with the double-click-to-set-template-as-default behavior.
/// (for details, <see cref="mbts.ui.spark.AlgoStudioView.EnableToggleDefaultOnDoubleClickProperty"/>)
void IHandleSelectedItemChanged.on_selected_item_changed(object old_value, object new_value) {
if (new_value is AlgoDefinitionVM def) {
selected_definition = def;
selected_template = null;
} else if (new_value is AlgoDefinitionTemplateVM templ) {
selected_definition = templ.owner;
selected_template = templ;
} else {
selected_definition = null;
selected_template = null;
}
_selected_changed?.Invoke((selected_definition, selected_template));
RaisePropertyChanged(nameof(content_source));
}
}
}
@@ -1,214 +0,0 @@
using bts.api;
using bts.api.orderman;
using bts.collections;
using bts.spark;
using mbts.spark.utilities;
using mbts.ui;
using mbts.ui.controls;
using mbts.ui.shell;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
namespace mbts.spark {
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
IObserver<SecurityMD?>,
IObserver<SecurityImmutable?>,
IObserver<OrdersModel>,
IObserver<AlgoEditingPermissions> {
static readonly HashSet<string>
_algo_root_echo_props = new HashSet<string> {
nameof(is_expanded),
nameof(editing_permissions),
nameof(definition),
nameof(display_name)
};
static readonly IReadOnlyDictionary<string, string>
_value_echo_props = new Dictionary<string, string> {
{ nameof(AlgoDefinitionVM.name), nameof(display_name) },
{ nameof(AlgoDefinitionVM.abbreviation), nameof(abbreviation) },
{ nameof(AlgoDefinitionVM.badge_fill), nameof(meta) }
};
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
readonly ObservableAggregateCollection<IAlgoParamVM> _algo_params = new ObservableAggregateCollection<IAlgoParamVM>();
readonly DisposableContainer _value_hooks = new DisposableContainer();
Action _definition_changed = () => { };
_ObservedData? _data_cache;
AlgoDefinitionVM _value;
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
value = algo_definition_vm;
_dispose = new CompositeDisposable(_value_hooks) {
_child_algo_selectors.clear_collections,
_algo_params.clear_collections,
() => _value?.Dispose()
};
}
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
public AlgoDefinitionVM value {
get => _value;
set {
if (_value != value) {
_value?.Dispose();
_value = value;
_value_hooks.replace(_set_hooks(value));
RaisePropertyChanged(nameof(value));
}
}
}
public AlgoKey id => _value.key;
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
public OrderMetadata meta {
get {
if (_value != null) {
if (_value.custom_badge_color.enabled)
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
else if (_value.algo_root != null)
return _value.algo_root.meta;
}
return OrderDataHelper.get_metadata(order_type);
}
}
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
public bool can_expand => _algo_root?.can_expand ?? false;
public bool is_expanded {
get => _value.algo_root?.is_expanded ?? false;
set {
if (_value != null)
_value.is_expanded = value;
}
}
public string display_name => _value?.name ?? string.Empty;
public string abbreviation => _value?.abbreviation;
public AlgoDisplayGroup group => _value?.group ?? AlgoDisplayGroup.BTS;
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
[EditorBrowsable(EditorBrowsableState.Never)]
public ObservableCollection<IAlgoSelector> child_algo_selectors_view {
get {
_value?.algo_root?.ensure_init();
return _child_algo_selectors;
}
}
public IReadOnlyObservableCollection<IAlgoParamVM> algo_params => _algo_params;
[EditorBrowsable(EditorBrowsableState.Never)]
public dynamic children_vm => _algo_root?.children_vm;
IAlgoVM _algo_root => _value?.algo_root;
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> values) => _algo_root?.load_many(values);
void IAlgoVM.ensure_init() => _value?.algo_root?.ensure_init();
IDisposable _set_hooks(AlgoDefinitionVM vm) {
var algo_root_subs = new DisposableContainer();
vm.PropertyChanged += on_value_prop_changed;
return new CompositeDisposable(
vm.observe_algo_root().Subscribe(r => {
algo_root_subs.replace(null);
_child_algo_selectors.clear_collections();
_algo_params.clear_collections();
if (r != null) {
if (_data_cache.HasValue) {
_data_cache.Value.forward_to(r);
}
_child_algo_selectors.add_collection(r.child_algo_selectors);
_algo_params.add_collection(r.algo_params);
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
algo_root_subs.replace(new CompositeDisposable(
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
});
}
RaisePropertyChanged(nameof(group));
}),
algo_root_subs) {
() => vm.PropertyChanged -= on_value_prop_changed
};
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_algo_root_echo_props.Contains(e.PropertyName))
RaisePropertyChanged(e.PropertyName);
}
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
RaisePropertyChanged(wrapper_prop);
}
}
// permissions
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
}
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
// model
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
}
void IObserver<OrdersModel>.OnError(Exception error) { }
void IObserver<OrdersModel>.OnCompleted() { }
// security
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
}
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
void IObserver<SecurityImmutable?>.OnCompleted() { }
// security md
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
}
void IObserver<SecurityMD?>.OnError(Exception error) { }
void IObserver<SecurityMD?>.OnCompleted() { }
struct _ObservedData {
public readonly AlgoEditingPermissions permissions;
public readonly OrdersModel model;
public readonly SecurityImmutable? security;
public readonly SecurityMD? security_md;
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
this.permissions = permissions;
this.model = model;
this.security = security;
this.security_md = security_md;
}
public void forward_to(IAlgoVM recipient) {
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
if (model != null)
(recipient as IObserver<OrdersModel>)?.OnNext(model);
if (security_md.HasValue)
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
if (security.HasValue)
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
}
public _ObservedData with(AlgoEditingPermissions? permissions) =>
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
public _ObservedData with(OrdersModel model) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityImmutable? security) =>
new _ObservedData(permissions, model, security, security_md);
public _ObservedData with(SecurityMD? security_md) =>
new _ObservedData(permissions, model, security, security_md);
}
}
}
@@ -1,625 +0,0 @@
<UserControl
x:Name="me"
x:Class="mbts.ui.spark.AlgoEditorGridView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Resources/DataGrid.xaml"/>
<ResourceDictionary Source="/Modules/Spreader/ExecutionStrategyEditor.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="VerticalPush1">4</sys:Double>
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<sys:String x:Key="NonUniformValueText">-</sys:String>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
<Thickness x:Key="ComboBox.CellPadding" Top="2" Bottom="2"/>
<!--Converters-->
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
<cv:TimeConverter x:Key="TimeConverter"/>
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" Multiplier="18" UnitType="Pixel"/>
<cv:EnumItemsSourceConverter x:Key="EnumItemsSourceConverter"/>
<cv:NullSubstitutionConverterWrapper x:Key="SimpleValueTextConverter" ValueForNull="{StaticResource NonUniformValueText}"/>
<cv:NullSubstitutionConverterWrapper x:Key="NullableEnumDescriptionConverter" Converter="{StaticResource EnumDescriptionConverter}" ValueForNull="{StaticResource NonUniformValueText}"/>
<cv:NullSubstitutionConverterWrapper x:Key="QuantityMeasureTextConverter" Converter="{x:Static cv:ValueConverters.QuantityMeasureTextConverter}" ValueForNull="{StaticResource NonUniformValueText}"/>
<cv:DoubleTextConverter x:Key="DoubleTextConverter" NaNText="{StaticResource NonUniformValueText}"/>
<cv:ValueSubstitutionConverterWrapper x:Key="StringTextConverter">
<cv:KeyValue Key="{x:Null}" Value="{StaticResource NonUniformValueText}"/>
<cv:KeyValue Key="{x:Static sys:String.Empty}" Value="{StaticResource NonUniformValueText}"/>
</cv:ValueSubstitutionConverterWrapper>
<cv:EnumHasFlagsConverter x:Key="ParamPermissionsToIsReadOnlyConverter">
<sp:AlgoParamPermissions>None</sp:AlgoParamPermissions>
<sp:AlgoParamPermissions>ReadOnly</sp:AlgoParamPermissions>
</cv:EnumHasFlagsConverter>
<!--Default Styles-->
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
</Style>
<!--Common Templates-->
<DataTemplate x:Key="AlgoVMItemTemplate">
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="ExecutionStrategyTemplate">
<TextBlock>
<Hyperlink Click="_on_execution_strategy_toggle_click">
Show Strategy Params
</Hyperlink>
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="IndentedParamNameAndBadgeCellTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding level, Converter={StaticResource GridLengthPixelConverter}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<usp:OrderTypeItem Grid.Column="1" Focusable="False" IsTabStop="False" BadgeFill="{Binding Path='(sp:AlgoParamRowVM.algo).(sp:IAlgoVM.meta).Color', Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding Path='param.(sp:IAlgoParamVM.display_name)'}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="NullableEnumDescriptionTemplate">
<TextBlock Text="{Binding Converter={StaticResource NullableEnumDescriptionConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="ParamNameAndDisambiguationCellTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="SimpleValueTextTemplate">
<TextBlock x:Name="value_text" Text="{Binding param.value, Converter={StaticResource SimpleValueTextConverter}}"/>
</DataTemplate>
<!--Param Name Templates-->
<u:KeyedTemplateSelector x:Key="ParamNameTemplateSelector" Default="{StaticResource ParamNameAndDisambiguationCellTemplate}">
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col1"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<TextBlock x:Name="bid_text" Grid.Column="1" HorizontalAlignment="Center">
<TextBlock.ToolTip>
<TextBlock>
Best Bid Price (click to assign)
<LineBreak/>
<Italic>
Hold a number key to cross the market by 'n' ticks.
</Italic>
</TextBlock>
</TextBlock.ToolTip>
<Hyperlink
Focusable="False"
Foreground="{u:BTSColor MarketData.bid}"
Command="{Binding DataContext.payup_bid_cmd, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
PreviewMouseLeftButtonDown="{x:Static usp:AlgoEditorGridView.PayupTicksMouseButtonHandler}"
>
<Run>
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="DataContext.bbo.bid" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
</MultiBinding>
</Run.Text>
</Run>
</Hyperlink>
</TextBlock>
<TextBlock x:Name="ask_text" Grid.Column="2" HorizontalAlignment="Center" Margin="4,0">
<TextBlock.ToolTip>
<TextBlock>
Best Ask Price (click to assign)
<LineBreak/>
<Italic>
Hold a number key to cross the market by 'n' ticks.
</Italic>
</TextBlock>
</TextBlock.ToolTip>
<Hyperlink
Focusable="False"
Foreground="{u:BTSColor MarketData.ask}"
Command="{Binding DataContext.payup_ask_cmd, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
PreviewMouseLeftButtonDown="{x:Static usp:AlgoEditorGridView.PayupTicksMouseButtonHandler}"
>
<Run>
<Run.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="DataContext.bbo.ask" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
</MultiBinding>
</Run.Text>
</Run>
</Hyperlink>
</TextBlock>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.security, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{x:Null}">
<Setter TargetName="bid_text" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="ask_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding param.permissions, Converter={StaticResource ParamPermissionsToIsReadOnlyConverter}}" Value="{u:True}">
<Setter TargetName="bid_text" Property="ToolTip" Value="Best Bid Price"/>
<Setter TargetName="ask_text" Property="ToolTip" Value="Best Ask Price"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Size}">
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col0"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="price_size_col1"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="disambig_text" Margin="0,0,2,0" FontWeight="Light" Text="{Binding path_disambiguation, StringFormat='[{0}]', Mode=OneWay}"/>
<TextBlock Text="{Binding Path='param.(sp:IAlgoParamVM.display_name)', Mode=OneWay}"/>
<TextBlock Text="*" Visibility="{Binding param.is_dirty, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</StackPanel>
<TextBlock
x:Name="bid_text"
Grid.Column="1"
HorizontalAlignment="Center"
Foreground="{u:BTSColor MarketData.bid}"
Text="{Binding DataContext.bbo.bid_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Bid Size"
/>
<TextBlock
x:Name="ask_text"
Grid.Column="2"
HorizontalAlignment="Center"
Margin="4,0"
Foreground="{u:BTSColor MarketData.ask}"
Text="{Binding DataContext.bbo.ask_size, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
ToolTip="Best Ask Size"
/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding path_disambiguation}" Value="{x:Static sys:String.Empty}">
<Setter TargetName="disambig_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.security, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{x:Null}">
<Setter TargetName="bid_text" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="ask_text" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</u:KeyedTemplateSelector>
<!--Read Templates-->
<usp:AlgoParamTemplateSelector
x:Key="ParamTemplateSelector"
IntTemplate="{StaticResource SimpleValueTextTemplate}"
NullableIntTemplate="{StaticResource SimpleValueTextTemplate}"
UIntTemplate="{StaticResource SimpleValueTextTemplate}"
NullableUIntTemplate="{StaticResource SimpleValueTextTemplate}"
ExecutionStrategyTemplate="{StaticResource ExecutionStrategyTemplate}"
>
<usp:AlgoParamTemplateSelector.KeyedTemplates>
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
<Binding Path="param.value"/>
<Binding Path="DataContext.spec" RelativeSource="{RelativeSource AncestorType=usp:AlgoEditorGridView}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
<DataTemplate>
<CheckBox Focusable="False" VerticalAlignment="Center" IsChecked="{Binding param.value, Mode=TwoWay}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.BoolTemplate>
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource DoubleTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
<usp:AlgoParamTemplateSelector.EnumTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource EnumDescriptionConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.EnumTemplate>
<!--Nullable Enum-->
<usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource NullableEnumDescriptionConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<!--Order Def-->
<usp:AlgoParamTemplateSelector.OrderDefTemplate>
<DataTemplate>
<usp:OrderTypeItem BadgeFill="{Binding Path='(sp:AlgoParamRowVM.param).(sp:IAlgoSelector.selected_algo).meta.Color', Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding Path='(sp:AlgoParamRowVM.param).(sp:IAlgoSelector.selected_algo).(sp:IAlgoVM.display_name)'}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.OrderDefTemplate>
<!--Quantity Measure-->
<usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource QuantityMeasureTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<!--String-->
<usp:AlgoParamTemplateSelector.StringTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource StringTextConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.StringTemplate>
<!--Timestamp-->
<usp:AlgoParamTemplateSelector.TimestampTemplate>
<DataTemplate>
<TextBlock Text="{Binding param.value, Converter={StaticResource TimeConverter}}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.TimestampTemplate>
</usp:AlgoParamTemplateSelector>
<!--Write Templates-->
<usp:AlgoParamTemplateSelector x:Key="ParamEditingTemplateSelector" ExecutionStrategyTemplate="{StaticResource ExecutionStrategyTemplate}">
<usp:AlgoParamTemplateSelector.KeyedTemplates>
<u:TemplateSelectorKeyValue Key="{x:Static usp:AlgoParamTemplateKeys.Price}">
<DataTemplate>
<usp:PriceEntryControl
Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
ContractSpec="{Binding DataContext.spec, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
Focusable="False"
IsTabStop="False"
ArrowKeyTickModifiers="Ctrl"
>
<usp:PriceEntryControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</usp:PriceEntryControl.CommandBindings>
<usp:PriceEntryControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</usp:PriceEntryControl.InputBindings>
</usp:PriceEntryControl>
</DataTemplate>
</u:TemplateSelectorKeyValue>
</usp:AlgoParamTemplateSelector.KeyedTemplates>
<!--Bool-->
<usp:AlgoParamTemplateSelector.BoolTemplate>
<DataTemplate>
<CheckBox Focusable="False" VerticalAlignment="Center" IsChecked="{Binding param.value, Mode=TwoWay}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.BoolTemplate>
<!--Double-->
<usp:AlgoParamTemplateSelector.DoubleTemplate>
<DataTemplate>
<c:TickUpDownDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownDouble.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownDouble.CommandBindings>
<c:TickUpDownDouble.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownDouble.InputBindings>
</c:TickUpDownDouble>
</DataTemplate>
</usp:AlgoParamTemplateSelector.DoubleTemplate>
<!--Enum-->
<usp:AlgoParamTemplateSelector.EnumTemplate>
<DataTemplate>
<ComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.EnumTemplate>
<!--Int-->
<usp:AlgoParamTemplateSelector.IntTemplate>
<DataTemplate>
<c:TickUpDownInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownInt.CommandBindings>
<c:TickUpDownInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownInt.InputBindings>
</c:TickUpDownInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.IntTemplate>
<!--Nullable Bool-->
<usp:AlgoParamTemplateSelector.NullableBoolTemplate>
<DataTemplate>
<CheckBox Focusable="False" VerticalAlignment="Center" IsThreeState="True" IsChecked="{Binding param.value, Mode=TwoWay}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableBoolTemplate>
<!--Nullable Enum-->
<usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<DataTemplate>
<ComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding Converter={x:Static usp:AlgoEditorGridView.NullableEnumParamRowItemsSourceConverter}}"
ItemTemplate="{StaticResource NullableEnumDescriptionTemplate}"
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableEnumTemplate>
<!--Nullable Int-->
<usp:AlgoParamTemplateSelector.NullableIntTemplate>
<DataTemplate>
<c:TickUpDownNullableInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableInt.CommandBindings>
<c:TickUpDownNullableInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableInt.InputBindings>
</c:TickUpDownNullableInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableIntTemplate>
<!--Nullable UInt-->
<usp:AlgoParamTemplateSelector.NullableUIntTemplate>
<DataTemplate>
<c:TickUpDownNullableUInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<c:TickUpDownNullableUInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownNullableUInt.CommandBindings>
<c:TickUpDownNullableUInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownNullableUInt.InputBindings>
</c:TickUpDownNullableUInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.NullableUIntTemplate>
<!--Order Def-->
<usp:AlgoParamTemplateSelector.OrderDefTemplate>
<DataTemplate>
<ComboBox HorizontalAlignment="Stretch" DataContext="{Binding param}" ItemsSource="{Binding Path='(sp:IAlgoSelector.algos)'}" ItemTemplate="{StaticResource AlgoVMItemTemplate}" SelectedItem="{Binding Path='(sp:IAlgoSelector.selected_algo)', UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</usp:AlgoParamTemplateSelector.OrderDefTemplate>
<!--QuantityMeasure-->
<usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<DataTemplate>
<uag:QuantityMeasureControl Focusable="False" HorizontalAlignment="Stretch" QuantityMeasure="{Binding param.value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
<uag:QuantityMeasureControl.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</uag:QuantityMeasureControl.CommandBindings>
<uag:QuantityMeasureControl.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</uag:QuantityMeasureControl.InputBindings>
</uag:QuantityMeasureControl>
</DataTemplate>
</usp:AlgoParamTemplateSelector.QuantityMeasureTemplate>
<!--Timestamp-->
<usp:AlgoParamTemplateSelector.TimestampTemplate>
<DataTemplate>
<Grid DataContext="{Binding algo}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox
x:Name="PART_StartMode_ComboBox"
HorizontalAlignment="Stretch"
ItemsSource="{Binding start_modes}"
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
SelectedItem="{Binding start_mode}"
/>
<c:DateTimePicker
x:Name="PART_AbsoluteStart_DateTimePicker"
Grid.Column="2"
TimeTuple="{Binding absolute_start}"
Visibility="{Binding show_absolute_start, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
<c:TimeSpanControl
x:Name="PART_RelativeStart_TimeSpanControl"
Grid.Column="2"
TimeSpan="{Binding relative_start, Mode=TwoWay}"
Visibility="{Binding show_relative_start, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</Grid>
</DataTemplate>
</usp:AlgoParamTemplateSelector.TimestampTemplate>
<!--UInt-->
<usp:AlgoParamTemplateSelector.UIntTemplate>
<DataTemplate>
<c:TickUpDownUInt Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.value, UpdateSourceTrigger=PropertyChanged}">
<c:TickUpDownUInt.CommandBindings>
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
</c:TickUpDownUInt.CommandBindings>
<c:TickUpDownUInt.InputBindings>
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
</c:TickUpDownUInt.InputBindings>
</c:TickUpDownUInt>
</DataTemplate>
</usp:AlgoParamTemplateSelector.UIntTemplate>
</usp:AlgoParamTemplateSelector>
<usp:AlgoEditorGridRowStyleSelector x:Key="RowStyleSelector">
<usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding param.is_dirty}" Value="{u:True}"/>
<Condition Binding="{Binding AnimateDirty, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}" Value="{u:True}"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#8098FB98"/>
<Setter Property="ToolTip" Value="Value has been modified."/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
<usp:AlgoEditorGridRowStyleSelector.SpreaderStyle>
<Style TargetType="DataGridRow">
<Setter Property="DetailsVisibility" Value="Collapsed"/>
<Setter Property="DetailsTemplate">
<Setter.Value>
<DataTemplate>
<AdornerDecorator>
<usp:ExecutionStrategyEditor
DataContext="{Binding algo.strategy_editor}"
HorizontalAlignment="Stretch"
Margin="6"
SyntheticName="{Binding name}"
TickFactors="{Binding tick_factors}"
IsNew="{Binding is_new}"
CanUserAddNewLeg="{Binding can_add_leg}"
IsNewLegPopupOpen="{Binding is_new_leg_popup_open}"
PriceCalc="{Binding price_calc}"
ParameterRows="{Binding rows}"
LegTuples="{Binding leg_tuples}"
RemoveLegCommand="{Binding remove_leg}"
ContractPickerModel="{Binding contract_picker_model}"
IsEnabled="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}, Converter={StaticResource NotConverter}}"
HedgePricing="{Binding hedge_pricing}"
HedgePricingItemsSource="{Binding hedge_pricing_options}"
/>
</AdornerDecorator>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</usp:AlgoEditorGridRowStyleSelector.SpreaderStyle>
</usp:AlgoEditorGridRowStyleSelector>
</ResourceDictionary>
</UserControl.Resources>
<u:StateManager.Groups>
<u:StateGroupsManager>
<u:StateDefinitionCollection>
<u:StateDefinition State="{Binding IsReadOnly, Source={u:DirectReference me}, Converter={x:Static usp:AlgoEditorGridView.GeneralVisualStateConverter}}"/>
<u:StateDefinition State="{Binding CanUserChangeAlgoRoot, Source={u:DirectReference me}, Converter={x:Static usp:AlgoEditorGridView.AlgoRootVisualStateConverter}}"/>
</u:StateDefinitionCollection>
</u:StateGroupsManager>
</u:StateManager.Groups>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}">
<VisualState Name="{x:Static Normal, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}"/>
<VisualState Name="{x:Static ReadOnly, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.General}}">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(DataGrid.IsReadOnly)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
<ColorAnimation Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" BeginTime="0" Duration="0" To="Gray"/>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="root_algo_combo_box" Storyboard.TargetProperty="(Control.IsEnabled)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}">
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}"/>
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoEditorGridView.VisualStateNames.AlgoRoot}}">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="root_algo_label" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="root_algo_combo_box" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<ThicknessAnimation Storyboard.TargetName="_param_grid" Storyboard.TargetProperty="(Control.Margin)" BeginTime="0" Duration="0" To="0,4,0,0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="root_algo_label" VerticalAlignment="Center" Margin="{StaticResource Margin.LP2}" Visibility="Collapsed">Algo</TextBlock>
<ComboBox
x:Name="root_algo_combo_box"
Margin="40,0,0,0"
ItemsSource="{Binding AlgoRootsViewSource.View, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
SelectedItem="{Binding SelectedAlgoRoot, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
Visibility="Collapsed"
>
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}" DisplayName="{Binding display_name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<DataGrid
x:Name="_param_grid"
Grid.Row="1"
Grid.IsSharedSizeScope="True"
VirtualizingPanel.ScrollUnit="Pixel"
AlternatingRowBackground="{x:Null}"
IsReadOnly="False"
Margin="0,2,0,0"
MinRowHeight="18"
ColumnHeaderHeight="0"
ItemsSource="{Binding AlgoParamRowsViewSource.View, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorGridView}}"
SelectionMode="Single"
RowStyleSelector="{StaticResource RowStyleSelector}"
>
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="True" Header="Param" CellTemplateSelector="{StaticResource ParamNameTemplateSelector}" MinWidth="120" Width="*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
<Setter Property="IsTabStop" Value="False"/>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ParamTemplateSelector}" CellEditingTemplateSelector="{StaticResource ParamEditingTemplateSelector}" MinWidth="120" Width="*">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType=DataGrid}}" Value="{u:False}">
<Setter Property="ToolTip" Value="Click to edit"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
@@ -1,207 +0,0 @@
using bts.collections;
using bts.utils;
using mbts.spark;
using mbts.ui.controls;
using mbts.ui.converters;
using mbts.ui.utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorGridView : UserControl {
public static readonly IValueConverter
GeneralVisualStateConverter = ValueConverter.create<bool, string>(is_read_only => is_read_only ? VisualStateNames.General.ReadOnly : VisualStateNames.General.Normal),
AlgoRootVisualStateConverter = ValueConverter.create<bool, string>(show_bbo => show_bbo ? VisualStateNames.AlgoRoot.Active : VisualStateNames.AlgoRoot.Inactive),
AlgoRootComboBoxMarginConverter = ValueConverter.create<double, Thickness>(name_col_width => new Thickness(name_col_width + 8, 0, 0, 0)),
NullableEnumParamRowItemsSourceConverter = ValueConverter.create<AlgoParamRowVM, IEnumerable>(param_row => {
if (param_row?.param != null) {
var tenum = param_row.param.GetType().GetInterfaceGenericArgument(AlgoParamTemplateSelector.generic_algo_param_itype);
if (tenum.IsNullable()) {
// .First() should be safe, since we know this is a Nullable<T>.
tenum = tenum.GetGenericArguments().First();
}
if (tenum.IsEnum)
return Enum.GetValues(tenum);
}
return null;
});
public static readonly MouseButtonEventHandler PayupTicksMouseButtonHandler = (s, e) => {
if ((s as Hyperlink)?.Command is ICommand cmd) {
cmd.ExecuteIfCan(KeyboardExtensions.GetPressedNumber() ?? 0);
e.Handled = true;
}
};
readonly ObservableAggregateCollection<AlgoParamRowVM> _param_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
public AlgoEditorGridView() {
InitializeComponent();
_set_value_column_width();
// AlgoRoots
AlgoRootsViewSource = new CollectionViewSource<_AlgoRootsCollectionView>();
BindingOperations.SetBinding(AlgoRootsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoRoots)) { Source = this });
AlgoRootsViewSource.GroupDescriptions.Add(new PropertyGroupDescription(nameof(IAlgoVM.group)));
// AlgoParamRows
AlgoParamRowsViewSource = new CollectionViewSource<ListCollectionView<AlgoParamRowVM>>();
BindingOperations.SetBinding(AlgoParamRowsViewSource, CollectionViewSource.SourceProperty, new Binding(nameof(AlgoParamRows)) { Source = this });
AlgoParamRowsViewSource.IsLiveFilteringRequested = true;
AlgoParamRowsViewSource.LiveFilteringProperties.Add(nameof(AlgoParamRowVM.permissions));
AlgoParamRowsViewSource.Filter += param_rows_filter;
DataGridEditHelper.set_hooks(this);
void param_rows_filter(object sender, FilterEventArgs e) =>
e.Accepted = e.Item is AlgoParamRowVM row && row.param.permissions != AlgoParamPermissions.None;
}
public enum Columns { ParamName, Value }
event Action CommitEditRequested, CancelEditRequested;
public CollectionViewSource AlgoRootsViewSource { get; }
public CollectionViewSource<ListCollectionView<AlgoParamRowVM>> AlgoParamRowsViewSource { get; }
bool _stretch_value_column = true;
public bool StretchValueColumn {
get => _stretch_value_column;
set {
if (_stretch_value_column != value) {
_stretch_value_column = value;
_set_value_column_width();
}
}
}
#region AnimateDirtyParams
public static readonly DependencyProperty AnimateDirtyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(AnimateDirty), true);
public bool AnimateDirty {
get => (bool)GetValue(AnimateDirtyProperty);
set => SetValue(AnimateDirtyProperty, value);
}
#endregion
#region IsReadOnly
public static readonly DependencyProperty IsReadOnlyProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(IsReadOnly));
public bool IsReadOnly {
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
#endregion
#region CanUserChangeAlgoRoot
public static readonly DependencyProperty CanUserChangeAlgoRootProperty =
WPFHelper.create_dp<bool, AlgoEditorGridView>(nameof(CanUserChangeAlgoRoot));
public bool CanUserChangeAlgoRoot {
get => (bool)GetValue(CanUserChangeAlgoRootProperty);
set => SetValue(CanUserChangeAlgoRootProperty, value);
}
#endregion
#region AlgoRoots
public static readonly DependencyProperty AlgoRootsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<IAlgoVM>, AlgoEditorGridView>(nameof(AlgoRoots));
public IReadOnlyObservableCollection<IAlgoVM> AlgoRoots {
get => GetValue(AlgoRootsProperty) as IReadOnlyObservableCollection<IAlgoVM>;
set => SetValue(AlgoRootsProperty, value);
}
#endregion
#region SelectedAlgoRoot
public static readonly DependencyProperty SelectedAlgoRootProperty =
WPFHelper.create_dp<IAlgoVM, AlgoEditorGridView>(nameof(SelectedAlgoRoot), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
public IAlgoVM SelectedAlgoRoot {
get => GetValue(SelectedAlgoRootProperty) as IAlgoVM;
set => SetValue(SelectedAlgoRootProperty, value);
}
#endregion
#region AlgoParamRows
public static readonly DependencyProperty AlgoParamRowsProperty =
WPFHelper.create_dp<IReadOnlyObservableCollection<AlgoParamRowVM>, AlgoEditorGridView>(nameof(AlgoParamRows));
public IReadOnlyObservableCollection<AlgoParamRowVM> AlgoParamRows {
get => GetValue(AlgoParamRowsProperty) as IReadOnlyObservableCollection<AlgoParamRowVM>;
set => SetValue(AlgoParamRowsProperty, value);
}
#endregion
public static AlgoEditorGridView create_attach_view() =>
new AlgoEditorGridView {
Margin = new Thickness(6),
CanUserChangeAlgoRoot = true,
};
public void commit_edit() => CommitEditRequested?.Invoke();
public void cancel_edit() => CancelEditRequested?.Invoke();
void _set_value_column_width() {
foreach (var c in _param_grid?.Columns.empty_if_null())
c.Width = _stretch_value_column ? new DataGridLength(1, DataGridLengthUnitType.Star) : DataGridLength.Auto;
}
void _on_execution_strategy_toggle_click(object sender, RoutedEventArgs e) {
if (sender is Hyperlink hl && hl.try_find_ancestor_of_type(out DataGridRow row_view)) {
Visibility vis; string text;
if (row_view.DetailsVisibility == Visibility.Visible) {
vis = Visibility.Collapsed;
text = "Show Strategy Params";
} else {
vis = Visibility.Visible;
text = "Hide Strategy Params";
}
hl.Inlines.Clear();
hl.Inlines.Add(text);
row_view.SetCurrentValue(DataGridRow.DetailsVisibilityProperty, vis);
}
}
public static class VisualStateNames {
public static class General {
public const string GroupName = nameof(General);
public static readonly string
Normal = $"{GroupName}{nameof(Normal)}",
ReadOnly = $"{GroupName}{nameof(ReadOnly)}";
}
public static class AlgoRoot {
public const string GroupName = nameof(AlgoRoot);
public static readonly string
Active = $"{GroupName}{VisualStates.Active}",
Inactive = $"{GroupName}{VisualStates.Inactive}";
}
}
class _AlgoRootsCollectionView : ListCollectionView<IAlgoVM> {
public _AlgoRootsCollectionView(IList<IAlgoVM> list) : base(list) =>
CustomSort = AlgoCollectionHelper.algo_vm_comparer.to_non_generic();
}
}
class AlgoEditorGridRowStyleSelector : StyleSelector {
public Style DefaultStyle { get; set; }
public Style SpreaderStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container) {
if (item is AlgoParamRowVM row) {
if (row.param.id == nameof(bts.api.orderman.SpreaderOrderDefinition.ExecutionStrategy))
return SpreaderStyle;
}
return DefaultStyle;
}
}
}
@@ -1,88 +0,0 @@
using bts.collections;
using mbts.ui.controls;
using mbts.ui.utility;
using mbts.utils;
using System;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Reactive.Disposables;
namespace mbts.spark {
static class AlgoParamRowsHelper {
public static IDisposable connect(AlgoDefinitionVM algo, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) =>
algo == null ? null : new CompositeDisposable(algo.observe_algo_root().OnDispatcher().SubscribeEach(a => {
rows.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, null, child_rows, include_algo_type_rows);
rows.add_collection(child_rows.AsReadOnly());
return hook;
})) {
rows.clear_collections
};
public static IDisposable negate_is_dirty(IReadOnlyObservableCollection<AlgoParamRowVM> rows) {
return ObservableAutoWrapper.connect_item_hooks(rows, hook);
IDisposable hook(AlgoParamRowVM r) {
r.param.is_dirty_changed += negate;
return Disposable.Create(unhook);
void negate(bool is_dirty) {
if (is_dirty)
r.param.mark_committed();
}
void unhook() =>
r.param.is_dirty_changed -= negate;
}
}
static IDisposable _wrap_rows(AlgoDefinitionVM algo_def, ObservableAggregateCollection<AlgoParamRowVM> rows, bool include_algo_type_rows) {
AlgoParamRowVM root = null;
ObservableAggregateCollection<AlgoParamRowVM> algo_params;
if (include_algo_type_rows) {
// while normal params live and die with the selected algo-type (when the algo-type changes, they are removed, and replaced with params for the new algo type),
// the algo-type row is constant (it is the row with the algo-type dropdown, which the user may use to select different algos).
algo_params = new ObservableAggregateCollection<AlgoParamRowVM>();
rows.add_collection(new ObservableAppendCollection<AlgoParamRowVM>(algo_params, root = new AlgoSelectorParamRowVM(new AlgoVMWrapper(algo_def), algo_def, null)).AsReadOnly());
} else {
algo_params = rows;
}
return algo_def.observe_algo_root().SubscribeEach(a => {
algo_params.clear_collections();
if (a == null)
return Disposable.empty;
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
/// Note: the order of operations here matters
/// <see cref="_wrap_rows(IAlgoVM, AlgoParamRowVM, ObservableAggregateCollection{AlgoParamRowVM}, bool)" /> has the potential to trigger a lot of CollectionChanged events (depending on how deep the algo-tree is).
/// There is no need to burden the aggregate 'rows' with echoing all of these events, if the child-row collection is finished changing when it is added to the aggregate.
var hook = _wrap_rows(a, root, child_rows, include_algo_type_rows);
algo_params.add_collection(child_rows.AsReadOnly());
return hook;
});
}
static IDisposable _wrap_rows(IAlgoVM algo_root, AlgoParamRowVM parent, ObservableAggregateCollection<AlgoParamRowVM> all_rows, bool include_algo_type_rows) {
var wrapped_params = new ObservableAutoWrapper<AlgoParamRowVM, IAlgoParamVM>(algo_root.algo_params, pvm => new AlgoParamRowVM(algo_root, pvm, parent));
var selector_params = new ObservableCollection<AlgoParamRowVM>();
all_rows.add_collection(wrapped_params);
all_rows.add_collection(selector_params);
return ObservableAggregateCollection.connect(selector_params, algo_root.child_algo_selectors, new Func<IAlgoSelector, (ObservableCollection<AlgoParamRowVM>, IDisposable)>(hook_children));
(ObservableCollection<AlgoParamRowVM>, IDisposable) hook_children(IAlgoSelector s) {
var child_rows = new ObservableAggregateCollection<AlgoParamRowVM>();
var selector_wrapper = new AlgoSelectorParamRowVM(algo_root, s, parent);
return (
include_algo_type_rows ?
(ObservableCollection<AlgoParamRowVM>)new ObservableAppendCollection<AlgoParamRowVM>(child_rows, selector_wrapper) :
child_rows,
s.subscribe_selected_algo().OnDispatcher().SubscribeEach(a => {
s.value = a;
child_rows.clear_collections();
return _wrap_rows(a, selector_wrapper, child_rows, include_algo_type_rows);
}));
}
}
}
}
@@ -1,78 +0,0 @@
using bts.collections;
using bts.spark;
using mbts.spark;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
namespace mbts.ui.spark {
static class AlgoParamTemplateKeys {
public const string
Price = nameof(Price),
Size = nameof(Size);
}
/// TODO: remove this class and just use a <see cref="KeyedTemplateSelector"/>.
class AlgoParamTemplateSelector : DataTemplateSelector {
public static readonly Type generic_algo_param_itype = typeof(IAlgoParamVM<>);
KeyedCollection<TemplateSelectorKeyValue, string> _keyed_templates = new KeyedCollection<TemplateSelectorKeyValue, string>(tsk => tsk.Key.ToString());
public DataTemplate BoolTemplate { get; set; }
public DataTemplate NullableBoolTemplate { get; set; }
public DataTemplate IntTemplate { get; set; }
public DataTemplate NullableIntTemplate { get; set; }
public DataTemplate UIntTemplate { get; set; }
public DataTemplate NullableUIntTemplate { get; set; }
public DataTemplate DoubleTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
public DataTemplate NullableEnumTemplate { get; set; }
public DataTemplate StringTemplate { get; set; }
public DataTemplate QuantityMeasureTemplate { get; set; }
public DataTemplate OrderDefTemplate { get; set; }
public DataTemplate ExecutionStrategyTemplate { get; set; }
public DataTemplate TimestampTemplate { get; set; }
/// <see cref="_keyed_templates"/> is needed for the 'price' param. Price was formerly edited in a separate control, however now it is just another param row.
/// Before this, the generic type argument in the <see cref="IAlgoParamVM{T}"/> implementation was enough to determine the template.
/// Since 'price' is just a double, further specification was needed to associate it with a 'PriceEntryControl' template.
public IList KeyedTemplates => _keyed_templates;
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if (item is ITemplateSelectorKey tsk && tsk.key is string template_key && _keyed_templates.TryGetValue(template_key, out var tkv))
return tkv.Value;
IAlgoParamVM pvm = item as IAlgoParamVM ?? (item as AlgoParamRowVM)?.param;
if (pvm != null) {
if (pvm is IAlgoParamVM<double>) return DoubleTemplate;
if (pvm is IAlgoParamVM<int>) return IntTemplate;
if (pvm is IAlgoParamVM<int?>) return NullableIntTemplate;
if (pvm is IAlgoParamVM<uint>) return UIntTemplate;
if (pvm is IAlgoParamVM<uint?>) return NullableUIntTemplate;
if (pvm is IAlgoParamVM<bool>) return NullableBoolTemplate;
if (pvm is IAlgoParamVM<bool?>) return BoolTemplate;
if (pvm is IAlgoParamVM<string>) return StringTemplate;
if (pvm is IAlgoParamVM<IAlgoVM>) return OrderDefTemplate;
if (pvm is IAlgoParamVM<QuantityMeasureImmutable?>) return QuantityMeasureTemplate;
if (pvm is IAlgoParamVM<ExecutionStrategy>) return ExecutionStrategyTemplate;
if (pvm is IAlgoParamVM<Google.Protobuf.WellKnownTypes.Timestamp>) return TimestampTemplate;
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), generic_algo_param_itype).FirstOrDefault();
if (targ != null) {
if (targ.IsEnum)
return EnumTemplate;
else if (targ.IsNullable()) {
targ = targ.GetGenericArguments()[0];
if (targ.IsEnum)
return NullableEnumTemplate;
}
}
}
return null;
}
}
}
@@ -1,181 +0,0 @@
#define ENTER_BEHAVES_LIKE_TAB
using bts;
using mbts.ui.controls;
using mbts.ui.extensions;
using mbts.ui.spark.algo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorGridView {
static class DataGridEditHelper {
public static IDisposable set_hooks(AlgoEditorGridView algo_editor) {
var grid = algo_editor._param_grid;
grid.PreviewKeyDown += on_preview_key_down;
grid.KeyDown += on_key_down;
grid.SelectionChanged += on_selection_changed;
grid.PreparingCellForEdit += on_preparing_cell_for_edit;
grid.CellEditEnding += on_cell_edit_ending;
grid.GotFocus += on_got_focus;
algo_editor.CommitEditRequested += on_commit_edit_requested;
algo_editor.CancelEditRequested += on_cancel_edit_requested;
return Disposable.Create(() => {
grid.PreviewKeyDown += on_preview_key_down;
grid.KeyDown -= on_key_down;
grid.SelectionChanged -= on_selection_changed;
grid.PreparingCellForEdit -= on_preparing_cell_for_edit;
grid.CellEditEnding -= on_cell_edit_ending;
grid.GotFocus -= on_got_focus;
algo_editor.CommitEditRequested -= on_commit_edit_requested;
algo_editor.CancelEditRequested -= on_cancel_edit_requested;
});
/// Handlers
// hooked up to PreviewKeyDown, instead of KeyDown, so the cells can't override this behavior
void on_preview_key_down(object sender, KeyEventArgs e) {
switch (e.Key) {
case Key.Escape: e.Handled = cancel_edit(); break;
case Key.Enter:
if (grid.TryGetCurrentRowView(out var row_view)) {
if (row_view.IsEditing) {
commit_edit();
#if ENTER_BEHAVES_LIKE_TAB
NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Down);
#endif
} else grid.BeginEdit();
}
e.Handled = true;
break;
}
}
// hooked up to KeyDown, instead of PreviewKeyDown, so cells can implement custom Key/CommandBindings, or other key-based behavior without the DataGrid handling it in the PreviewKeyDown event.
// for ex: TickUpDown cells can do things like TickUp/Down when Ctrl+Up/Down arrow-key is pressed.
void on_key_down(object sender, KeyEventArgs e) {
switch (e.Key) {
case Key.Up: NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Up); break;
case Key.Down: NavigationHelper.keyboard_move_focus(FocusNavigationDirection.Down); break;
default: return;
}
e.Handled = true;
}
void on_selection_changed(object sender, SelectionChangedEventArgs e) {
_commit(e.RemovedItems?.Cast<object>()?.SafeSingleOrDefault());
_commit(e.AddedItems?.Cast<object>()?.SafeSingleOrDefault());
void _commit(object row_item) {
if (grid.TryGetRowView(row_item, out var row_view) && row_view.IsEditing)
grid.CommitEdit();
}
}
void on_preparing_cell_for_edit(object sender, DataGridPreparingCellForEditEventArgs e) {
if (e.EditingElement is ContentPresenter presenter) {
OneOf.Match(get<TextBox>, get<ComboBox>, get<QuantityMeasureControl>, get<ToggleButton>, text_box, combo_box, quantity_measure_control, toggle_button);
T get<T>() => presenter.descendants_of_type<T>().FirstOrDefault();
// TextBox
void text_box(TextBox tb) => Keyboard.Focus(tb);
// ComboBox
void combo_box(ComboBox cb) { }
// QuantityMeasureControl
async void quantity_measure_control(QuantityMeasureControl qmc) {
if (!focus_text()) {
await qmc.LoadedAsync();
focus_text();
}
bool focus_text() =>
qmc.try_find_descendent_of_type(out TextBox tb) && Keyboard.Focus(tb) == tb;
}
// ToggleButton
void toggle_button(ToggleButton tb) {
if (Mouse.LeftButton == MouseButtonState.Pressed && !tb.IsLoaded) {
// this is a hack to toggle IsChecked when the user clicks a checkbox cell that is not already in edit mode.
IDisposable dispose = null;
dispose = Disposable.Create(
tb.SubscribePreviewMouseUp(on_mouse_up),
tb.SubscribeMouseLeave(on_mouse_leave));
void on_mouse_up(object s, MouseButtonEventArgs mbe) {
if (tb.IsMouseOver)
tb.SetCurrentValue(ToggleButton.IsCheckedProperty, !tb.IsChecked);
Disposable.dispose(ref dispose);
}
void on_mouse_leave(object s, MouseEventArgs me) =>
Disposable.dispose(ref dispose);
}
}
}
e.EditingElement.Focus();
}
void on_cell_edit_ending(object sender, DataGridCellEditEndingEventArgs e) {
if (e.EditingElement is ContentPresenter presenter) {
switch (e.EditAction) {
case DataGridEditAction.Cancel: OneOf.Match(get<TextBox>, get<ComboBox>, get<ToggleButton>, cancel_text_box, cancel_combo_box, cancel_toggle_button); break;
case DataGridEditAction.Commit: OneOf.Match(get<TextBox>, get<ComboBox>, get<ToggleButton>, commit_text_box, commit_combo_box, commit_toggle_button); break;
}
T get<T>() => presenter.descendants_of_type<T>().FirstOrDefault();
// TextBox
void commit_text_box(TextBox tb) => tb.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
void cancel_text_box(TextBox tb) => tb.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget();
// ComboBox
void commit_combo_box(ComboBox cb) { }
void cancel_combo_box(ComboBox cb) { }
// ToggleButton
void commit_toggle_button(ToggleButton tb) { }
void cancel_toggle_button(ToggleButton tb) { }
}
}
void on_got_focus(object sender, RoutedEventArgs e) {
if (e.OriginalSource is DataGridCell cell && cell.try_find_ancestor_of_type(out DataGridRow row)) {
if (row.IsEditing) {
if (!cell.IsEditing) {
grid.CancelEdit(DataGridEditingUnit.Row);
if (try_set_current())
grid.BeginEdit(e);
}
} else {
try_set_current();
if (!row.IsEditing)
grid.BeginEdit(e);
}
bool try_set_current() {
grid.SetCurrentValue(DataGrid.CurrentItemProperty, row.Item);
if (grid.TryGetCurrentRowView(out var row_view)) {
row_view.SetCurrentValue(DataGridRow.IsSelectedProperty, true);
grid.SetCurrentValue(DataGrid.CurrentCellProperty, new DataGridCellInfo(row.Item, grid.Columns[1]));
return true;
} else return false;
}
}
}
void on_commit_edit_requested() {
if (grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing)
commit_edit();
}
void commit_edit() {
using (grid.SuspendGotFocus(on_got_focus))
grid.CommitEdit(DataGridEditingUnit.Row, true);
}
void on_cancel_edit_requested() =>
cancel_edit();
bool cancel_edit() {
if (grid.CurrentCell != null && grid.TryGetCurrentRowView(out var row_view) && row_view.IsEditing) {
using (grid.SuspendGotFocus(on_got_focus))
grid.CancelEdit(DataGridEditingUnit.Row);
return true;
} else return false;
}
}
}
}
}
@@ -1,120 +0,0 @@
<UserControl
x:Class="mbts.ui.spark.AlgoEditorView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sp="clr-namespace:mbts.spark"
xmlns:u="clr-namespace:mbts.ui"
xmlns:cv="clr-namespace:mbts.ui.converters"
xmlns:usp="clr-namespace:mbts.ui.spark"
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--Primitive Constants-->
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
<sys:Double x:Key="VerticalPush1">4</sys:Double>
<sys:Double x:Key="VerticalPush2">8</sys:Double>
<sys:Double x:Key="VerticalPush3">12</sys:Double>
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
<!--Converters-->
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
<!--Default Styles-->
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
<Setter Property="Padding" Value="8"/>
</Style>
<Style TargetType="TextBox">
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
</Style>
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<GroupBox
x:Name="content_host"
Grid.Row="2"
Background="Transparent"
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
Margin="0,6,0,0"
Padding="12,8"
>
<GroupBox.Style>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="BorderBrush" Value="Transparent">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</Style.Triggers>
</Style>
</GroupBox.Style>
<GroupBox.Header>
<ComboBox
ToolTipService.ShowOnDisabled="True"
Width="140"
Margin="{StaticResource Margin.LP1}"
ItemsSource="{Binding algos_view}"
SelectedItem="{Binding algo_root}"
>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.IsEnabled>
<Binding Path="editing_permissions">
<Binding.Converter>
<cv:EnumHasFlagsConverter>
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
</cv:EnumHasFlagsConverter>
</Binding.Converter>
</Binding>
</ComboBox.IsEnabled>
<ComboBox.ItemTemplate>
<DataTemplate>
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</GroupBox.Header>
<ContentControl
Grid.IsSharedSizeScope="True"
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
Margin="{StaticResource Margin.TP1}"
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
IsTabStop="False"
>
<ContentControl.Resources>
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
</ContentControl.Resources>
</ContentControl>
</GroupBox>
</UserControl>
@@ -1,28 +0,0 @@
using mbts.ui.extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace mbts.ui.spark {
partial class AlgoEditorView : UserControl {
public AlgoEditorView() {
InitializeComponent();
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
content_host.MouseEnter += content_host_MouseEnter;
content_host.MouseLeave += content_host_MouseLeave;
// local methods
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
}
#region IsMouseOverNodeContent
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
#endregion
}
}
+48 -110
View File
@@ -1,8 +1,6 @@
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.collections;
@@ -18,10 +16,10 @@ using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows.Data;
using System.Windows.Media;
using static bts.api.orderman.Fill.Types;
@@ -33,19 +31,22 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
readonly OrdersModel _model;
readonly DIYGridRowsManager<FillTuple, Columns, FillsFilterStates> _rows_manager;
readonly RXOperableCollection<Row, FillTuple, string> _rows;
readonly DIYGridSortManager<Columns> _sort_manager;
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
List<(ColumnBase col, Columns key, ListSortDirection direction)> _active_comparers = new List<(ColumnBase col, Columns key, ListSortDirection direction)>();
IWindowSettingsManager<GridSettings> _settings_manager;
int _total_row_count;
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FillsFilterStates>, Columns>(c => c.key);
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FilterStates>, Columns>(c => c.key);
// security
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(),
DiscreteValuesFilterVM.Create<FillTuple, SecurityImmutable, SecurityCriteria>(
@@ -60,59 +61,13 @@ namespace mbts.spark {
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
comparer: Comparer<SecurityImmutable>.Default
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
// symbol
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
DiscreteValuesFilterVM.Create<FillTuple, string>(
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
ft => ft.security?.Symbol ?? string.Empty,
nameof(OrderBookRowVM.symbol)),
DataGridFilterKnownTemplates.DiscreteValues,
DefaultColumnWidths.Symbol);
// algo items view (common resources)
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
// order type
var order_type_filter = DiscreteValuesFilterVM.CreateTableUpdate<FillTuple, AlgoDataVM, AlgoID, OrderType[]>(
ft => new AlgoDataVM(ft.algo ?? AlgoData.empty),
AlgoDataVM.id_fn,
ots => Array.ConvertAll(ots ?? Array.Empty<OrderType>(), ot => AlgoID.from_algo_key(AlgoKey.from_known_type(ot))),
ids => ids.empty_if_null().Select(AlgoID.to_algo_key).Where(k => k.mode == AlgoKey.Mode.Factory).Select(k => k.factory).ToArray(),
out var order_type_observer,
out var order_type_items_view);
order_type_items_view.CustomSort = algo_items_comp;
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
order_type_observer.OnNext(TableUpdate<AlgoDataVM, AlgoID>.mk_empty(AlgoDataVM.id_fn)
.with_added(OrderDataHelper.all_order_types
.Select(OrderDataHelper.Defaults.CustomOrderDefinitionImmutable)
.Select(AlgoData.from_custom_def)
.Select(d => new AlgoDataVM(d))));
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
add_col_expl(Columns.OrderType,
new OrderTypeColumn<Columns>(Columns.OrderType, new Binding(nameof(Row.badge_fill)), new Binding(nameof(Row.display_name)), order_type_filter) {
id = col_id(Columns.OrderType),
sort_member_path = nameof(Row.order_type)
}, order_type_filter);
// root algo type
var root_algo_type_filter = DiscreteValuesFilterVM.CreateTableUpdate<FillTuple, AlgoDataVM, AlgoID>(
ft => new AlgoDataVM(ft.root_algo ?? AlgoData.empty),
AlgoDataVM.id_fn,
out var root_algo_type_observer,
out var root_algo_type_items_view);
root_algo_type_items_view.CustomSort = algo_items_comp;
root_algo_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
add_col_expl(Columns.RootAlgoType,
new OrderTypeColumn<Columns>(
Columns.RootAlgoType,
new Binding(nameof(Row.root_algo_badge_color)) { Converter = new ui.converters.ColorToBrushConverter() },
new Binding(nameof(Row.root_algo_display_name)),
root_algo_type_filter,
DefaultColumnHeaders.RootAlgoType) {
id = col_id(Columns.RootAlgoType),
sort_member_path = nameof(Row.root_algo_order_type)
}, root_algo_type_filter);
// side
var side_filter = dv_filter(new[] { Side.Buy, Side.Sell }, r => r.fill.Side);
add_col_expl(Columns.Side, new SideColumn<Row, Columns>(Columns.Side, r => r.side, side_filter) { id = col_id(Columns.Side) }, side_filter);
@@ -128,46 +83,45 @@ namespace mbts.spark {
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
// rows
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
/// Rows
_rows = new RXOperableCollection<Row, FillTuple, string>(
(ft, fk) => new Row(ft, model),
ft => ft.fill.FillKey,
new ComparerBuilder<FillTuple>(new[] {
_get_column_comparer(Columns.FillTime),
_get_column_comparer(Columns.FillKey)
}).to_comparer());
// row sort/filter managers
_sort_manager = new DIYGridSortManager<Columns>(col_tups.WrapSelector(t => (t.col, t.key)), Columns.FillKey);
_filters_manager = DIYGridFiltersManager.create(col_tups);
var rows_manager = new DIYGridRowsManager<FillTuple, Columns, FilterStates>(_filters_manager, _sort_manager, _get_column_comparer, f => _rows.filter = f, c => _rows.comparer = c, _rows.reset, FillsWindow.window_type_name);
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
o.OnNext(last_unfiltered_fills.as_snapshot());
_unfiltered_fills_changed += o.OnNext;
return Disposable.Create(() => _unfiltered_fills_changed -= o.OnNext);
});
_dispose = new CompositeDisposable(
grid_settings_manager.load_from(settings_manager.current_settings());
//
_dispose = Disposable.Create(
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
RXCollection.connect(rows, fills_buffer.Filter(filter_src, FillTuple.key_fn), comp_src, ft => new Row(ft, model), r => r.fill_tuple),
fills_source.OnDispatcher().Subscribe(t => {
last_unfiltered_fills = t;
_rows.update(t);
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
_unfiltered_fills_changed?.Invoke(t);
}),
state_obs.OnDispatcher().Subscribe(s => {
if (s.grid != null && _settings_manager != null) {
if (s.grid != null) {
// this means we are not using defaults,
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
var fs = s.filters ?? new FillsFilterStates {
var fs = s.filters ?? new FilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
};
_rows_manager.on_settings(fs, s.sorting_criterion);
rows_manager.on_settings(fs, s.sorting_criterion);
}),
model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher().Subscribe(root_algo_type_observer),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager)
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
fills_source.Connect()
);
// local methods
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
@@ -177,17 +131,14 @@ namespace mbts.spark {
Width = width
}, filter);
void add_col_expl<TFilter>(Columns col_key, ColumnBase col, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter) =>
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FillsFilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
// discrete-values-filter factories
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter<T>(IReadOnlyCollection<T> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter_rx<T>(IObservable<IEnumerable<T>> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
}
// this event is used to forward updates from the main data to the filtered data that drives the rows, after resetting the total row count.
event Action<TableUpdate<FillTuple, string>> _unfiltered_fills_changed;
public ObservableCollection<ColumnBase> columns { get; } = new ObservableCollection<ColumnBase>();
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
public ObservableCollection<Row> rows => _rows;
public int total_row_count => _total_row_count;
public SimpleGridSettingsManager<Row> grid_settings_manager { get; }
@@ -199,26 +150,26 @@ namespace mbts.spark {
return grid_settings_manager.create();
}
public FillsFilterStates get_filters() =>
_rows_manager.get_filters();
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
_rows_manager.get_sorting_criterion();
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
_rows_manager.on_sorting(e);
_sort_manager.create();
static Columns _col_key(ColumnBase column) =>
((IKeyedColumn<Columns>)column).key;
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
public FilterStates get_filters() =>
_filters_manager.create();
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples_with_root.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples_with_root;
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples;
}
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
state.SelectManyInSeries(s => _get_observable(bus, model, s));
static IComparer<FillTuple> _get_column_comparer(Columns col) {
@@ -230,13 +181,11 @@ namespace mbts.spark {
case Columns.FillTime: return wrap(ft => ft.fill.Timestamp.to_local_time());
case Columns.OrderKey: return wrap(ft => ft.fill.OrderKey);
case Columns.OrderType: return wrap_default(ft => ft.order?.order_type() ?? OrderType.None);
case Columns.RootAlgoType: return wrap_default(ft => ft.root_algo.HasValue ? ft.root_algo.Value.value.CustomCase : OrderType.None);
case Columns.Price: return wrap(ft => ft.fill.LastPrice);
case Columns.Security: return wrap(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable);
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
case Columns.Side: return wrap_default(ft => ft.fill.Side);
case Columns.Size: return wrap(ft => ft.fill.LastShares);
case Columns.Symbol: return wrap(ft => ft.security?.Symbol ?? string.Empty);
case Columns.TimeInForce: return wrap_default(ft => ft.order?.time_in_force() ?? TimeInForce.Undefined);
case Columns.User: return wrap(ft => ft.order?.user() ?? string.Empty);
default: goto case Columns.FillTime;
@@ -259,20 +208,16 @@ namespace mbts.spark {
}
public class Row : SecurityRow, IEquatable<Row>, IRowVM {
public readonly FillTuple fill_tuple;
readonly OrdersModel _model;
readonly FillTuple _fill_tuple;
readonly FillImmutable _fill;
readonly OrderRecordImmutable _order;
AlgoData? _root_algo => fill_tuple.root_algo;
AlgoDefinitionMetadata? _root_algo_meta => fill_tuple.root_algo_meta;
Brush _badge_fill;
Color? _root_algo_badge_color;
OrderMetadata _order_metadata;
public Row(FillTuple fill_tuple, OrdersModel model) :
base(fill_tuple.security ?? SecurityDataHelper.Defaults.SecurityImmutable) {
this.fill_tuple = fill_tuple;
_fill_tuple = fill_tuple;
_fill = fill_tuple.fill;
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
_model = model;
@@ -282,10 +227,9 @@ namespace mbts.spark {
public DateTime fill_time => _fill.Timestamp.to_local_time();
public TimeInForce time_in_force => _order.time_in_force();
public OrderType order_type => _order.order_type();
//public OrderType root_algo_type => _or
public string display_name => OrderUtils.display_name(_order, fill_tuple.algo);
public string display_name => OrderUtils.display_name(_order, _fill_tuple.algo);
public OrderMetadata order_metadata => _order_metadata ?? (_order_metadata = _model.factory.get_metadata(_order.Def.CustomDef.CustomCase));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(fill_tuple.algo_meta, order_metadata));
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_order, _fill_tuple.algo_meta, order_metadata));
public string account { get; private set; }
public string user => _order.user();
public Venue exchange => security.Exchange;
@@ -296,12 +240,6 @@ namespace mbts.spark {
public string fill_key => _fill.FillKey;
public string order_key => _order.Key.ToString();
// Root Algo
public OrderType root_algo_order_type => _root_algo.HasValue ? _root_algo.Value.value.CustomCase : OrderType.None;
public OrderMetadata root_algo_order_metadata => _model.factory.get_metadata(root_algo_order_type);
public Color root_algo_badge_color => _root_algo_badge_color ?? (_root_algo_badge_color = OrderUtils.badge_color(_root_algo_meta, root_algo_order_metadata)).Value;
public string root_algo_display_name => _root_algo?.name ?? root_algo_order_type.display_name();
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();
Loaded 100 of 1217 files, more files were not shown because too many files have changed in this diff. Show more