Compare commits
227
Commits
2.0.0-beta4
...
2.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
523002ac56 | ||
|
|
9c25a8751e | ||
|
|
89fc99b59a | ||
|
|
cd413d10ab | ||
|
|
1c5cc9c1bb | ||
|
|
7965743a80 | ||
|
|
c9db5d13a9 | ||
|
|
13e911760d | ||
|
|
a12c3128a3 | ||
|
|
6bb84b65a8 | ||
|
|
6fb1140a97 | ||
|
|
0e449844ca | ||
|
|
476206edb0 | ||
|
|
3483ac0712 | ||
|
|
d6e586c4ce | ||
|
|
0f1735fba2 | ||
|
|
e5254dcf7f | ||
|
|
7bd183c8a4 | ||
|
|
aaf09b197b | ||
|
|
6081128b92 | ||
|
|
6d6ecb33ba | ||
|
|
6e85612adc | ||
|
|
f33a13bfc8 | ||
|
|
c4eeac8d0a | ||
|
|
432f39a1fa | ||
|
|
6084da64a2 | ||
|
|
5ae484e6c1 | ||
|
|
a2d4eb7d10 | ||
|
|
8082ab12dd | ||
|
|
b1def915d6 | ||
|
|
70bb22c966 | ||
|
|
06520791fa | ||
|
|
1fa5e3c9af | ||
|
|
7fede58044 | ||
|
|
090a7985c3 | ||
|
|
f5fa2ff23a | ||
|
|
6a1b154061 | ||
|
|
73a4e474f1 | ||
|
|
6f630a0e62 | ||
|
|
4e30591837 | ||
|
|
3144032ed6 | ||
|
|
d9221e1053 | ||
|
|
bee1df45ad | ||
|
|
316be12b71 | ||
|
|
6c51ff83d5 | ||
|
|
1cad5b6b39 | ||
|
|
171396ca57 | ||
|
|
193eb4e445 | ||
|
|
69b3831271 | ||
|
|
39efacace4 | ||
|
|
e70dfc8832 | ||
|
|
60bf851567 | ||
|
|
053038df8d | ||
|
|
f0afc74e5f | ||
|
|
9d3c8aaf5c | ||
|
|
8575d92373 | ||
|
|
21ee7c6af5 | ||
|
|
f6cf0f28ea | ||
|
|
2b51bda3e7 | ||
|
|
1be2e81920 | ||
|
|
ea45c5c694 | ||
|
|
768567dfc7 | ||
|
|
870e7a9ef9 | ||
|
|
a9e572a1bf | ||
|
|
c7899a1dae | ||
|
|
7bb077a74d | ||
|
|
a29eaa2618 | ||
|
|
762369ad2b | ||
|
|
a8610beafd | ||
|
|
5e1a91f484 | ||
|
|
04fdb9626d | ||
|
|
6c5429b353 | ||
|
|
2bedf1f355 | ||
|
|
02752d9151 | ||
|
|
689bfa1c76 | ||
|
|
70af046131 | ||
|
|
7a45013782 | ||
|
|
6f7851b7ba | ||
|
|
85f42d1e23 | ||
|
|
f8ddaab884 | ||
|
|
78373c61ce | ||
|
|
13e5c017e2 | ||
|
|
3789d73593 | ||
|
|
38f42fe747 | ||
|
|
3a6f9a608d | ||
|
|
e14a5b2575 | ||
|
|
04a68a4e96 | ||
|
|
dd049374d7 | ||
|
|
526ec306ae | ||
|
|
f16ed40c82 | ||
|
|
96572539a7 | ||
|
|
793eb14ed7 | ||
|
|
1f94454d63 | ||
|
|
c7e3bdcd6c | ||
|
|
004a7b1395 | ||
|
|
b1498f3df1 | ||
|
|
b0759eba62 | ||
|
|
ff69cafe7f | ||
|
|
f1e90394a0 | ||
|
|
b24f855321 | ||
|
|
6e56b6cbaf | ||
|
|
16ff473fd6 | ||
|
|
4565fada08 | ||
|
|
648f808583 | ||
|
|
7a6212932e | ||
|
|
aa933696ec | ||
|
|
39d59dbbb0 | ||
|
|
a511ea3a4b | ||
|
|
e489e809ab | ||
|
|
e56d41d26b | ||
|
|
b1d45ad593 | ||
|
|
32038f8948 | ||
|
|
2a90b521da | ||
|
|
2098a60af1 | ||
|
|
67c0178b9a | ||
|
|
5da07c42bb | ||
|
|
f82035be98 | ||
|
|
a652268eb8 | ||
|
|
13d054a54c | ||
|
|
84cf2a3ad1 | ||
|
|
32e0d60dcd | ||
|
|
0a6755992a | ||
|
|
3cc0f32617 | ||
|
|
29ae9c6927 | ||
|
|
892abe098c | ||
|
|
9cf683f64f | ||
|
|
f2bb8de4e1 | ||
|
|
b6a28af99e | ||
|
|
13a9dd3696 | ||
|
|
60bbd12386 | ||
|
|
3e09274cb5 | ||
|
|
e9d1da4b51 | ||
|
|
68e3fcb1cd | ||
|
|
745be0909a | ||
|
|
74dd36bde1 | ||
|
|
0bee4168b4 | ||
|
|
d87c6027c9 | ||
|
|
859faadecd | ||
|
|
e8741f9151 | ||
|
|
ef8b0fc258 | ||
|
|
3b27fedcd3 | ||
|
|
0991fbfffd | ||
|
|
499072c94f | ||
|
|
fa8598adbd | ||
|
|
089c4ac35e | ||
|
|
af8b559fa6 | ||
|
|
62ba85d383 | ||
|
|
f4b2f84e68 | ||
|
|
474e7880d6 | ||
|
|
89f74b7c81 | ||
|
|
0abcb8bf23 | ||
|
|
47887b67d2 | ||
|
|
41a96b990d | ||
|
|
ce8bc5c631 | ||
|
|
eebb214574 | ||
|
|
47c827caf6 | ||
|
|
a82a22b133 | ||
|
|
1fd444c91c | ||
|
|
f28ae29990 | ||
|
|
aedabcdf00 | ||
|
|
3b37f8e48b | ||
|
|
beb328ec09 | ||
|
|
f9b9bdab47 | ||
|
|
3beb82c959 | ||
|
|
9e6776c6fb | ||
|
|
e807f6a702 | ||
|
|
07212aa5d6 | ||
|
|
a0f31d6443 | ||
|
|
df6a5096c2 | ||
|
|
43336d04d4 | ||
|
|
ca73b20fe5 | ||
|
|
09f6ad4031 | ||
|
|
97b52f2e64 | ||
|
|
0adc77a5cf | ||
|
|
e469e8fc70 | ||
|
|
10da3c517c | ||
|
|
5f7c4b5c37 | ||
|
|
c34d05218a | ||
|
|
3abf99da91 | ||
|
|
8d94238e91 | ||
|
|
dc169fc4fd | ||
|
|
abe8361b18 | ||
|
|
9f406e83aa | ||
|
|
2ee163bf8a | ||
|
|
775a720e0a | ||
|
|
84aefabfea | ||
|
|
102fefbd92 | ||
|
|
fc7a4b0800 | ||
|
|
6d9c083c0f | ||
|
|
3e822bcca1 | ||
|
|
8dde9bf3c5 | ||
|
|
0a683dae15 | ||
|
|
77f5723936 | ||
|
|
bd643aa525 | ||
|
|
8a53863218 | ||
|
|
6ad12e922c | ||
|
|
3171ae0afb | ||
|
|
c1fe0b3191 | ||
|
|
a071b974c8 | ||
|
|
dd7db5661b | ||
|
|
4b8d725528 | ||
|
|
db1b2a63af | ||
|
|
6006a328fe | ||
|
|
5cb48fb517 | ||
|
|
f2f8c258f9 | ||
|
|
cb8dac05d5 | ||
|
|
f6eb1e4745 | ||
|
|
16e8a6e26d | ||
|
|
9744f53d65 | ||
|
|
b231a66fc7 | ||
|
|
65b3d4fffa | ||
|
|
ba1b667ae8 | ||
|
|
e31b05042f | ||
|
|
84e443ee4b | ||
|
|
3821dfb8ff | ||
|
|
01a8502b20 | ||
|
|
edfa079326 | ||
|
|
316a617c65 | ||
|
|
0e92a12e65 | ||
|
|
5f4eb58db9 | ||
|
|
f7cdb1f00b | ||
|
|
0caa6b6ee0 | ||
|
|
6fb8fdc9f6 | ||
|
|
b7fcd17ef0 | ||
|
|
482ca6b484 | ||
|
|
7ed3a977ab | ||
|
|
59cfe3dc8d |
No files matched your search
Vendored
+177
-91
@@ -1,83 +1,14 @@
|
||||
{
|
||||
"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": [],
|
||||
@@ -102,24 +33,113 @@
|
||||
"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",
|
||||
"test_filter"
|
||||
],
|
||||
"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/src/server/sparkd/sparkd",
|
||||
"args": ["test", "[spreader]"],
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
@@ -139,7 +159,9 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
@@ -151,7 +173,10 @@
|
||||
"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": [],
|
||||
@@ -171,7 +196,9 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
@@ -183,7 +210,10 @@
|
||||
"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": [],
|
||||
@@ -203,7 +233,9 @@
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": ["bts@sparkvm"],
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
@@ -215,7 +247,10 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": ["test", "[mbo]"],
|
||||
"args": [
|
||||
"test",
|
||||
"[mbo]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
@@ -240,14 +275,16 @@
|
||||
"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",
|
||||
@@ -255,16 +292,18 @@
|
||||
"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",
|
||||
@@ -274,12 +313,59 @@
|
||||
"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
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -4,6 +4,43 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 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
|
||||
|
||||
+1
-1
Submodule docker/linux updated: ce08a5129f...e13bb5d8df.
@@ -22,9 +22,9 @@ namespace mbts.ui {
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
|
||||
AttachAlgosKey = nameof(AttachAlgosKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
|
||||
DetachAlgosKey = nameof(DetachAlgosKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
|
||||
DocumentGeometryKey = nameof(DocumentGeometryKey),
|
||||
@@ -37,11 +37,14 @@ namespace mbts.ui {
|
||||
EditDocumentAltGeometryKey = nameof(EditDocumentAltGeometryKey),
|
||||
EditDocumentGeometryKey = nameof(EditDocumentGeometryKey),
|
||||
GearGeometryKey = nameof(GearGeometryKey),
|
||||
LetterAKey = nameof(LetterAKey),
|
||||
LetterDKey = nameof(LetterDKey),
|
||||
OrdersGeometryKey = nameof(OrdersGeometryKey),
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PencilGeometryKey = nameof(PencilGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
|
||||
RubberStampGeometryKey = nameof(RubberStampGeometryKey),
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
@@ -118,7 +121,7 @@ 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(AttachAlgosGeometryKey);
|
||||
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"); } }
|
||||
|
||||
@@ -143,7 +146,7 @@ namespace mbts.ui {
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
|
||||
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"); } }
|
||||
@@ -164,6 +167,7 @@ namespace mbts.ui {
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
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 Star => create_icon(StarGeometryKey);
|
||||
@@ -192,6 +196,19 @@ 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);
|
||||
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
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
|
||||
@@ -40,58 +48,6 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Unlink free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
|
||||
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
|
||||
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
|
||||
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
|
||||
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
|
||||
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
|
||||
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
|
||||
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
|
||||
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Disconnected chains free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
|
||||
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
|
||||
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
|
||||
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
|
||||
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
|
||||
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
|
||||
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
|
||||
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
|
||||
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
|
||||
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
|
||||
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
|
||||
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
|
||||
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
|
||||
C164.311,139.575,168.644,142.574,173.171,141.769z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
|
||||
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
|
||||
C34.717,189.582,39.058,192.589,43.561,191.776z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--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
|
||||
@@ -334,6 +290,10 @@
|
||||
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
|
||||
|
||||
@@ -204,13 +204,15 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _show_order_book(Side side) =>
|
||||
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData(
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
}));
|
||||
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_ticket(MarketViewView.Columns col) {
|
||||
var order = _orders_model.factory.order(security);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
@@ -22,7 +23,6 @@ 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) { }
|
||||
|
||||
@@ -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.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);
|
||||
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);
|
||||
//
|
||||
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) {
|
||||
@@ -180,6 +180,40 @@ 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));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -11,11 +12,12 @@ 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 {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
@@ -25,6 +27,9 @@ 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, WindowSettingsManager<GridSettings> settings_manager)
|
||||
: base("Market View", bus, factory, SparkCommands.ShowMarketView.icon) {
|
||||
@@ -50,23 +55,91 @@ namespace mbts.spark {
|
||||
|
||||
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 void thread_init() {
|
||||
_init_guard.check_access();
|
||||
Log.notnull(_state);
|
||||
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 FillsWindowFactory.StartupData(
|
||||
new FillsWindowFactory.FilterStates {
|
||||
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 = 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),
|
||||
SparkCommands.ShowFills(SparkCommands.KeyGestures.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(SparkCommands.KeyGestures.ShowSyntheticCreate).complete(_vm.sorted_selected_securities_observable(), secs => secs.Count > 0 && secs.Count <= 24, secs => new SecurityCriteria(secs)),
|
||||
show_fills_cmd_wrapper,
|
||||
show_tas_cmd_wrapper,
|
||||
show_synth_create_cmd_wrapper,
|
||||
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(() => {
|
||||
@@ -87,8 +160,27 @@ namespace mbts.spark {
|
||||
return dialog;
|
||||
}))
|
||||
};
|
||||
_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())));
|
||||
//
|
||||
_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;
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<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>
|
||||
|
||||
@@ -68,12 +68,13 @@ namespace mbts.spark {
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder(SparkCommands.KeyGestures.ShowLadder).complete(
|
||||
vm_connectable_builder.create(vm => vm.current_security_md), md => md.HasValue, ConvertUtils.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new bts.shell.orderbook.OrderBookStartupData {
|
||||
filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_vm.value.row_vms.Select(r => r.security))
|
||||
}
|
||||
}
|
||||
), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
), 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 WindowSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.synthetic_management_grid,
|
||||
|
||||
@@ -73,7 +73,7 @@ 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).ToLArray()
|
||||
exchanges = EnumHelper.get_values_except(Venue.Bts).ToArray()
|
||||
}
|
||||
}));
|
||||
contract_picker_model.cancel_action = _close_new_leg_popup;
|
||||
|
||||
@@ -168,12 +168,13 @@ namespace mbts.spark {
|
||||
void _show_ladder() =>
|
||||
_bus.post(SparkTags.ShowLadderTag, md);
|
||||
|
||||
void _show_order_book() =>
|
||||
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
|
||||
filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
struct AlgoData : IEquatable<AlgoData> {
|
||||
@@ -36,4 +37,31 @@ 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 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,9 +1,76 @@
|
||||
using bts.api.orderman;
|
||||
using bts.persist.algo;
|
||||
using bts.utils;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
[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 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_order_def(OrderDefinitionImmutable order_def) {
|
||||
switch (order_def.CustomDef.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Composite: return new AlgoID(order_def.CustomDef.Composite.AlgoTemplateKey);
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new AlgoID(order_def.CustomDef.Dynamic.DynamicOrderType);
|
||||
default: return new AlgoID(order_def.CustomDef.CustomCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoID from_algo_key(AlgoKey key) {
|
||||
switch (key.mode) {
|
||||
case AlgoKey.Mode.Factory:
|
||||
if (key.factory.structure.structure.Count > 0)
|
||||
return new AlgoID((CustomOrderDefinition.CustomOneofCase)key.factory.structure.structure[0]);
|
||||
break;
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return new AlgoID(key.persisted.id_uint);
|
||||
}
|
||||
return new AlgoID(CustomOrderDefinition.CustomOneofCase.None);
|
||||
}
|
||||
|
||||
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 && Equals(obj);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// TODO: Remove <see cref="AlgoKey"/>, and redirect references to <see cref="AlgoID"/>.
|
||||
struct AlgoKey : IEquatable<AlgoKey> {
|
||||
readonly AlgoPersistedKey? _persisted_key;
|
||||
readonly AlgoNameStructureKey? _factory_key;
|
||||
@@ -83,4 +150,4 @@ namespace mbts.spark {
|
||||
|
||||
InvalidOperationException _unexpected_mode_ex() => new InvalidOperationException($"Unexpected Mode '{mode}.'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,9 @@ namespace mbts.spark {
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinition.CustomOneofCase order_type(this AlgoStructureKey structure_key) =>
|
||||
structure_key.structure.Count > 0 ? (CustomOrderDefinition.CustomOneofCase)structure_key.structure[0] : CustomOrderDefinition.CustomOneofCase.None;
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ namespace mbts.spark {
|
||||
is_hidden.GetHashCode(),
|
||||
abbreviation == null ? 0 : abbreviation.GetHashCode(),
|
||||
badge_color?.GetHashCode() ?? 0,
|
||||
EqualityComparer.aggregate_hash_limited(_favorite_templates));
|
||||
_favorite_templates == null ? 0 : 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,23 +1,36 @@
|
||||
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 [] {
|
||||
new[] {
|
||||
Comparer<AlgoDefinitionVM>.Create(_CompareUtils.compare_groups),
|
||||
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 [] {
|
||||
new[] {
|
||||
Comparer<IAlgoVM>.Create(_CompareUtils.compare_groups),
|
||||
Comparer<IAlgoVM>.Create(_CompareUtils.compare_names)
|
||||
}).to_comparer().to_safe();
|
||||
@@ -34,18 +47,50 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void update_persisted_definition_wrappers(ObservableKeyedCollection<IAlgoVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
|
||||
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)
|
||||
target[d.key].load_from(d.value);
|
||||
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(new AlgoVMWrapper(factory(d)));
|
||||
} 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.structure.order_type());
|
||||
case AlgoKey.Mode.Unpersisted:
|
||||
case AlgoKey.Mode.Persisted:
|
||||
return AlgoDisplayGroup.Studio;
|
||||
}
|
||||
return AlgoDisplayGroup.BTS;
|
||||
}
|
||||
|
||||
static class _CompareUtils {
|
||||
static AlgoDisplayGroup[] _sorted_groups = new[] {
|
||||
AlgoDisplayGroup.Exchange,
|
||||
@@ -57,14 +102,20 @@ namespace mbts.spark {
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,6 +202,7 @@ 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;
|
||||
@@ -372,7 +373,12 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void load_many(IEnumerable<CustomOrderDefinitionImmutable> defs) {
|
||||
var def_list = defs as IList<CustomOrderDefinitionImmutable> ?? defs.ToList();
|
||||
/// 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;
|
||||
|
||||
@@ -18,12 +18,16 @@ namespace mbts.spark {
|
||||
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 {
|
||||
|
||||
@@ -29,13 +29,12 @@ namespace mbts.spark {
|
||||
nameof(editing_permissions),
|
||||
nameof(definition),
|
||||
nameof(display_name)
|
||||
},
|
||||
_value_echo_props = new HashSet<string> {
|
||||
nameof(AlgoDefinitionVM.name),
|
||||
nameof(AlgoDefinitionVM.abbreviation),
|
||||
nameof(AlgoDefinitionVM.badge_fill),
|
||||
nameof(AlgoDefinitionVM.can_undo),
|
||||
nameof(AlgoDefinitionVM.can_redo)
|
||||
};
|
||||
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>();
|
||||
@@ -143,8 +142,8 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_value_echo_props.Contains(e.PropertyName))
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
if (_value_echo_props.TryGetValue(e.PropertyName, out string wrapper_prop))
|
||||
RaisePropertyChanged(wrapper_prop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -377,7 +377,7 @@
|
||||
<DataTemplate>
|
||||
<ComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding param.value, Converter={StaticResource EnumItemsSourceConverter}, Mode=OneTime}"
|
||||
ItemsSource="{Binding Converter={x:Static usp:AlgoEditorGridView.NullableEnumParamRowItemsSourceConverter}}"
|
||||
ItemTemplate="{StaticResource NullableEnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding param.value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
@@ -481,10 +481,14 @@
|
||||
<usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding param.is_dirty}" Value="{u:True}">
|
||||
<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."/>
|
||||
</DataTrigger>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</usp:AlgoEditorGridRowStyleSelector.DefaultStyle>
|
||||
|
||||
+26
-2
@@ -5,8 +5,8 @@ using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -19,7 +19,19 @@ namespace mbts.ui.spark {
|
||||
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));
|
||||
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) {
|
||||
@@ -40,6 +52,8 @@ namespace mbts.ui.spark {
|
||||
// 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) =>
|
||||
@@ -64,6 +78,16 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
}
|
||||
|
||||
#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));
|
||||
|
||||
+3
-1
@@ -19,6 +19,8 @@ namespace mbts.ui.spark {
|
||||
|
||||
/// 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);
|
||||
|
||||
public DataTemplate BoolTemplate { get; set; }
|
||||
@@ -58,7 +60,7 @@ namespace mbts.ui.spark {
|
||||
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(), typeof(IAlgoParamVM<>)).FirstOrDefault();
|
||||
var targ = ReflectionHelper.GetGenericTypeArgs(pvm.GetType(), generic_algo_param_itype).FirstOrDefault();
|
||||
if (targ != null) {
|
||||
if (targ.IsEnum)
|
||||
return EnumTemplate;
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace mbts.spark {
|
||||
public OrderType order_type => _order.order_type();
|
||||
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(_order, _fill_tuple.algo_meta, order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(_fill_tuple.algo_meta, order_metadata));
|
||||
public string account { get; private set; }
|
||||
public string user => _order.user();
|
||||
public Venue exchange => security.Exchange;
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using mbts.co2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderModel {
|
||||
readonly OrdersModel _order_gateway;
|
||||
|
||||
public LadderModel(SecurityMD source, OrdersModel ordergw, Func<OrderSecurity, bool> order_filter = null) {
|
||||
public LadderModel(SecurityMD source, OrdersModel ordergw, MBOService mbo, Func<OrderSecurity, bool> order_filter = null) {
|
||||
security_md = source;
|
||||
_order_gateway = ordergw;
|
||||
orders = ordergw.on_security_working_orders(source.security.Id);
|
||||
if (order_filter != null)
|
||||
orders = orders.Filter(o => order_filter(new OrderSecurity(source.security, o)), o => o.Key);
|
||||
orders = ordergw.on_security_working_orders_with_piq(source.security.Id);
|
||||
this.mbo_service = mbo;
|
||||
if (order_filter != null) {
|
||||
orders = orders.Filter(order_filter, o => o.order.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CancelType { All, Bids, Asks }
|
||||
@@ -27,7 +27,9 @@ namespace mbts.spark {
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public MDConsumerHandle md => security_md.md;
|
||||
public OrdersModel order_gateway => _order_gateway;
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> orders { get; }
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> orders { get; }
|
||||
|
||||
public MBOService mbo_service { get; }
|
||||
|
||||
public Task cancel_orders(CancelType ct) {
|
||||
switch (ct) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
using mbts;
|
||||
using mbts.config;
|
||||
using mbts.persist;
|
||||
@@ -21,17 +22,26 @@ namespace bts.shell {
|
||||
readonly LadderConfig _ladder_conf;
|
||||
readonly IBus _bus;
|
||||
readonly IWindowManager _wm;
|
||||
readonly MBOService _mbo;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
readonly OrdersModel _orders_model;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
public LadderWindowFactory(IShellModule owner, IBus bus, IWindowManager wm, UserSettingsHub settings_hub, OrdersModel orders_model)
|
||||
public LadderWindowFactory(
|
||||
IShellModule owner,
|
||||
IBus bus,
|
||||
IWindowManager wm,
|
||||
UserSettingsHub settings_hub,
|
||||
OrdersModel orders_model,
|
||||
MBOService mbo)
|
||||
|
||||
: base(owner, "SparkLadder") {
|
||||
_bus = bus;
|
||||
_wm = wm;
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_mbo = mbo;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
@@ -54,7 +64,7 @@ namespace bts.shell {
|
||||
IShellWindow create_ladder_window(SecurityMD md, LadderSettings state_settings) {
|
||||
var id = Interlocked.Increment(ref _ladder_count) % Environment.ProcessorCount;
|
||||
var view = new Lazy<UIElement>(() => {
|
||||
var model = new LadderModel(md, _orders_model);
|
||||
var model = new LadderModel(md, _orders_model, _mbo);
|
||||
var settings_manager = new WindowSettingsManager<LadderSettings>(
|
||||
_settings_hub, state_settings == null || state_settings.use_defaults ? null : state_settings,
|
||||
us => sanitize_ladder_settings(us.ladder),
|
||||
|
||||
@@ -44,11 +44,14 @@ namespace mbts.spark {
|
||||
double _real_price = double.NaN, _phantom_price = double.NaN;
|
||||
uint filled, quantity, num_root_orders;
|
||||
int working_quantity;
|
||||
uint? _min_piq = UInt32.MaxValue;
|
||||
|
||||
public event Action piq_changed;
|
||||
|
||||
LadderModel model => _owner.model;
|
||||
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta)>();
|
||||
Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)> _orders = new Dictionary<uint, (OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta)>();
|
||||
|
||||
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
|
||||
public LadderOrderVM((OrderRecordImmutable, uint?, AlgoData?, AlgoDefinitionMetadata?) op, double real_price, LadderVM owner, IBus bus, IObservable<LadderSettings.BadgeStyle> bs) {
|
||||
_owner = owner;
|
||||
_bus = bus;
|
||||
_bs = bs;
|
||||
@@ -114,7 +117,7 @@ namespace mbts.spark {
|
||||
var single_order_metadata = _owner.model.order_gateway.factory.get_metadata(single_order);
|
||||
border = single_order_metadata.Color.rgb_brush();
|
||||
if (_orders.TryGetValue(single_order.Key, out var opm)) {
|
||||
algo_studio_abbreviation = opm.meta?.abbreviation ?? string.Empty;
|
||||
algo_studio_abbreviation = _sanitize_abbreviation(string.IsNullOrEmpty(opm.meta?.abbreviation) ? _generate_abbreviation_for_algo_studio_algo(opm.algo) : opm.meta?.abbreviation);
|
||||
border = opm.meta?.badge_color?.rgb_color().rgb_brush() ?? border;
|
||||
}
|
||||
if (single_order.order_type().is_bts_algo_type()) {
|
||||
@@ -131,6 +134,34 @@ namespace mbts.spark {
|
||||
|
||||
_set_badge_text();
|
||||
|
||||
string _generate_abbreviation_for_algo_studio_algo(AlgoData? a) {
|
||||
char[] delim = { ' ' };
|
||||
string candidate = string.Empty;
|
||||
if (a.HasValue) {
|
||||
var algo_name = a.Value.name;
|
||||
var words = algo_name.Split(delim);
|
||||
if (words.Length == 1) {
|
||||
var single_word = words[0];
|
||||
if (single_word.Length > 1) {
|
||||
candidate = words[0];
|
||||
} else {
|
||||
candidate = single_word;
|
||||
}
|
||||
} else {
|
||||
candidate = words.Take(2).Select(w => w.Substring(0, 1)).list_string(string.Empty);
|
||||
}
|
||||
}
|
||||
return candidate.ToUpper();
|
||||
}
|
||||
|
||||
string _sanitize_abbreviation(string ab) {
|
||||
string candidate = ab;
|
||||
if (ab.Length > 2) {
|
||||
candidate = candidate.Substring(0, 2);
|
||||
}
|
||||
return candidate.ToUpper();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void _set_badge_text() {
|
||||
@@ -138,12 +169,32 @@ namespace mbts.spark {
|
||||
RaisePropertyChanged(nameof(text_lines));
|
||||
}
|
||||
|
||||
public void add_order((OrderRecordImmutable order, uint? p, AlgoDefinitionMetadata? meta) new_op) {
|
||||
/// <summary>
|
||||
/// Add Order performs an add the first time it's called via the constructor. When it's called, directly
|
||||
/// it really serves as an update/replace. Also note that since the min_piq is a separate piece of state representing
|
||||
/// the lowest piq of the orders the badge represents, we must reevaluate if that has changed on said update.
|
||||
/// </summary>
|
||||
public void add_order((OrderRecordImmutable order, uint? p, AlgoData? algo, AlgoDefinitionMetadata? meta) new_op) {
|
||||
_orders[new_op.order.Key] = new_op;
|
||||
// reevaluate min piq if this order's piq is less
|
||||
if (new_op.p.HasValue) {
|
||||
// if this lovm only represents 1 order and the piq is now different, regardless of what it is that's our piq
|
||||
if (_orders.Count == 1 && new_op.p.Value != min_piq) {
|
||||
min_piq = new_op.p.Value;
|
||||
} else if (new_op.p.Value < min_piq) {
|
||||
min_piq = new_op.p.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_order(uint order_key) {
|
||||
_orders.Remove(order_key);
|
||||
// on a remove, potentially the order w/ the lowest piq has been removed, so
|
||||
// if the min() is now > than the current min_piq, update the min_piq
|
||||
var prospective_piq = _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
|
||||
if (prospective_piq > min_piq) {
|
||||
min_piq = prospective_piq;
|
||||
}
|
||||
}
|
||||
|
||||
//public void update(OrderRecordImmutable neworder) {
|
||||
@@ -155,7 +206,16 @@ namespace mbts.spark {
|
||||
|
||||
public uint security_key => _owner.security.Id;
|
||||
public double price { get => double.IsNaN(_phantom_price) ? _real_price : _phantom_price; }
|
||||
public uint? min_piq => _orders.Select(op => op.Value.p).Where(piq => piq.HasValue).Min();
|
||||
public uint? min_piq {
|
||||
get {
|
||||
return _min_piq;
|
||||
}
|
||||
set {
|
||||
if (Set(ref _min_piq, value)) {
|
||||
piq_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
public double real_price => _real_price;
|
||||
public Side side {
|
||||
get {
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace mbts.spark {
|
||||
//_window_height = backing(nameof(window_height), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 600);
|
||||
//_window_width = backing(nameof(window_width), settings_manager.subscribe_current().OnDispatcher().Select(s => s.zoom_factor), (scale) => scale * 420);
|
||||
position_click_command = new DelegateCommand(() => size_calculator.value = DoubleHelper.safe_convert_to_uint(Math.Abs(position)));
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, MouseButton button)>(send_order);
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
show_book_command = new DelegateCommand(_show_book);
|
||||
show_ticket_command = new DelegateCommand(_show_ticket);
|
||||
@@ -146,24 +146,10 @@ namespace mbts.spark {
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
// we need to hook this up to model.orders which is filtered for just the security
|
||||
model.order_gateway.working_orders_with_piq.OnDispatcher().Subscribe(working => {
|
||||
model.orders.OnDispatcher().Subscribe(working => {
|
||||
// Here we manage N orders to 1 price slot in the ladder
|
||||
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
|
||||
|
||||
// In master we originally had something similar to this snippet to restrict the foreach to just the orders
|
||||
// for this ladder. In 1944, at one point we made use of model.orders, which made use of the on_security_working_orders demuxer.
|
||||
// When we merged PIQ into 1944, we moved from that demuxer to working_orders_with_piq, so we reintroduce this little snippet here
|
||||
// to once again restrict the processing to those working orders that match the criteria. Check with John to see if we can
|
||||
// make a demuxer from working_orders_with_piq, which is an IReadOnlyDictionary; that probably won't work, but I have no doubt
|
||||
// there is a better way.
|
||||
var ladders_orders =
|
||||
working.Values
|
||||
.Where(o =>
|
||||
o.order.Def.SecurityKey == security.Id &&
|
||||
(o.order.HasState && o.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible))
|
||||
.ToReadOnlyDictionary(os => os.order.Key, os => os);
|
||||
|
||||
_cache = ladders_orders;
|
||||
_cache = working.state;
|
||||
|
||||
foreach (var os in _cache.Values) {
|
||||
var wo = os.order;
|
||||
@@ -180,9 +166,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
if (_slot_price_vs_orders.TryGetValue(slot_price_target, out LadderOrderVM slot_vm)) {
|
||||
slot_vm.add_order((wo, os.piq, os.meta));
|
||||
slot_vm.add_order((wo, os.piq, os.algo, os.meta));
|
||||
} else {
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
|
||||
slot_vm = new LadderOrderVM((wo, os.piq, os.algo, os.meta), slot_price_target, this, _bus, _settings_manager.subscribe_current().Select(s => s.badge_style));
|
||||
_slot_price_vs_orders[slot_price_target] = slot_vm;
|
||||
}
|
||||
|
||||
@@ -246,7 +232,7 @@ namespace mbts.spark {
|
||||
// we return only entries who have piqs and whose piqs are not uint.Max
|
||||
// uint.Max happens when the server is restarted but the client is still up
|
||||
// maybe long term we don't show this data while the exchange connection is down
|
||||
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
|
||||
public IEnumerable<(double, uint)> price_vs_piq => _slot_price_vs_orders.Values.Where(result => result.min_piq.HasValue && result.min_piq.Value != uint.MaxValue).Select(o => (o.price, o.min_piq.Value));
|
||||
public bool is_synthetic => _model.security.ContractCase == Security.ContractOneofCase.Synthetic;
|
||||
|
||||
Property<string> _status;
|
||||
@@ -326,7 +312,7 @@ namespace mbts.spark {
|
||||
public uint volume_column_width { get => _volume_column_width.Value; set => _volume_column_width.set(value); }
|
||||
internal LadderModel model => _model;
|
||||
internal IObservable<LadderSettings> settings => _settings_manager.subscribe_current();
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoDefinitionMetadata? meta) op) {
|
||||
internal OrderRecordImmutable get_root_at_price((OrderRecordImmutable order, uint? _, AlgoData? __, AlgoDefinitionMetadata? meta) op) {
|
||||
var order = op.order;
|
||||
double p = order.price();
|
||||
foreach (var o in lineage(order.ParentKey)) {
|
||||
@@ -362,13 +348,15 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public async void send_order((double price, Side? side, bool size_from_alternate) args) {
|
||||
public async void send_order((double price, Side? side, MouseButton buton) args) {
|
||||
if (args.buton == MouseButton.Middle) return;
|
||||
var size_from_alternate = args.buton == MouseButton.Right;
|
||||
if (order_properties_vm == null)
|
||||
return;
|
||||
var s = _settings_manager.current_settings();
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (args.size_from_alternate && s.alternate_order_size == 0))
|
||||
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0))
|
||||
return;
|
||||
else if (!args.size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
else if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
|
||||
size_calculator.flash_error();
|
||||
} else {
|
||||
var order = order_properties_vm.create(args.side.Value);
|
||||
@@ -385,7 +373,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
uint size = args.size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
|
||||
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
|
||||
await _model.order_gateway.send_order(order, args.price, size);
|
||||
if (auto_clear)
|
||||
size_calculator.value = 0;
|
||||
@@ -413,12 +401,13 @@ namespace mbts.spark {
|
||||
|
||||
}
|
||||
|
||||
void _show_book() =>
|
||||
_bus.post(SparkTags.ShowOrderBookTag, new bts.shell.orderbook.OrderBookStartupData {
|
||||
filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
}
|
||||
});
|
||||
void _show_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_ticket() {
|
||||
if (order_properties_vm != null)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.md.auxiliary;
|
||||
using bts.md;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using bts.config;
|
||||
using mbts.co2;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
@@ -17,11 +19,13 @@ using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using mbts.config;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
@@ -56,7 +60,7 @@ namespace mbts.spark {
|
||||
LadderTactileAdorner _tactile_adorner;
|
||||
ulong _prices_updated;
|
||||
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
bool _price_dirty = false;
|
||||
bool _price_dirty = false, _piq_dirty = false;
|
||||
double[] _price_list = null;
|
||||
IComparer<double> _price_comparer;
|
||||
double _last_origin_price;
|
||||
@@ -257,11 +261,15 @@ namespace mbts.spark {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (LadderOrderVM item in e.NewItems) {
|
||||
_ordershost.add_order(item);
|
||||
item.piq_changed += _set_piq_dirty;
|
||||
_set_piq_dirty();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (LadderOrderVM item in e.OldItems) {
|
||||
_ordershost.remove_order(item);
|
||||
item.piq_changed -= _set_piq_dirty;
|
||||
_set_piq_dirty();
|
||||
}
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
@@ -272,6 +280,9 @@ namespace mbts.spark {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
void _set_piq_dirty() { _piq_dirty = true; }
|
||||
|
||||
#endregion
|
||||
|
||||
Column[] columns { get; set; } = new Column[0];
|
||||
@@ -313,25 +324,56 @@ namespace mbts.spark {
|
||||
Focus();
|
||||
var point = e.GetPosition(this);
|
||||
var column = _column_for_point(point);
|
||||
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle)
|
||||
_scroll_to_center();
|
||||
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle) {
|
||||
show_mbo_popup(point).ignore();
|
||||
}
|
||||
else if (_settings != null) {
|
||||
var price = price_for_point(point);
|
||||
Side? bs = null;
|
||||
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
|
||||
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
|
||||
var pbs = (price, bs, e.ChangedButton == MouseButton.Right);
|
||||
var pbs = (price, bs, e.ChangedButton);
|
||||
if (Command != null && Command.CanExecute(pbs)) {
|
||||
if (bs.HasValue) {
|
||||
using (_tactile_adorner.show_busy(_row_for_point(point), bs.Value == Side.Buy ? ColumnType.Bid : ColumnType.Ask))
|
||||
await Command.ExecuteAsync(pbs);
|
||||
} else
|
||||
}
|
||||
else {
|
||||
Command.Execute(pbs);
|
||||
}
|
||||
}
|
||||
}
|
||||
base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
private async Task show_mbo_popup(Point point) {
|
||||
var query = MBOPopupModel.default_query.with(
|
||||
security_id: _vm.model.security.Id,
|
||||
price_level: price_for_point(point)
|
||||
);
|
||||
var model = new MBOPopupModel(_vm.model.mbo_service, _vm.model.security, query);
|
||||
// Load the first set of orders so we know if there are any before showing the popup
|
||||
var result = await model.load_orders();
|
||||
if (result.success && result.value.orders.Count > 0) {
|
||||
var view = new MBOPopupView(model, result.value);
|
||||
var popup = new AutoClosePopup {
|
||||
PlacementTarget = this,
|
||||
PlacementRectangle = new Rect(point, point),
|
||||
IsOpen = true,
|
||||
Child = new Border {
|
||||
BorderBrush = Brushes.Black,
|
||||
BorderThickness = new Thickness(0.5),
|
||||
Child = new ScrollViewer {
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
|
||||
HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
|
||||
Content = view,
|
||||
}
|
||||
}
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
var column = _column_for_point(e.GetPosition(this));
|
||||
if (column.column_type == ColumnType.Price)
|
||||
@@ -485,6 +527,7 @@ namespace mbts.spark {
|
||||
_prices_updated = 0;
|
||||
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
|
||||
_price_dirty = true;
|
||||
_piq_dirty = true;
|
||||
_vm.on_tick += _tick;
|
||||
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
|
||||
_volume_sub = _vm.model.order_gateway.md_auxiliary_model.security_volume_at_price(security.Id).Subscribe(t => _volume_container.write_value(t.state.Values));
|
||||
@@ -580,8 +623,10 @@ namespace mbts.spark {
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
}
|
||||
// TODO - detect changes in PIQ to selectively redraw
|
||||
_refresh_piq();
|
||||
if (_piq_dirty) {
|
||||
_refresh_piq();
|
||||
_piq_dirty = false;
|
||||
}
|
||||
_refresh_volume_at_price();
|
||||
_last_origin_price = _mkt_break_lines.origin;
|
||||
_price_dirty = false;
|
||||
@@ -622,8 +667,7 @@ namespace mbts.spark {
|
||||
if (!_settings.piq_column.show)
|
||||
return;
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
var piq_list = _vm.price_vs_piq;
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, piq_list.ToArray(), _settings.row_height, security.ContractSpec, _settings.piq_column);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, origin_index, _price_list, _vm.price_vs_piq, _settings.row_height, security.ContractSpec, _settings.piq_column);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
|
||||
@@ -72,8 +72,7 @@ namespace mbts.spark {
|
||||
panel.Children.Remove(item);
|
||||
}
|
||||
}
|
||||
} //else
|
||||
//Log.Ui.warn($"Failed to remove order ({order.id}): order not in {nameof(LadderOrdersHost)}'s {nameof(_orders)}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void clear_orders() {
|
||||
@@ -192,11 +191,8 @@ namespace mbts.spark {
|
||||
Margin = Badge.DefaultMargin,
|
||||
Orientation = Orientation.Horizontal,
|
||||
MaxWidth = _col_width
|
||||
//MaxHeight = _row_height,
|
||||
//ClipToBounds = true
|
||||
};
|
||||
|
||||
//var view_box = new Viewbox { Child = panel, Width = _col_width, Height = _row_height };
|
||||
_position_panel(panel, price);
|
||||
_stacks[price] = new PriceCellComponents(panel);
|
||||
Children.Add(panel);
|
||||
|
||||
@@ -288,22 +288,22 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
// this is basically a copy of redner_sizes up above
|
||||
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, (double price, uint piq)[] piqs, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
|
||||
internal static void render_piqs(DrawingVisual dv, GlyphContext glyph_context, int origin_index, double[] prices, IEnumerable<(double price, uint piq)> pvp, double row_height, ContractSpecificationImmutable spec, LadderColumnSettings settings) {
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Length)) {
|
||||
if (!prices.within_range(origin_index) || piqs.is_null_or_empty())
|
||||
if (!prices.within_range(origin_index) || pvp.is_null_or_empty())
|
||||
return;
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
for (int i = 0; i < piqs.Length; i++) {
|
||||
double price = piqs[i].price;
|
||||
foreach (var entry in pvp) {
|
||||
double price = entry.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
int tick_diff = spec.tickdiff_unchecked(curr_price, price);
|
||||
curr_index += tick_diff;
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Length) {
|
||||
var gr = format_text(piqs[i].piq.ToString(), curr_index, row_height, settings, glyph_context);
|
||||
var gr = format_text(entry.piq.ToString(), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Top"/>
|
||||
</Style>
|
||||
</TabControl.ItemContainerStyle>
|
||||
<!-- Fonts and Colors Tab -->
|
||||
<TabItem Header="Fonts and Colors" VerticalAlignment="Stretch">
|
||||
<!-- Column Settings Tab -->
|
||||
<TabItem Header="Column Settings" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="27"/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell.orderbook;
|
||||
using bts.utils;
|
||||
@@ -27,6 +28,7 @@ using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
using System.Windows.Input;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
@@ -63,6 +65,7 @@ namespace mbts.spark {
|
||||
partial class OrderBookVM {
|
||||
public class OrderBookColumnCollection : ObservableCollection<ColumnBase>, IValueFactory<(FilterStates filters, IReadOnlyList<SortingCriteria> sorting)>, IDisposable {
|
||||
readonly List<ICVOrderBookColumn> _active_comparers = new List<ICVOrderBookColumn>();
|
||||
CompositeDisposable _resources = new CompositeDisposable();
|
||||
|
||||
public OrderBookColumnCollection(IBus bus, OrdersModel model, IObservable<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> order_securities) {
|
||||
// security
|
||||
@@ -161,12 +164,37 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.time_in_force)),
|
||||
DataGridFilterKnownTemplates.EnumValues,
|
||||
_wrap_comparer(r => r.time_in_force)));
|
||||
// (algo items)
|
||||
var algo_items_source = model.subscribe_all_algos().SelectKeyValue(AlgoDataVM.from_data, d => AlgoID.from_algo_key(d.data.key)).OnDispatcher();
|
||||
var algo_items_comp = AlgoCollectionHelper.algo_data_vm_comparer.to_non_generic();
|
||||
var algo_items_group_desc = new PropertyGroupDescription(nameof(AlgoDataVM.group));
|
||||
// order type
|
||||
Add(order_type_column = new OrderBookOrderTypeColumn(
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderType>(
|
||||
utilities.OrderDataHelper.all_order_types,
|
||||
ort => ort.order.order_type(),
|
||||
nameof(OrderBookRowVM.order_type))));
|
||||
Columns.OrderType,
|
||||
nameof(OrderBookRowVM.badge_fill),
|
||||
nameof(OrderBookRowVM.display_name),
|
||||
_wrap_comparer(r => r.order_type),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.algo.GetValueOrDefault()),
|
||||
o => AlgoID.from_order_def(o.order.Def),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var order_type_filter_observer, out var order_type_items_view)));
|
||||
_resources.Add(algo_items_source.Subscribe(order_type_filter_observer));
|
||||
order_type_items_view.CustomSort = algo_items_comp;
|
||||
order_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
// root algo type
|
||||
Add(root_algo_type_column = new OrderBookOrderTypeColumn(
|
||||
Columns.RootAlgoType,
|
||||
nameof(OrderBookRowVM.root_algo_badge_fill),
|
||||
nameof(OrderBookRowVM.root_algo_display_name),
|
||||
_wrap_comparer(r => r.order_tuple.root_algo.value.CustomCase),
|
||||
DiscreteValuesFilterVM.CreateTableUpdate<OrderSecurityAlgoMeta, AlgoDataVM, AlgoID>(
|
||||
o => new AlgoDataVM(o.root_algo),
|
||||
o => AlgoID.from_algo_key(o.root_algo.key),
|
||||
vm => AlgoID.from_algo_key(vm.data.key), out var root_algo_type_observer, out var root_algo_type_items_view),
|
||||
DefaultColumnHeaders.RootAlgoType));
|
||||
_resources.Add(algo_items_source.Subscribe(root_algo_type_observer));
|
||||
root_algo_type_items_view.CustomSort = algo_items_comp;
|
||||
root_algo_type_items_view.GroupDescriptions.Add(algo_items_group_desc);
|
||||
// order sub type
|
||||
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
|
||||
Columns.SubType,
|
||||
@@ -307,6 +335,7 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<Venue[]> exchange_column { get; }
|
||||
public SimpleTextColumn<TimeInForce[]> time_in_force_column { get; }
|
||||
public OrderBookOrderTypeColumn order_type_column { get; }
|
||||
public OrderBookOrderTypeColumn root_algo_type_column { get; }
|
||||
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
|
||||
public SideColumn side_column { get; }
|
||||
public SizeColumn qty_column { get; }
|
||||
@@ -333,6 +362,7 @@ namespace mbts.spark {
|
||||
exchanges = exchange_column.filter.get_state(),
|
||||
tifs = time_in_force_column.filter.get_state(),
|
||||
order_types = order_type_column.filter.get_state(),
|
||||
root_algo_types = root_algo_type_column.filter.get_state(),
|
||||
order_sub_types = sub_type_column.filter.get_state(),
|
||||
sides = side_column.filter.get_state(),
|
||||
qty = qty_column.filter.get_state(),
|
||||
@@ -362,6 +392,7 @@ namespace mbts.spark {
|
||||
filled_qty_column.filter.load_state(fs.qty_filled);
|
||||
side_column.filter.load_state(fs.sides);
|
||||
order_type_column.filter.load_state(fs.order_types);
|
||||
root_algo_type_column.filter.load_state(fs.root_algo_types);
|
||||
sub_type_column.filter.load_state(fs.order_sub_types);
|
||||
time_in_force_column.filter.load_state(fs.tifs);
|
||||
exchange_column.filter.load_state(fs.exchanges);
|
||||
@@ -424,6 +455,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _resources);
|
||||
foreach (var col in this.OfType<ICVOrderBookColumn>())
|
||||
(col.filter as IDisposable)?.Dispose();
|
||||
Clear();
|
||||
@@ -534,7 +566,7 @@ namespace mbts.spark {
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
|
||||
font_brush, null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
RenderUtils.get_foreground(row, font_brush), null, horizontal_alignment, VerticalAlignment.Center, DefaultCellPadding);
|
||||
}
|
||||
|
||||
#region Explicit Column Defs
|
||||
@@ -566,21 +598,32 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
public OrderBookOrderTypeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, OrderType[]> filter)
|
||||
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
|
||||
|
||||
public OrderBookOrderTypeColumn(Columns key, string badge_fill_binding_path, string display_name_binding_path, IComparer<OrderBookRowVM> row_comparer, IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter, string header = DefaultColumnHeaders.OrderType)
|
||||
: base(key, new Binding(badge_fill_binding_path), new Binding(display_name_binding_path), filter, header) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
font_weight_binding = RenderUtils.get_font_weight_binding();
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
_cached_sort_direction = SortDirection;
|
||||
this.comparer = row_comparer;
|
||||
}
|
||||
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, OrderType[]> filter { get; }
|
||||
public IComparer<OrderBookRowVM> comparer => _wrap_comparer(r => r.order_type);
|
||||
public new IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, AlgoID[]> filter { get; }
|
||||
public override string filter_template => DataGridFilterKnownTemplates.AlgoType;
|
||||
public IComparer<OrderBookRowVM> comparer { get; }
|
||||
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection { get => _cached_sort_direction; set => SortDirection = value; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
var view = base.view_factory();
|
||||
view.SetBinding(OrderTypeItem.ForegroundProperty, _foreground_binding);
|
||||
return view;
|
||||
}
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
base.OnSortDirectionChanged(new_value);
|
||||
@@ -612,7 +655,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
|
||||
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding();
|
||||
readonly BindingBase _font_weight_binding = RenderUtils.get_font_weight_binding(), _foreground_binding;
|
||||
readonly IValueConverter _expander_visiblilty_converter = new ui.converters.BooleanToVisibilityConverter { VisibilityForFalse = Visibility.Hidden };
|
||||
readonly IValueConverter _margin_converter = new AnonymousValueConverter<int, Thickness>(level => new Thickness(10 * level, 0, 0, 0));
|
||||
|
||||
@@ -621,6 +664,7 @@ namespace mbts.spark {
|
||||
public SecurityColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, SecurityCriteria> filter) : base(Columns.Security) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
_foreground_binding = RenderUtils.get_foreground_binding(this);
|
||||
Header = DefaultColumnHeaders.Security;
|
||||
Width = DefaultColumnWidths.Security;
|
||||
_cached_sort_direction = SortDirection;
|
||||
@@ -647,7 +691,7 @@ namespace mbts.spark {
|
||||
security_view.SetBinding(TextBlock.FontSizeProperty, new Binding(nameof(font_size)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.TextProperty, nameof(OrderBookRowVM.security_text));
|
||||
security_view.SetBinding(TextBlock.HorizontalAlignmentProperty, new Binding(nameof(horizontal_alignment)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.ForegroundProperty, new Binding(nameof(font_brush)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.ForegroundProperty, _foreground_binding);
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
|
||||
//
|
||||
@@ -742,7 +786,7 @@ namespace mbts.spark {
|
||||
// create the attach button and assign the menu to it
|
||||
var attach_button = new Button() {
|
||||
Name = "attach_button",
|
||||
Content = "A",
|
||||
Content = new Image() { Source = Icons.AttachAlgos },
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
Focusable = false,
|
||||
@@ -864,7 +908,7 @@ namespace mbts.spark {
|
||||
|
||||
var detach_button = new Button() {
|
||||
Name = "detach_button",
|
||||
Content = "D",
|
||||
Content = new Image() { Source = Icons.DetachAlgos },
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
Focusable = false,
|
||||
@@ -1028,6 +1072,10 @@ namespace mbts.spark {
|
||||
popup.SetBinding(Popup.DataContextProperty, new Binding("PlacementTarget.DataContext") {
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.Self)
|
||||
});
|
||||
popup.Closed += (s, e) => {
|
||||
if (popup.try_find_ancestor_of_type(out MiniOrderBookView mini_ob) && mini_ob.DataContext is OrderBookVM ob_vm)
|
||||
ob_vm.is_attaching_orders = false;
|
||||
};
|
||||
|
||||
var the_whole_thing = new Grid();
|
||||
the_whole_thing.Children.Add(update_button);
|
||||
@@ -1115,19 +1163,24 @@ namespace mbts.spark {
|
||||
static class RenderUtils {
|
||||
public static FontWeight
|
||||
root_font_weight = FontWeights.Normal,
|
||||
child_font_weight = FontWeights.Light;
|
||||
child_font_weight = FontWeights.Normal;
|
||||
static readonly IMultiValueConverter _foreground_converter =
|
||||
ValueConverter.create<bool, Brush, Brush>(_get_foreground);
|
||||
|
||||
public static double font_size = FontSizes.Small;
|
||||
|
||||
public static readonly IValueConverter
|
||||
_sub_type_text_converter = new ui.spark.converters.OrderSubTypeDisplayTextConverter(),
|
||||
_side_brush_converter = new AnonymousValueConverter<Side, Brush>(s => s == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask),
|
||||
_font_weight_converter = new AnonymousValueConverter<bool, FontWeight>(is_child => is_child ? FontWeights.Light : FontWeights.Normal);
|
||||
_font_weight_converter = new AnonymousValueConverter<bool, FontWeight>(is_child => is_child ? child_font_weight : root_font_weight);
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
_price_text_converter = new ui.spark.converters.TickDisplayConverter(),
|
||||
_background_converter = new AnonymousMultiValueConverter<bool, OrderStatus, Brush>(
|
||||
(selected, status) => selected || status != OrderStatus.Working ? Brushes.Transparent : BTSColors.Orders.working_order_background_brush);
|
||||
|
||||
static readonly bool _animate_child_order_rows = mbts.config.Configuration.get<bts.config.OrderBookOptions>().animate_child_order_rows;
|
||||
|
||||
public static BindingBase _get_background_binding() {
|
||||
var mb = new MultiBinding { Converter = _background_converter };
|
||||
mb.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
|
||||
@@ -1135,9 +1188,29 @@ namespace mbts.spark {
|
||||
return mb;
|
||||
}
|
||||
|
||||
public static Brush get_side_brush(OrderBookRowVM r) => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask;
|
||||
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
|
||||
}
|
||||
public static BindingBase get_foreground_binding(ColumnBase col) {
|
||||
var col_font_brush_binding = new Binding(nameof(ColumnBase.font_brush)) { Source = col };
|
||||
if (!_animate_child_order_rows)
|
||||
return col_font_brush_binding;
|
||||
var mb = new MultiBinding { Converter = _foreground_converter };
|
||||
mb.Bindings.Add(new Binding(nameof(OrderBookRowVM.is_child)));
|
||||
mb.Bindings.Add(col_font_brush_binding);
|
||||
return mb;
|
||||
}
|
||||
|
||||
public static Brush get_side_brush(OrderBookRowVM r) => get_foreground(r, r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
public static BindingBase get_font_weight_binding() => new Binding(nameof(OrderBookRowVM.is_child)) { Converter = _font_weight_converter };
|
||||
|
||||
public static Brush get_foreground(OrderBookRowVM row, Brush brush) =>
|
||||
_animate_child_order_rows ? _get_foreground(row.is_child, brush) : brush;
|
||||
|
||||
static Brush _get_foreground(bool is_child, Brush brush) {
|
||||
if (is_child) {
|
||||
brush = brush.Clone();
|
||||
brush.Opacity = 0.75;
|
||||
}
|
||||
return brush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,18 +18,16 @@ namespace mbts.spark {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
Brush _badge_fill;
|
||||
Color? _root_algo_badge_color_backing;
|
||||
Brush _badge_fill, _root_algo_badge_fill;
|
||||
IRaiseCanExecuteChangedCommand _show_order_modify_cmd, _toggle_expanded_cmd, _cancel_cmd, _detach_command, _attach_command, _update_command, _modify_order_quantity_andor_price, _revert_prospective_order_quantity_andor_price;
|
||||
bool _is_parent, _is_expanded, _is_selected, _is_attachable, _is_detachable;
|
||||
int _depth_level;
|
||||
SecurityImmutable? _security => order_tuple.security;
|
||||
|
||||
|
||||
// for mini-ob
|
||||
uint _prospective_quantity;
|
||||
double _prospective_price;
|
||||
|
||||
|
||||
public OrderBookRowVM(OrderBookVM owner, OrderSecurityAlgoMeta order_tuple, bool is_expanded) {
|
||||
_owner = owner;
|
||||
this.order_tuple = order_tuple;
|
||||
@@ -37,11 +35,6 @@ namespace mbts.spark {
|
||||
_is_expanded = is_expanded;
|
||||
prospective_quantity = this.quantity;
|
||||
prospective_price = this.price;
|
||||
this.invalidated += OrderBookRowVM_invalidated;
|
||||
}
|
||||
|
||||
private void OrderBookRowVM_invalidated() {
|
||||
modify_order_quantity_andor_price?.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
//// This takes care of the case where the OB has all rows visible
|
||||
@@ -52,8 +45,8 @@ namespace mbts.spark {
|
||||
//// handler
|
||||
public IObservable<OrderSecurityAlgoMeta> subscribe_data() {
|
||||
return Observable.FromEvent<Action, OrderSecurityAlgoMeta>(
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => () => cb(order_tuple),
|
||||
cb => invalidated += cb,
|
||||
cb => invalidated -= cb);
|
||||
}
|
||||
|
||||
@@ -65,7 +58,7 @@ namespace mbts.spark {
|
||||
public OrderSecurityAlgoMeta order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order;
|
||||
public OrderMetadata order_metadata => _owner.get_metadata(this);
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = order_tuple.badge_fill(order_metadata));
|
||||
public Brush badge_fill => _badge_fill ?? (_badge_fill = OrderUtils.badge_fill(order_tuple.meta, order_metadata));
|
||||
public uint order_key => order.Key;
|
||||
public uint parent_order_key => order.ParentKey;
|
||||
public uint security_key => order.security_key();
|
||||
@@ -101,8 +94,14 @@ namespace mbts.spark {
|
||||
public bool is_non_terminal => order.is_non_terminal();
|
||||
public bool is_attachable { get => _is_attachable; set => SetVisual(ref _is_attachable, value); }
|
||||
public bool is_detachable { get => _is_detachable; set => SetVisual(ref _is_detachable, value); }
|
||||
|
||||
public string filled_status_desc => order.filled_status_description(security.spec_or_default());
|
||||
|
||||
// Root Algo
|
||||
public CustomOrderDefinition.CustomOneofCase root_algo_order_type => order_tuple.root_algo.value.CustomCase;
|
||||
public OrderMetadata root_algo_order_metadata => _owner.get_metadata(root_algo_order_type);
|
||||
public Brush root_algo_badge_fill => _root_algo_badge_fill ?? (_root_algo_badge_fill = new SolidColorBrush(_root_algo_badge_color));
|
||||
public string root_algo_display_name => order_tuple.root_algo.name;
|
||||
|
||||
// Tree
|
||||
public bool is_parent { get => _is_parent; set => Set(ref _is_parent, value); }
|
||||
public bool is_child => order.has_parent();
|
||||
@@ -148,32 +147,38 @@ namespace mbts.spark {
|
||||
public ICommand cancel_command => _cancel_cmd ??
|
||||
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working || status == OrderStatus.Disconnected));
|
||||
|
||||
public ICommand modify_order_quantity_andor_price => _modify_order_quantity_andor_price ??
|
||||
(_modify_order_quantity_andor_price = new DelegateCommand(
|
||||
() => {
|
||||
if (is_qty_dirty && is_price_dirty) {
|
||||
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
|
||||
} else if (is_qty_dirty) {
|
||||
_owner.modify_order_quantity(this, prospective_quantity);
|
||||
} else if (is_price_dirty) {
|
||||
_owner.modify_order_price(this, this.prospective_price);
|
||||
public ICommand modify_order_quantity_andor_price {
|
||||
get {
|
||||
if (_modify_order_quantity_andor_price == null) {
|
||||
_modify_order_quantity_andor_price = new DelegateCommand(execute, can_execute);
|
||||
invalidated += _modify_order_quantity_andor_price.RaiseCanExecuteChanged;
|
||||
// impl
|
||||
void execute() {
|
||||
if (is_qty_dirty && is_price_dirty) {
|
||||
_owner.modify_order_price_and_quantity(this, prospective_price, prospective_quantity);
|
||||
} else if (is_qty_dirty) {
|
||||
_owner.modify_order_quantity(this, prospective_quantity);
|
||||
} else if (is_price_dirty) {
|
||||
_owner.modify_order_price(this, this.prospective_price);
|
||||
}
|
||||
_unselect_me();
|
||||
}
|
||||
_unselect_me();
|
||||
},
|
||||
bool can_execute() => status == OrderStatus.Working && (is_price_dirty || is_qty_dirty);
|
||||
}
|
||||
return _modify_order_quantity_andor_price;
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
|
||||
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
|
||||
() => {
|
||||
prospective_price = price;
|
||||
prospective_quantity = quantity;
|
||||
},
|
||||
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
|
||||
)
|
||||
);
|
||||
|
||||
public ICommand revert_prospective_order_quantity_andor_price => _revert_prospective_order_quantity_andor_price ??
|
||||
(_revert_prospective_order_quantity_andor_price = new DelegateCommand(
|
||||
() => {
|
||||
prospective_price = price;
|
||||
prospective_quantity = quantity;
|
||||
},
|
||||
() => this.status == OrderStatus.Working && (is_price_dirty || is_qty_dirty)
|
||||
)
|
||||
);
|
||||
|
||||
public ICommand detach_command => _detach_command ??
|
||||
(_detach_command = new DelegateCommand(() => {
|
||||
_owner.detach_orders(this);
|
||||
@@ -193,6 +198,18 @@ namespace mbts.spark {
|
||||
_select_me();
|
||||
})
|
||||
);
|
||||
|
||||
// private props
|
||||
Color _root_algo_badge_color {
|
||||
get => _root_algo_badge_color_backing ?? (_root_algo_badge_color_backing = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata)).Value;
|
||||
set {
|
||||
if (_root_algo_badge_color_backing != value) {
|
||||
_root_algo_badge_color_backing = value;
|
||||
_root_algo_badge_fill = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IHierarchicalRow
|
||||
bool IHierarchicalRow.is_parent => is_parent;
|
||||
bool IHierarchicalRow.is_expanded => is_expanded;
|
||||
@@ -262,6 +279,15 @@ namespace mbts.spark {
|
||||
// display name
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
// root badge fill
|
||||
if (_root_algo_badge_color_backing.HasValue) {
|
||||
// only need to invalidate it if it had a value (it is lazily instantiated)
|
||||
_root_algo_badge_color = OrderUtils.badge_color(order_tuple.root_meta, root_algo_order_metadata);
|
||||
RaisePropertyChanged(nameof(root_algo_badge_fill));
|
||||
}
|
||||
// root display name
|
||||
if (old_order_tuple.root_algo.name != order_tuple.root_algo.name)
|
||||
RaisePropertyChanged(nameof(root_algo_display_name));
|
||||
// status
|
||||
if ((Columns.Status & dirty) != 0)
|
||||
RaisePropertyChanged(nameof(status));
|
||||
@@ -288,7 +314,6 @@ namespace mbts.spark {
|
||||
// this will update the cancel column in the mini ob
|
||||
cancel_command.TryRaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
// always refresh attach and detach b/c
|
||||
// conditionally raise invalidated
|
||||
if ((dirty & ViewRefresh.invalidate_mask) != 0)
|
||||
|
||||
@@ -3,6 +3,7 @@ using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.md;
|
||||
using bts.shell;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
@@ -64,7 +65,14 @@ namespace mbts.spark {
|
||||
_detachable_selected_orders = new HashSet<uint>();
|
||||
FilterData _cached_filter_data;
|
||||
|
||||
public OrderBookVM(IObservable<string> owner_title, IBus bus, INotificationService notification_service, OrdersModel model, IWindowSettingsManager<OrderBookGridSettings> settings_manager, bool rows_are_initially_expanded = false) {
|
||||
public OrderBookVM(
|
||||
IObservable<string> owner_title,
|
||||
IBus bus,
|
||||
INotificationService notification_service,
|
||||
OrdersModel model,
|
||||
IWindowSettingsManager<OrderBookGridSettings> settings_manager,
|
||||
bool rows_are_initially_expanded = false) {
|
||||
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_selected_params_mon = new SelectedOrderParamsMon(model.dynamic_helper);
|
||||
@@ -160,7 +168,7 @@ namespace mbts.spark {
|
||||
security_plurality = p;
|
||||
RaisePropertyChanged(nameof(security_plurality));
|
||||
}),
|
||||
model.on_order_tuples.OnDispatcher().Subscribe(t => {
|
||||
model.on_order_tuples_with_root.OnDispatcher().Subscribe(t => {
|
||||
_cached_orders = t.state;
|
||||
_row_collection.update(t.added, t.changed, t.removed.Select(o => o.order.Key));
|
||||
}),
|
||||
@@ -248,13 +256,20 @@ namespace mbts.spark {
|
||||
var order_tups = selected_rows.Select(r => r.order_tuple).ToList();
|
||||
var mod_algo_vm = attach_vm.algo_vm.algo_root;
|
||||
await Task.WhenAll(order_tups.Select(t => {
|
||||
var algo = t.order.Def.CustomDef.CloneToMutable();
|
||||
var algo = t.order.Def.CustomDef.actual().CloneToMutable();
|
||||
mod_algo_vm.assign_to(algo);
|
||||
if (algo_template_key != 0) {
|
||||
algo = new CustomOrderDefinition {
|
||||
Composite = new CompositeOrderDefinition {
|
||||
AlgoTemplateKey = algo_template_key,
|
||||
Definition = algo
|
||||
}
|
||||
};
|
||||
}
|
||||
return _model.send_order(
|
||||
t.order.Def.with(
|
||||
attach_target_order_key: t.order.Key,
|
||||
custom_def: algo,
|
||||
algo_template_key: algo_template_key
|
||||
custom_def: algo
|
||||
),
|
||||
double.IsNaN(price) ? t.order.price() : price,
|
||||
size ?? t.order.quantity());
|
||||
@@ -268,15 +283,14 @@ namespace mbts.spark {
|
||||
Disposable.replace(ref attach_vm_hooks,
|
||||
Disposable.Create(
|
||||
connect_common_selected_params(attach_vm),
|
||||
_selected_params_mon.subscribe_algos().Subscribe(algos => {
|
||||
attach_vm.algo_vm.observe_algo_root().Subscribe(algo_root => {
|
||||
bool show_price, show_size;
|
||||
using (var en = algos.Select(a => a.CustomCase).Distinct().GetEnumerator()) {
|
||||
if (show_price = show_size = en.MoveNext()) {
|
||||
do {
|
||||
if (show_price) show_price = AttachConf.show_price(en.Current);
|
||||
if (show_size) show_size = AttachConf.show_size(en.Current);
|
||||
} while ((show_price || show_size) && en.MoveNext());
|
||||
}
|
||||
if (algo_root == null) {
|
||||
show_price = false;
|
||||
show_size = false;
|
||||
} else {
|
||||
show_price = AttachConf.show_price(algo_root.order_type);
|
||||
show_size = AttachConf.show_size(algo_root.order_type);
|
||||
}
|
||||
attach_vm.price_permissions = bool_to_perm(show_price);
|
||||
attach_vm.size_permissions = bool_to_perm(show_size);
|
||||
@@ -285,7 +299,7 @@ namespace mbts.spark {
|
||||
}),
|
||||
// custom templates (created by user)
|
||||
_model.algo_model.definitions.OnDispatcher().Subscribe(u =>
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots as ObservableKeyedCollection<IAlgoVM, AlgoKey>, u, mk_factory(true)))));
|
||||
AlgoCollectionHelper.update_persisted_definition_wrappers(attach_vm.algo_vm.algo_roots_index, u, mk_factory(true)))));
|
||||
// vm factory
|
||||
Func<AlgoData, AlgoDefinitionVM> mk_factory(bool connect) =>
|
||||
fd => {
|
||||
@@ -532,9 +546,23 @@ namespace mbts.spark {
|
||||
public bool try_get_parent(OrderBookRowVM row, out OrderSecurityAlgoMeta parent) =>
|
||||
_cached_orders.TryGetValue(row.parent_order_key, out parent);
|
||||
|
||||
public OrderRecordImmutable get_root(OrderRecordImmutable r) {
|
||||
while (r.ParentKey != 0 && _cached_orders.TryGetValue(r.ParentKey, out var parent_osam)) {
|
||||
r = parent_osam.order;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
internal void detach_orders(OrderBookRowVM order) =>
|
||||
_detach_orders_async(new[] { order }).ignore();
|
||||
|
||||
public OrderSecurityAlgoMeta get_ordersecurityalgometa(OrderRecordImmutable order) {
|
||||
_cached_orders.TryGetValue(order.Key, out var osam);
|
||||
return osam;
|
||||
}
|
||||
|
||||
public OrderMetadata get_metadata(CustomOrderDefinition.CustomOneofCase order_type) => _model.factory.get_metadata(order_type);
|
||||
|
||||
public OrderMetadata get_metadata(OrderBookRowVM row) => _model.factory.get_metadata(row.order_type);
|
||||
|
||||
internal OrderBookRowVM get_parent(OrderBookRowVM row) => _row_collection.get_parent(row.order_key);
|
||||
@@ -598,7 +626,11 @@ namespace mbts.spark {
|
||||
await Task.WhenAll(update_params.Select(t => _model.update_order(t.order, new_price: price, new_size: size, new_algo: t.new_algo)));
|
||||
(OrderRecordImmutable order, CustomOrderDefinitionImmutable? new_algo) update_params_factory(OrderBookRowVM row) {
|
||||
var mod_algo = row.order.Def.CustomDef.CloneToMutable();
|
||||
mod_algo_vm.assign_to(mod_algo);
|
||||
if (mod_algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite) {
|
||||
mod_algo_vm.assign_to(mod_algo.Composite.Definition);
|
||||
} else {
|
||||
mod_algo_vm.assign_to(mod_algo);
|
||||
}
|
||||
var mod_algoi = new CustomOrderDefinitionImmutable(mod_algo);
|
||||
return (row.order, OrderDataHelper.EqualsHelpers.equals(row.order.Def.CustomDef, mod_algoi) ? (CustomOrderDefinitionImmutable?)null : mod_algoi);
|
||||
}
|
||||
@@ -840,10 +872,9 @@ namespace mbts.spark {
|
||||
revert();
|
||||
}
|
||||
|
||||
public void revert() {
|
||||
foreach (var r in param_rows)
|
||||
r.param.revert();
|
||||
}
|
||||
public void revert() =>
|
||||
param_rows.revert_all();
|
||||
|
||||
public IObservable<bool> subscribe_is_dirty() => _is_dirty.observe_current(true);
|
||||
|
||||
void _reset_ladder_vm() {
|
||||
@@ -851,7 +882,10 @@ namespace mbts.spark {
|
||||
return;
|
||||
Disposable.replace(ref _ladder_vm,
|
||||
_security_md.HasValue ?
|
||||
LadderVM.create_readonly(_owner._bus, new LadderModel(security_md.Value, _owner._model, new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
|
||||
LadderVM.create_readonly(
|
||||
_owner._bus,
|
||||
new LadderModel(security_md.Value, _owner._model, _owner._model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key)))) :
|
||||
null);
|
||||
RaisePropertyChanged(nameof(ladder_vm));
|
||||
}
|
||||
@@ -1104,4 +1138,4 @@ namespace mbts.spark {
|
||||
!_algo_type_conf.TryGetValue(algo_type, out var result) || result.show_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,12 @@ namespace bts.shell.orderbook {
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
[ProtoMember(8)]
|
||||
public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
@@ -70,6 +74,7 @@ namespace bts.shell.orderbook {
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
price.safe_equals(other.price) &&
|
||||
@@ -96,6 +101,7 @@ namespace bts.shell.orderbook {
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
@@ -128,7 +134,8 @@ namespace bts.shell.orderbook {
|
||||
add_discrete_values(fs.accounts, o => o.order.account());
|
||||
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
|
||||
add_discrete_values(fs.tifs, o => o.order.time_in_force());
|
||||
add_discrete_values(fs.order_types, o => o.order.order_type());
|
||||
add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
|
||||
add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
|
||||
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
|
||||
add_discrete_values(fs.sides, o => o.order.side());
|
||||
add_discrete_values(fs.statuses, o => o.order.status());
|
||||
@@ -191,4 +198,4 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,10 @@ namespace bts.shell.orderbook {
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
@@ -130,31 +134,34 @@ namespace bts.shell.orderbook {
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.sorting_criterion);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion));
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criterion)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings side => new GridColumnSettings(Columns.Side.ToString(), "B/S", width: DIYGrid.DefaultColumnWidths.Side, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Size, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings filled_quantity => new GridColumnSettings(Columns.Filled.ToString(), "Filled Qty", width: DIYGrid.DefaultColumnWidths.Filled, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings avg_price => new GridColumnSettings(Columns.AvgFilled.ToString(), "Avg Filled", width: DIYGrid.DefaultColumnWidths.AveragePrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -173,6 +180,7 @@ namespace bts.shell.orderbook {
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
x:Name="_grid"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
InactiveSelectionOpacity="0.3"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -109,63 +108,4 @@
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.RowStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<!-- Completed order background -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Completed, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=completed_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<!-- Error, RejectedBySpark, Disconnected order background -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Error, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum RejectedBySpark, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Disconnected, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=error_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
<!-- Cancel order background -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Canceled, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=canceled_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
@@ -1,4 +1,11 @@
|
||||
using bts.api.orderman;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
@@ -41,11 +48,53 @@ namespace mbts.spark {
|
||||
Attach = 1 << 25,
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29
|
||||
}
|
||||
|
||||
public OrderBookGridView() =>
|
||||
public OrderBookGridView() {
|
||||
InitializeComponent();
|
||||
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
|
||||
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
|
||||
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
|
||||
}
|
||||
|
||||
static class RowBackgroundHelper {
|
||||
public static readonly BindingBase row_background_binding;
|
||||
|
||||
static RowBackgroundHelper() {
|
||||
var bg_binding = new MultiBinding();
|
||||
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
|
||||
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
|
||||
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().status_color_opacity_when_selected;
|
||||
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
|
||||
});
|
||||
} else {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
if (is_selected)
|
||||
return null;
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) : null;
|
||||
});
|
||||
}
|
||||
row_background_binding = bg_binding;
|
||||
}
|
||||
|
||||
static Color? _status_color(OrderStatus status) {
|
||||
switch (status) {
|
||||
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
|
||||
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
|
||||
case OrderStatus.Disconnected:
|
||||
case OrderStatus.RejectedBySpark:
|
||||
case OrderStatus.Error:
|
||||
return BTSColors.Orders.error_order_background;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
|
||||
@@ -174,9 +174,12 @@
|
||||
SelectionHighlight="{Binding grid_settings_manager.core_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<FrameworkElement.Margin>
|
||||
<Binding Source="{StaticResource GridSplitterWidth}">
|
||||
<Binding Path="show_right_panel">
|
||||
<Binding.Converter>
|
||||
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
|
||||
<cv:ValueConverterPipe>
|
||||
<cv:BooleanToObjectConverter ValueForFalse="{u:Double 0}" ValueForTrue="{StaticResource GridSplitterWidth}"/>
|
||||
<cv:DoubleToThicknessConverter RightMultiplier="-1"/>
|
||||
</cv:ValueConverterPipe>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</FrameworkElement.Margin>
|
||||
@@ -576,7 +579,7 @@
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</Grid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom" Grid.ColumnSpan="2">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
|
||||
@@ -53,12 +53,19 @@ namespace mbts.spark {
|
||||
d |= VisualStateNames.SlaveData.Data.Ladder;
|
||||
return VisualStateNames.SlaveData.fmt_name(d);
|
||||
});
|
||||
static readonly IMultiValueConverter
|
||||
_right_panel_expander_margin_converter = ValueConverter.create<bool, Visibility, Thickness>(
|
||||
(is_expanded, scrollbar_vis) =>
|
||||
!is_expanded && scrollbar_vis == Visibility.Visible ?
|
||||
new Thickness(0, DIYGrid.DefaultColumnHeaderHeight, SystemParameters.VerticalScrollBarWidth, 0) :
|
||||
new Thickness(0));
|
||||
|
||||
IDisposable _attach_op;
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
bind_right_panel_expander_margin();
|
||||
var algo_editor_cm = new ContextMenu();
|
||||
algo_editor_cm.Items.AddRange(order_mod_menu());
|
||||
algo_editor.ContextMenu = algo_editor_cm;
|
||||
@@ -138,6 +145,19 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async void bind_right_panel_expander_margin() {
|
||||
if (!orders_grid.IsLoaded)
|
||||
await orders_grid.LoadedAsync();
|
||||
if (orders_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rows_presenter)) {
|
||||
if (rows_presenter.try_find_descendent_of_type(out ScrollViewer scroll_viewer)) {
|
||||
var mb = new MultiBinding { Converter = _right_panel_expander_margin_converter };
|
||||
mb.Bindings.Add(new Binding(nameof(OrderBookVM.show_right_panel)) { Source = vm });
|
||||
mb.Bindings.Add(new Binding(nameof(ScrollViewer.ComputedVerticalScrollBarVisibility)) { Source = scroll_viewer });
|
||||
right_panel_expander_toggle.SetBinding(MarginProperty, mb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public new OrderBookVM DataContext {
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.ui;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using static mbts.ui.shell.ProtoBufSerializationHelper;
|
||||
@@ -20,9 +21,17 @@ namespace bts.shell {
|
||||
AlgoStudioWindowFactory _aswf;
|
||||
|
||||
[ImportingConstructor]
|
||||
public TradingShellModule(IBus bus, IWindowManager wm, INotificationService notification_service, INotificationServiceDataCache notification_service_data_cache,
|
||||
UserSettingsHub settings_hub, OrdersModel orders_model, PAndLModel pandl_model, ErrorReporter error_reporter) {
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model);
|
||||
public TradingShellModule(
|
||||
IBus bus,
|
||||
IWindowManager wm,
|
||||
INotificationService notification_service,
|
||||
INotificationServiceDataCache notification_service_data_cache,
|
||||
UserSettingsHub settings_hub,
|
||||
OrdersModel orders_model,
|
||||
PAndLModel pandl_model,
|
||||
md.MBOService mbo,
|
||||
ErrorReporter error_reporter) {
|
||||
_lwf = new LadderWindowFactory(this, bus, wm, settings_hub, orders_model, mbo);
|
||||
_obwf = new OrderBookWindowFactory(this, bus, notification_service, notification_service_data_cache, orders_model, settings_hub);
|
||||
_fwf = new FillsWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_plwf = new PAndLWindowFactory(this, bus, settings_hub, pandl_model);
|
||||
@@ -52,9 +61,8 @@ namespace bts.shell {
|
||||
yield return _aswf;
|
||||
}
|
||||
|
||||
public Task<IShellWindow> create(Stream load_stream) {
|
||||
return dispatch_to_factory(load_stream, window_factories().ToLArray());
|
||||
}
|
||||
public Task<IShellWindow> create(Stream load_stream) =>
|
||||
dispatch_to_factory(load_stream, window_factories().ToArray());
|
||||
|
||||
public void Dispose() {
|
||||
foreach (var i in window_factories())
|
||||
|
||||
@@ -857,7 +857,7 @@ namespace mbts.spark.utilities {
|
||||
int phase_mult = 1;
|
||||
switch (def.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Cover:
|
||||
add_component(def.Cover.HedgeTicks);
|
||||
add_component(def.Cover.HedgeTicks.GetHashCode());
|
||||
add_component(_get_hash(_get_cover_child(def.Cover)));
|
||||
add_component(_get_hash(_get_cover_hedge(def.Cover)));
|
||||
break;
|
||||
|
||||
@@ -153,9 +153,21 @@ namespace mbts.spark {
|
||||
d => d.load_committed(),
|
||||
d => d == null ? Observable.Return(false) : d.subscribe_is_definition_dirty(),
|
||||
out IObserver<AlgoDefinitionVM> revert_definition_observer);
|
||||
// algo params dialog skirt
|
||||
set_default_algo_params_cmd = ObservantCommand.create(() => {
|
||||
algo_param_rows.commit_all();
|
||||
_close_algo_popup();
|
||||
}, out IObserver<bool> set_default_algo_params_observer);
|
||||
revert_algo_params_cmd = ObservantCommand.create(() => {
|
||||
algo_param_rows.revert_all();
|
||||
_close_algo_popup();
|
||||
}, out IObserver<bool> revert_algo_params_observer);
|
||||
var algo_params_dirty_mon = new ReactiveThresholdMonitor<AlgoParamRowVM>(r => r.param.subscribe_is_dirty());
|
||||
var nullable_security_md_source = security_md_source.Select(smd => new SecurityMD?(smd)).RefCountReplay(1);
|
||||
//
|
||||
_dispose = new CompositeDisposable(
|
||||
algo_params_dirty_mon.Subscribe(set_default_algo_params_observer, revert_algo_params_observer),
|
||||
ObservableAutoWrapper.connect_item_hooks(_algo_param_rows, algo_params_dirty_mon.join),
|
||||
_selected_algo_definition.subscribe_changed(_selected_algo_definition_Changed),
|
||||
as_demuxer().subscribe<AlgoDefinitionTemplateVM>(nameof(selected_algo_template)).Subscribe(
|
||||
undo_observer,
|
||||
@@ -358,6 +370,8 @@ namespace mbts.spark {
|
||||
public ICommand save_definition_cmd { get; }
|
||||
public ICommand save_template_cmd { get; }
|
||||
public ICommand revert_definition_cmd { get; }
|
||||
public ICommand set_default_algo_params_cmd { get; }
|
||||
public ICommand revert_algo_params_cmd { get; }
|
||||
public ICommand undo_cmd { get; }
|
||||
public ICommand redo_cmd { get; }
|
||||
public ICommand undo_definition_cmd { get; }
|
||||
@@ -386,9 +400,22 @@ namespace mbts.spark {
|
||||
if (_model.algo_model.templates_enabled()) {
|
||||
selected_algo_template.algo_root.assign_to(order.CustomDef);
|
||||
} else {
|
||||
selected_algo_definition.algo_root.assign_to(order.CustomDef);
|
||||
if (selected_algo_definition.key.mode == AlgoKey.Mode.Persisted)
|
||||
order.AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint;
|
||||
var algo = new CustomOrderDefinition();
|
||||
selected_algo_definition.algo_root.assign_to(algo);
|
||||
switch (selected_algo_definition.key.mode) {
|
||||
case AlgoKey.Mode.Persisted:
|
||||
order.CustomDef = new CustomOrderDefinition {
|
||||
Composite = new CompositeOrderDefinition {
|
||||
AlgoTemplateKey = selected_algo_definition.key.persisted.id_uint,
|
||||
Definition = algo
|
||||
}
|
||||
};
|
||||
selected_algo_definition.algo_root.assign_to(order.CustomDef.Composite.Definition);
|
||||
break;
|
||||
default:
|
||||
order.CustomDef = algo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
@@ -411,6 +438,11 @@ namespace mbts.spark {
|
||||
static IDisposable _connect_algo_param_rows(AlgoDefinitionVM def, ObservableAggregateCollection<AlgoParamRowVM> rows) =>
|
||||
AlgoParamRowsHelper.connect(def, rows, false);
|
||||
|
||||
void _close_algo_popup() {
|
||||
if (selected_algo_definition?.algo_root is IAlgoVM a)
|
||||
a.is_expanded = false;
|
||||
}
|
||||
|
||||
int? _get_favorite_algo_index(AlgoKey key) =>
|
||||
key.mode == AlgoKey.Mode.Persisted && _favorite_algos_index.TryGetValue(key, out int i) ? i : (int?)null;
|
||||
|
||||
|
||||
@@ -58,21 +58,39 @@ namespace bts.api.orderman {
|
||||
struct OrderSecurityAlgoMeta : IEquatable<OrderSecurityAlgoMeta> {
|
||||
public readonly OrderRecordImmutable order;
|
||||
public readonly SecurityImmutable? security;
|
||||
/// TODO: refactor <see cref="algo"/> to 'studio_algo' or something that indicates it does not bear the same relation to this order, that <see cref="root_algo"/> does to the root order.
|
||||
/// The former is meant to hold studio algo information, and nothing else. Naturally, it indicates whether or not this is a studio order by being non-null (<see cref="is_studio_algo"/>).
|
||||
/// The latter, however, can be any kind of algo (studio, exchange, etc.); it holds information about the root-most algo in this order's branch.
|
||||
public readonly AlgoData? algo;
|
||||
public readonly AlgoDefinitionMetadata? meta;
|
||||
public readonly AlgoData root_algo;
|
||||
public readonly AlgoDefinitionMetadata? root_meta;
|
||||
|
||||
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta) {
|
||||
public OrderSecurityAlgoMeta(OrderRecordImmutable order, SecurityImmutable? security, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? root_algo = null, AlgoDefinitionMetadata? root_meta = null) {
|
||||
this.order = order;
|
||||
this.security = security;
|
||||
this.algo = algo;
|
||||
this.meta = meta;
|
||||
if (root_algo.HasValue) {
|
||||
this.root_algo = root_algo.Value;
|
||||
this.root_meta = root_meta;
|
||||
} else {
|
||||
this.root_algo = algo ?? new AlgoData(order.Def.CustomDef);
|
||||
this.root_meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
public bool is_studio_algo => algo.HasValue;
|
||||
|
||||
public OrderSecurityAlgoMeta with(AlgoData? root_algo, AlgoDefinitionMetadata? root_meta = null) =>
|
||||
new OrderSecurityAlgoMeta(order, security, algo, meta, root_algo, root_meta);
|
||||
|
||||
public bool Equals(OrderSecurityAlgoMeta other) =>
|
||||
order == other.order &&
|
||||
security == other.security &&
|
||||
algo == other.algo &&
|
||||
meta == other.meta;
|
||||
meta == other.meta &&
|
||||
root_algo == other.root_algo;
|
||||
|
||||
public override bool Equals(object obj) => EqualityComparer.equals_obj(this, obj);
|
||||
|
||||
@@ -81,9 +99,10 @@ namespace bts.api.orderman {
|
||||
order.GetHashCode(),
|
||||
security?.GetHashCode() ?? 0,
|
||||
algo?.GetHashCode() ?? 0,
|
||||
meta?.GetHashCode() ?? 0);
|
||||
meta?.GetHashCode() ?? 0,
|
||||
root_algo.GetHashCode());
|
||||
|
||||
public static bool operator ==(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => a.Equals(b);
|
||||
public static bool operator !=(OrderSecurityAlgoMeta a, OrderSecurityAlgoMeta b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class CoverOrderVM : OrderTypeVMBase<CoverOrderDefinition, CoverOrderDefinitionImmutable> {
|
||||
readonly Property<int?> _hedge_ticks;
|
||||
readonly Property<uint?> _hedge_ticks;
|
||||
|
||||
public CoverOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) =>
|
||||
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1, true);
|
||||
_hedge_ticks = add_nullable_property(nameof(hedge_ticks), CoverOrderDefinition.HedgeTicksFieldNumber, 1u, true);
|
||||
|
||||
public int? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
|
||||
public uint? hedge_ticks { get => _hedge_ticks.Value; set => _hedge_ticks.set(value); }
|
||||
|
||||
protected override void validate() =>
|
||||
assert(hedge_ticks.HasValue, nameof(hedge_ticks), "Invalid hedge ticks.");
|
||||
|
||||
@@ -58,6 +58,9 @@ namespace mbts.spark {
|
||||
public static IAlgoVM dynamic(AlgoKey key, string display_name, string dynamic_type, AlgoEditingPermissions editing_permissions) => new DynamicOrderVM(key, dynamic_type, display_name, editing_permissions);
|
||||
|
||||
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
|
||||
if (def_data.value.CustomCase == AlgoType.Composite) {
|
||||
def_data = new AlgoData(def_data.value.Composite.Definition, def_data.name, def_data.key);
|
||||
}
|
||||
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
|
||||
vm.load_from(def_data.value);
|
||||
return vm;
|
||||
@@ -235,8 +238,11 @@ namespace mbts.spark {
|
||||
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
|
||||
|
||||
public void load_from(AlgoDefi value) {
|
||||
if (value.CustomCase != order_type)
|
||||
return;
|
||||
if (value.CustomCase != order_type) {
|
||||
value = value.actual();
|
||||
if (value.CustomCase != order_type)
|
||||
return;
|
||||
}
|
||||
using (_def_change_notifier.defer_execution()) {
|
||||
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
|
||||
var security_src = Observable.Create<SecurityImmutable?>(o => {
|
||||
@@ -268,8 +274,11 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
|
||||
if (defi.CustomCase != order_type)
|
||||
return null;
|
||||
if (defi.CustomCase != order_type) {
|
||||
defi = defi.actual();
|
||||
if (defi.CustomCase != order_type)
|
||||
return null;
|
||||
}
|
||||
using (_def_change_notifier.defer_execution()) {
|
||||
// TODO: each level of the order hierarchy could load in parallel
|
||||
await on_load_from_async(defi);
|
||||
@@ -1048,4 +1057,4 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,13 @@ using TopDefi = bts.api.orderman.TopOrderDefinitionImmutable;
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class TopOrderVM : OrderTypeVMBase<TopDef, TopDefi> {
|
||||
readonly Property<uint?> _watch_level_tick_offset;
|
||||
readonly Property<QuantityMeasureImmutable?> _top_threshold;
|
||||
|
||||
public TopOrderVM(AlgoKey key, string display_name, AlgoEditingPermissions editing_permissions)
|
||||
: base(key, display_name, editing_permissions) {
|
||||
_watch_level_tick_offset = add_nullable_property(nameof(watch_level_tick_offset), TopDef.WatchLevelTickOffsetFieldNumber, 0u);
|
||||
_top_threshold = add_property<QuantityMeasure, QuantityMeasureImmutable>(nameof(top_threshold), TopDef.TopThresholdFieldNumber, new QuantityMeasureImmutable(new QuantityMeasure { Ratio = 1.0, Absolute = 1 }));
|
||||
}
|
||||
|
||||
public uint? watch_level_tick_offset { get => _watch_level_tick_offset.Value; set => _watch_level_tick_offset.set(value); }
|
||||
public QuantityMeasureImmutable? top_threshold { get => _top_threshold.Value; set => _top_threshold.set(value); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,21 @@ namespace mbts.spark {
|
||||
public static string dynamic_order_type_display_name(this OrderRecordImmutable order) =>
|
||||
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.HasDynamic ? order.Def.CustomDef.Dynamic.DynamicOrderType : string.Empty;
|
||||
|
||||
public static uint algo_template_key(this OrderRecordImmutable order) =>
|
||||
order.HasDef && order.Def.HasCustomDef && order.Def.CustomDef.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite ?
|
||||
order.Def.CustomDef.Composite.AlgoTemplateKey : 0u;
|
||||
|
||||
/// <summary>
|
||||
/// If the definitions is a <see cref="CompositeOrderDefinition"/>, returns the underlying algo, otherwise returns <paramref name="custom_def"/> iteself.
|
||||
/// </summary>
|
||||
/// <param name="custom_def">The definition to for which the underlying algo will be returned.</param>
|
||||
/// <returns>The first non-composite algo definition in the specified <see cref="CustomOrderDefinition"/>.</returns>
|
||||
public static CustomOrderDefinitionImmutable actual(this CustomOrderDefinitionImmutable custom_def) {
|
||||
while (custom_def.CustomCase == CustomOrderDefinition.CustomOneofCase.Composite)
|
||||
custom_def = custom_def.Composite.Definition;
|
||||
return custom_def;
|
||||
}
|
||||
|
||||
public static OrderSubType sub_type(this OrderRecordImmutable order) =>
|
||||
order.HasDef ? order.Def.SubType : OrderSubType.None;
|
||||
|
||||
@@ -125,7 +140,7 @@ namespace mbts.spark {
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
|
||||
public static string display_name(OrderRecordImmutable order, AlgoData? algo) {
|
||||
if (!order.has_parent() && algo.HasValue) {
|
||||
if (algo.HasValue) {
|
||||
return algo.Value.name;
|
||||
} else if (order.HasDef && order.Def.HasCustomDef) {
|
||||
return order.Def.CustomDef.HasDynamic ?
|
||||
@@ -225,18 +240,12 @@ namespace mbts.spark {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static Brush badge_fill(this OrderSecurityAlgoMeta order_sam, OrderMetadata order_meta) =>
|
||||
badge_fill(order_sam.order, order_sam.meta, order_meta);
|
||||
|
||||
public static Brush badge_fill(OrderRecordImmutable order, AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) {
|
||||
/// TODO: figure out why <see cref="OrderDefinition.AlgoTemplateKey"/> is propagated to all descendant-algos (should really just belong to the algo-node/CustomDef that is the root of the AlgoTemplate).
|
||||
/// In the meantime, the following 'has-no-parent' conditional will suffice as a workaround so child-algos will be colored properly (according to their own <see cref="CustomOrderDefinition.CustomOneofCase"/>).
|
||||
if (!order.has_parent()) {
|
||||
if (algo_meta?.badge_color is Color c)
|
||||
return c.to_brush();
|
||||
}
|
||||
return order_meta.Color.rgb_brush();
|
||||
}
|
||||
public static Brush badge_fill(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
|
||||
new SolidColorBrush(badge_color(algo_meta, order_meta));
|
||||
|
||||
public static Color badge_color(AlgoDefinitionMetadata? algo_meta, OrderMetadata order_meta) =>
|
||||
algo_meta?.badge_color ?? order_meta.Color.rgb_color();
|
||||
|
||||
public static string order_collection_summary(double best_price, int best_price_volume, int total_volume, ContractSpecificationImmutable spec) {
|
||||
if (total_volume == 0)
|
||||
|
||||
@@ -18,7 +18,6 @@ using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using OrderUpdate = bts.api.core.TableUpdate<(bts.api.SecurityImmutable? sec, bts.api.orderman.OrderRecordImmutable order), uint>;
|
||||
using OrderUpdatePiq = bts.api.core.TableUpdate<bts.api.orderman.OrderSecurity, uint>;
|
||||
@@ -37,7 +36,8 @@ namespace mbts.spark {
|
||||
readonly ErrorReporter _error;
|
||||
readonly DynamicOrderHelper _dynamic_helper;
|
||||
readonly OrderActivitySummarizer _activity;
|
||||
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer, _security_working_orders_demuxer;
|
||||
readonly TableUpdateDemuxer<OrderRecordImmutable, uint, uint> _security_orders_demuxer;
|
||||
readonly TableUpdateDemuxer<OrderSecurity, uint, uint> _security_working_orders_with_piq_demuxer;
|
||||
readonly TableUpdateDemuxer<PositionUnitImmutable, int, uint> _security_positions_demuxer;
|
||||
IDisposable _dispose;
|
||||
static readonly bool _piq_enabled = Configuration.get<bts.config.toggles.PlacementInQueue>().enabled;
|
||||
@@ -82,20 +82,21 @@ namespace mbts.spark {
|
||||
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
_security_working_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
_security_working_orders_with_piq_demuxer = new TableUpdateDemuxer<OrderSecurity, uint, uint>(o => o.order.Key, o => o.order.security_key());
|
||||
_security_positions_demuxer = new TableUpdateDemuxer<PositionUnitImmutable, int, uint>(o => o.RecordId, p => p.SecurityId);
|
||||
var algo_meta_join = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
|
||||
studio_algos_with_meta = algo_model.definitions.LeftJoin<AlgoData, AlgoDefinitionMetadata, (AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
|
||||
algo_model.metadata_service.subscribe_metadata().Table(m => m.id),
|
||||
d => d.key,
|
||||
m => m.id,
|
||||
(algo, k, meta) => (algo, meta),
|
||||
(algo, k) => (algo, null),
|
||||
t => t.algo.key);
|
||||
t => t.algo.key)
|
||||
.Cache();
|
||||
var order_secs_algo_meta_join = on_orders.LeftJoin(
|
||||
algo_meta_join,
|
||||
studio_algos_with_meta,
|
||||
t => t.order.Key,
|
||||
am => am.algo.key,
|
||||
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.Def.AlgoTemplateKey)),
|
||||
t => new AlgoKey(new bts.persist.algo.AlgoPersistedKey(t.order.algo_template_key())),
|
||||
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
|
||||
ot => ot.order.Key);
|
||||
@@ -114,13 +115,13 @@ namespace mbts.spark {
|
||||
(osam, _) => new OrderSecurity(osam.security, osam.order, null, osam.algo, osam.meta),
|
||||
osp => osp.order.Key)
|
||||
.Cache();
|
||||
|
||||
working_orders_with_piq =
|
||||
on_order_tuples_with_piq.Scan(
|
||||
ImmutableDictionary<uint, OrderSecurity>.Empty,
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
|
||||
// order tuples with root
|
||||
on_order_tuples_with_root = on_order_tuples.RootDependent(
|
||||
o => o.order.Key,
|
||||
o => o.order.ParentKey,
|
||||
(osam, root_osam) => osam.with(root_osam.algo ?? new AlgoData(root_osam.order.Def.CustomDef), root_osam.meta)
|
||||
).Cache();
|
||||
// fills
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
@@ -152,10 +153,9 @@ namespace mbts.spark {
|
||||
.Cache();
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
|
||||
on_orders
|
||||
.Filter(order => order.order.is_active() && order.order.HasState && order.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
|
||||
.Select(t => t.order, o => o.Key)
|
||||
.Subscribe(_security_working_orders_demuxer),
|
||||
on_order_tuples_with_piq
|
||||
.Filter(os => os.order.is_active() && os.order.State.DisplayStatus == OrderState.Types.DisplayStatus.Visible, key => key.order.Key)
|
||||
.Subscribe(_security_working_orders_with_piq_demuxer),
|
||||
on_positions.Subscribe(_security_positions_demuxer),
|
||||
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
|
||||
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
|
||||
@@ -172,19 +172,21 @@ namespace mbts.spark {
|
||||
public IObservable<OrderUpdate> on_orders { get; }
|
||||
public IObservable<OrderUpdatePiq> on_order_tuples_with_piq { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples { get; }
|
||||
public IObservable<TableUpdate<OrderSecurityAlgoMeta, uint>> on_order_tuples_with_root { get; }
|
||||
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_detach { get; }
|
||||
public IObservable<TableUpdate<KeyValuePair<uint, bool>, uint>> on_orders_can_attach { get; }
|
||||
public IObservable<IIncrementalUpdate<SecurityImmutable, uint>> on_order_securities { get; }
|
||||
public IObservable<TableUpdate<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderSecurity>> working_orders { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, OrderSecurity>> working_orders_with_piq { get; }
|
||||
public IObservable<TableUpdate<PositionUnitImmutable, int>> on_positions { get; }
|
||||
public IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> studio_algos_with_meta { get; }
|
||||
|
||||
public AdminModel admin_model { get; }
|
||||
public MBOService mbo_service => _mbo_service;
|
||||
public IAlgoStudioModel algo_model { get; }
|
||||
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_working_orders(uint security) =>
|
||||
_security_working_orders_demuxer.subscribe(security);
|
||||
public IObservable<TableUpdate<OrderSecurity, uint>> on_security_working_orders_with_piq(uint security) =>
|
||||
_security_working_orders_with_piq_demuxer.subscribe(security);
|
||||
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
|
||||
_security_orders_demuxer.subscribe(security);
|
||||
@@ -228,9 +230,9 @@ namespace mbts.spark {
|
||||
// order already filled to completion
|
||||
return false;
|
||||
}
|
||||
var size = new_size ?? (uint)order.working_quantity();
|
||||
var size = new_size ?? order.quantity();
|
||||
var price = new_price ?? order.price();
|
||||
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
|
||||
if (size == order.quantity() && price == order.price() && !new_algo.HasValue) {
|
||||
// no parameters would be changed by this update
|
||||
return false;
|
||||
}
|
||||
@@ -437,6 +439,18 @@ namespace mbts.spark {
|
||||
return rod;
|
||||
}).Select(r => r.last_update);
|
||||
|
||||
public static IObservable<TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>> subscribe_all_algos(this OrdersModel orders_model) =>
|
||||
orders_model.studio_algos_with_meta.MergeTable(Observable.Return(
|
||||
new TableUpdate<(AlgoData algo, AlgoDefinitionMetadata? meta), AlgoKey>(
|
||||
ImmutableDictionary<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>.Empty.AddRange(orders_model.factory.default_algos(OrderDataHelper.all_order_types).Select(kv => new KeyValuePair<AlgoKey, (AlgoData algo, AlgoDefinitionMetadata? meta)>(kv.Key, (kv.Value, null)))),
|
||||
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
|
||||
Array.Empty<Change<(AlgoData,AlgoDefinitionMetadata?)>>(),
|
||||
Array.Empty<(AlgoData,AlgoDefinitionMetadata?)>(),
|
||||
d => d.algo.key
|
||||
).as_snapshot()));
|
||||
|
||||
//public static IObservable<TableUpdate<AlgoData, AlgoKey>> subscribe_all_algos_with_meta(this OrdersModel orders_model) =>
|
||||
|
||||
public static async Task<OrderRecordImmutable?> order_from_key(this OrdersModel orders_model, uint order_key) {
|
||||
(var tu, var success) = await orders_model.on_orders.FirstAsyncTask(t => t.state.ContainsKey(order_key), TimeSpan.FromSeconds(3));
|
||||
if (success && tu.state.TryGetValue(order_key, out var rec)) {
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<RootNamespace>bts</RootNamespace>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
<WarningsAsErrors />
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../spark_api/spark_api.csproj" />
|
||||
<ProjectReference Include="../spark_core/spark_core.csproj" />
|
||||
@@ -81,16 +81,47 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorGridView\AlgoEditorGridView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\AlgoStudio\View\AlgoEditorView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\LadderSettingsDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\LadderSettingsView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\LadderView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\Ladder\View\MiniLadderView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\MiniOrderBook\MiniOrderBookView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\MiniOrderBook\OrderBookView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderBookGridView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderBookToolBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderBookView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Modules\Trading\OrderBook\View\OrderParamsPanel.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="view\Controls\DIYGrid\Settings\SimpleGridSettingsDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent">
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -188,7 +188,7 @@ namespace mbts.collections {
|
||||
}
|
||||
|
||||
public IObservableCollection<T> items { get; }
|
||||
public ObservableCollection<T> selected_items { get; } = new ObservableCollection<T>();
|
||||
public ObservableCollectionEx<T> selected_items { get; } = new ObservableCollectionEx<T>();
|
||||
public IEnumerable<TKey> selected_keys => selected_items.Select(_key_selector);
|
||||
|
||||
public bool try_select(TKey key) {
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace mbts.collections
|
||||
foreach (TModel m in e.OldItems) _remove(m);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
var old_views = Values.ToLArray();
|
||||
var old_views = Values.ToArray();
|
||||
Clear();
|
||||
foreach (TValue v in old_views) ValueDestroyed?.Invoke(v);
|
||||
foreach (TModel m in _models) _add(m);
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace mbts.ui {
|
||||
public ObservantCommand(Action execute) : base(_ => execute()) { }
|
||||
public ObservantCommand(Action<object> execute) : base(execute) { }
|
||||
|
||||
public static ICommand create(Action execute, out IObserver<bool> observer) {
|
||||
var cmd = new ObservantCommand(execute);
|
||||
observer = cmd;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public static ICommand create<T>(Action<T> execute, Func<T, bool> get_can_execute, out IObserver<T> observer) {
|
||||
T param = default;
|
||||
bool can_execute = false;
|
||||
|
||||
@@ -54,19 +54,16 @@ namespace System.Reflection
|
||||
/// <summary>
|
||||
/// Gets the generic type arguments of the generic class, or interface implemented by the current Type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type that implements or is derived from the generic type (imp_generic_def).</param>
|
||||
/// <param name="imp_generic_def">The generic type definition which the current type implements (if an interface) or is derived from (if a class).</param>
|
||||
/// <param name="type">The type that implements or is derived from the generic type (<paramref name="generic_def"/>).</param>
|
||||
/// <param name="generic_def">The generic type definition which the current type implements (if an interface) or is derived from (if a class).</param>
|
||||
/// <returns>The generic type arguments of the generic type definition implemented or derived from by the current type.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The parameter generic_def does not represent a generic type definition. That is, System.Type.IsGenericTypeDefinition returns false.
|
||||
/// </exception>
|
||||
public static Type[] GetGenericTypeArgs(Type type, Type generic_def) {
|
||||
if (!generic_def.IsGenericTypeDefinition) throw MyExceptions.not_generic_type_definition(generic_def);
|
||||
Type[] rv = null;
|
||||
rv = generic_def.IsInterface ? _get_generic_interface_type_args(type, generic_def) : _get_generic_type_args(type, generic_def);
|
||||
if (rv == null) throw MyExceptions.does_not_implement_or_derive_from(type, generic_def);
|
||||
else return rv;
|
||||
}
|
||||
public static Type[] GetGenericTypeArgs(Type type, Type generic_def) =>
|
||||
!generic_def.IsGenericTypeDefinition ? throw MyExceptions.not_generic_type_definition(generic_def) :
|
||||
(generic_def.IsInterface ? _get_generic_interface_type_args(type, generic_def) : _get_generic_type_args(type, generic_def)) ??
|
||||
throw MyExceptions.does_not_implement_or_derive_from(type, generic_def);
|
||||
|
||||
public static IDisposable detect_property_changes<T>(T obj, Action<string> on_prop_changed, params string[] props) where T : class {
|
||||
var snapshot = props.ToDictionary(p => p, p => {
|
||||
|
||||
@@ -11,4 +11,6 @@ namespace bts.shell
|
||||
{
|
||||
Task<Size> MeasureContentSize();
|
||||
}
|
||||
|
||||
interface ISlowMeasureContentSize : IMeasureContentSize { }
|
||||
}
|
||||
@@ -205,10 +205,15 @@ namespace mbts.ui.shell {
|
||||
return new Property<T>(this, exp.Member.Name, default_value);
|
||||
}
|
||||
|
||||
// WILL invoke the get_value delegate
|
||||
protected DelegateProperty<T> backing<T>(string name, Func<T> get_value, params string[] invalidating_props) {
|
||||
return new DelegateProperty<T>(this, name, get_value, invalidating_props);
|
||||
}
|
||||
|
||||
// will NOT invoke the get_value delegate
|
||||
protected DelegateProperty<T> backing<T>(string name, Func<T> get_value, T default_value, params string[] invalidating_props) =>
|
||||
new DelegateProperty<T>(this, name, get_value, default_value, invalidating_props);
|
||||
|
||||
protected PropertyAutoWrapper<TModel, TView> backing<TModel, TView>(string name, TModel initial_model, Func<TModel, TView> convert) =>
|
||||
new PropertyAutoWrapper<TModel, TView>(this, name, initial_model, convert, EqualityComparer<TModel>.Default);
|
||||
|
||||
@@ -273,7 +278,10 @@ namespace mbts.ui.shell {
|
||||
|
||||
protected class DelegateProperty<T> : Property<T> {
|
||||
public DelegateProperty(ViewModelBase owner, string name, Func<T> get_value, params string[] invalidating_props)
|
||||
: base(owner, name, get_value()) {
|
||||
: this(owner, name, get_value, get_value(), invalidating_props) { }
|
||||
|
||||
public DelegateProperty(ViewModelBase owner, string name, Func<T> get_value, T default_value, params string[] invalidating_props)
|
||||
: base(owner, name, default_value) {
|
||||
_get_value = get_value;
|
||||
if (invalidating_props != null && invalidating_props.Length > 0) {
|
||||
var iprop_set = new HashSet<string>(invalidating_props);
|
||||
@@ -282,7 +290,7 @@ namespace mbts.ui.shell {
|
||||
refresh();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly Func<T> _get_value;
|
||||
|
||||
|
||||
@@ -116,7 +116,8 @@ namespace mbts.ui.controls {
|
||||
HubAlias = "Hub Alias",
|
||||
StripName = "Strip Name",
|
||||
MicCode = "Mic Code",
|
||||
PositionInQueue = "PIQ"
|
||||
PositionInQueue = "PIQ",
|
||||
RootAlgoType = "Root Algo"
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -8,16 +8,15 @@ namespace mbts.ui.controls {
|
||||
internal class OrderTypeColumn<TColKey> : FrameworkElementColumnBase<TColKey>, IFilterableColumn {
|
||||
readonly BindingBase _badge_fill_binding, _display_name_binding;
|
||||
|
||||
public OrderTypeColumn(TColKey key, BindingBase badge_fill_binding, Binding display_name_binding, IDataGridColumnFilter filter) : base(key) {
|
||||
public OrderTypeColumn(TColKey key, BindingBase badge_fill_binding, Binding display_name_binding, IDataGridColumnFilter filter, string header = DefaultColumnHeaders.OrderType) : base(key) {
|
||||
_badge_fill_binding = badge_fill_binding;
|
||||
_display_name_binding = display_name_binding;
|
||||
this.filter = filter;
|
||||
Header = DefaultColumnHeaders.OrderType;
|
||||
Width = DefaultColumnWidths.OrderType;
|
||||
Header = header;
|
||||
}
|
||||
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public string filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
public virtual string filter_template => DataGridFilterKnownTemplates.DiscreteValues;
|
||||
public BindingBase font_weight_binding { get; set; }
|
||||
|
||||
public override FrameworkElement view_factory() {
|
||||
@@ -37,6 +36,7 @@ namespace mbts.ui.controls {
|
||||
bind(FontSizeProperty, nameof(font_size));
|
||||
bind(ForegroundProperty, nameof(font_brush));
|
||||
bind(HorizontalAlignmentProperty, nameof(horizontal_alignment));
|
||||
bind(OrderTypeItem.WidthProperty, nameof(ActualWidth));
|
||||
return v;
|
||||
//
|
||||
void bind(DependencyProperty dp, string path) =>
|
||||
|
||||
@@ -211,7 +211,9 @@ namespace mbts.ui.controls {
|
||||
ActiveSelectionHighlightKey = nameof(ActiveSelectionHighlightKey),
|
||||
InactiveSelectionHighlightKey = nameof(InactiveSelectionHighlightKey),
|
||||
SelectionStrokeKey = nameof(SelectionStrokeKey);
|
||||
public const double DefaultRowHeight = 17;
|
||||
public const double
|
||||
DefaultRowHeight = 17,
|
||||
DefaultColumnHeaderHeight = 30;
|
||||
public static readonly Thickness
|
||||
DefaultCellPadding = new Thickness(4, 2, 4, 2),
|
||||
AnimatableCellPadding = new Thickness(8, 2, 4, 2);
|
||||
@@ -240,6 +242,7 @@ namespace mbts.ui.controls {
|
||||
public static readonly SolidColorBrush DefaultSelectionHighlight = new SolidColorBrush(Colors.MediumSlateBlue) { Opacity = 0.4 };
|
||||
public static readonly Pen DefaultSelectionStroke = new Pen(Brushes.MediumSlateBlue, 1);
|
||||
readonly NotifyCollectionChangedEventHandler _sort_descriptions_collection_changed_cb;
|
||||
double _inactive_selection_opacity = 0.2;
|
||||
DataContextHooks _data_context_hooks;
|
||||
|
||||
public static readonly DependencyProperty SelectionHighlightProperty =
|
||||
@@ -247,7 +250,7 @@ namespace mbts.ui.controls {
|
||||
nameof(SelectionHighlight),
|
||||
(o, v) => {
|
||||
o.Resources[ActiveSelectionHighlightKey] = v;
|
||||
o.Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(v.Color) { Opacity = 0.2 }.frozen();
|
||||
o._reset_inactive_selection_brush();
|
||||
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_solid_brush().frozen(), 1).frozen();
|
||||
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
|
||||
col.on_selection_fill(v);
|
||||
@@ -320,14 +323,24 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
}));
|
||||
Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(SelectionHighlight.Color) { Opacity = 0.2 };
|
||||
Resources[ActiveSelectionHighlightKey] = SelectionHighlight;
|
||||
_reset_inactive_selection_brush();
|
||||
_data_context_hooks = new DataContextHooks(this, DataContext);
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
public event DIYGridSortingEventHandler Sorting;
|
||||
|
||||
public double InactiveSelectionOpacity {
|
||||
get => _inactive_selection_opacity;
|
||||
set {
|
||||
if (value != _inactive_selection_opacity && value.is_within_range(0.1, 0.9)) {
|
||||
_inactive_selection_opacity = value;
|
||||
_reset_inactive_selection_brush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ItemContainerGenerator RowContainerGenerator =>
|
||||
_row_presenter.ItemContainerGenerator;
|
||||
|
||||
@@ -374,6 +387,18 @@ namespace mbts.ui.controls {
|
||||
void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) =>
|
||||
Disposable.replace(ref _data_context_hooks, new DataContextHooks(this, e.NewValue));
|
||||
|
||||
void _reset_inactive_selection_brush() {
|
||||
try {
|
||||
if (TryFindResource(ActiveSelectionHighlightKey) is SolidColorBrush brush) {
|
||||
var clone = brush.Clone();
|
||||
if (_inactive_selection_opacity < clone.Opacity) {
|
||||
clone.Opacity = _inactive_selection_opacity;
|
||||
}
|
||||
Resources[InactiveSelectionHighlightKey] = clone;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
#region Column Handling
|
||||
private void invalidate_column_layout(object sender = null, EventArgs args = null) {
|
||||
var geometry = GetColumnGeometry().ToList();
|
||||
@@ -589,7 +614,7 @@ namespace mbts.ui.controls {
|
||||
_row_presenter.ItemContainerStyle = _row_style;
|
||||
}
|
||||
|
||||
public Style RowStyle { get => GetValue(RowStyleProperty) as Style; set => SetValue(StyleProperty, value); }
|
||||
public Style RowStyle { get => GetValue(RowStyleProperty) as Style; set => SetValue(RowStyleProperty, value); }
|
||||
#endregion
|
||||
|
||||
public static readonly DependencyProperty CurrentRowProperty = DependencyProperty.Register(
|
||||
@@ -913,4 +938,4 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@
|
||||
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="CommandTarget" Value="{Binding RelativeSource={RelativeSource AncestorType=bts:DIYGrid}}"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Height" Value="30"/>
|
||||
<Setter Property="Height" Value="{x:Static bts:DIYGrid.DefaultColumnHeaderHeight}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
|
||||
|
||||
@@ -11,7 +11,22 @@ using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
partial class DIYGrid {
|
||||
public class HighPerformanceRow<TRowVM, TColKey> : FrameworkElement where TRowVM : class, IRowVM {
|
||||
public class HighPerformanceRowBase : FrameworkElement {
|
||||
protected Brush cached_background;
|
||||
|
||||
public static readonly DependencyProperty BackgroundProperty =
|
||||
WPFHelper.create_dp<Brush, HighPerformanceRowBase>(
|
||||
nameof(Background),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o.cached_background = v);
|
||||
|
||||
public Brush Background {
|
||||
get => GetValue(BackgroundProperty) as Brush;
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class HighPerformanceRow<TRowVM, TColKey> : HighPerformanceRowBase where TRowVM : class, IRowVM {
|
||||
double _row_height;
|
||||
ObservableCollection<ColumnBase> _columns;
|
||||
List<ColumnGeometry> _column_layout = new List<ColumnGeometry>();
|
||||
@@ -85,6 +100,9 @@ namespace mbts.ui.controls {
|
||||
_element_cells.VisualAtIndex(index);
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
if (cached_background != null) {
|
||||
dc.DrawRectangle(cached_background, null, new Rect(0, 0, ActualWidth, ActualHeight));
|
||||
}
|
||||
if (_row != null) {
|
||||
foreach (var cell in _column_layout) {
|
||||
if (!cell.column.is_collapsed && cell.column is IRenderColumn<TRowVM> rc)
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace mbts.spark.filters {
|
||||
|
||||
static class DataGridFilterKnownTemplates {
|
||||
public const string
|
||||
AlgoType = "AlgoTypeTemplate",
|
||||
DoubleMinMax = "DoubleMinMaxTemplate",
|
||||
IntMinMax = "IntMinMaxTemplate",
|
||||
UIntMinMax = "UIntMinMaxTemplate",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using bts.collections;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.collections;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -58,6 +61,14 @@ namespace mbts.spark.filters {
|
||||
IEqualityComparer<TValue> equality_comparer = null,
|
||||
IComparer<TValue> comparer = null
|
||||
) => new DynamicItemsFilter<TRow, TValue, TState>(items, get_row_val, get_state, load_state, prop_name, equality_comparer ?? EqualityComparer<TValue>.Default, comparer);
|
||||
|
||||
public static IDataGridColumnPersistentFilter<TRow, TKey[]> CreateTableUpdate<TRow, TValue, TKey>(
|
||||
Func<TRow, TValue> factory, Func<TRow, TKey> row_key, Func<TValue, TKey> key, out IObserver<TableUpdate<TValue, TKey>> items_observer, out ListCollectionView<TValue> items_view) where TKey : struct, IEquatable<TKey> {
|
||||
var filter = new TableUpdateItemsFilter<TRow, TValue, TKey, TKey[]>(factory, row_key, key, keys => keys, keys => keys.ToArray());
|
||||
items_observer = filter;
|
||||
items_view = filter.items_view;
|
||||
return filter;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Implementation (private)
|
||||
@@ -73,7 +84,7 @@ namespace mbts.spark.filters {
|
||||
public IReadOnlyCollection<TValue> items { get; }
|
||||
public ObservableCollectionEx<TValue> selected_items { get; } = new ObservableCollectionEx<TValue>();
|
||||
|
||||
public override TValue[] get_state() => selected_items.ToLArray();
|
||||
public override TValue[] get_state() => selected_items.ToArray();
|
||||
protected override void _clear() {
|
||||
_destroy_snapshot();
|
||||
selected_items.Clear();
|
||||
@@ -133,10 +144,10 @@ namespace mbts.spark.filters {
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.OnDispatcher().Subscribe(update => {
|
||||
foreach (var v in update.added)
|
||||
_items_set.Add(v);
|
||||
foreach (var v in update.removed.Except(_selected_items_set))
|
||||
_items_set.Remove(v);
|
||||
foreach (var v in update.added)
|
||||
_items_set.Add(v);
|
||||
_unsorted_items.sync_with(_items_set);
|
||||
});
|
||||
}
|
||||
@@ -238,6 +249,75 @@ namespace mbts.spark.filters {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TableUpdateItemsFilter<TRow, TValue, TKey, TState> : FilterVMBase<TRow, TValue, TState>, IObserver<TableUpdate<TValue, TKey>> where TKey : struct, IEquatable<TKey> {
|
||||
readonly ObservableKeyedCollection<TValue, TKey> _items;
|
||||
readonly KeyedMultiSelector<TValue, TKey> _selection_model;
|
||||
readonly Func<TValue, TKey> _key;
|
||||
readonly Func<TRow, TValue> _factory;
|
||||
readonly Func<TRow, TKey> _row_key;
|
||||
readonly Func<TState, IEnumerable<TKey>> _load_state;
|
||||
readonly Func<IEnumerable<TKey>, TState> _get_state;
|
||||
readonly IEqualityComparer<TValue> _comparer;
|
||||
readonly HashSet<TKey> _selected_keys = new HashSet<TKey>(), _desired_keys = new HashSet<TKey>();
|
||||
ListCollectionView<TValue> _items_view;
|
||||
|
||||
public TableUpdateItemsFilter(Func<TRow, TValue> factory, Func<TRow, TKey> row_key, Func<TValue, TKey> key, Func<TState, IEnumerable<TKey>> load_state, Func<IEnumerable<TKey>, TState> get_state)
|
||||
: base(factory, null) {
|
||||
_factory = factory;
|
||||
_row_key = row_key;
|
||||
_key = key;
|
||||
_load_state = load_state;
|
||||
_get_state = get_state;
|
||||
_comparer = EqualityComparer.Create(_key);
|
||||
_items = new ObservableKeyedCollection<TValue, TKey>(_key);
|
||||
_selection_model = new KeyedMultiSelector<TValue, TKey>(_items, _key);
|
||||
}
|
||||
|
||||
public ObservableCollection<TValue> items => _items;
|
||||
public ListCollectionView<TValue> items_view => _items_view ?? (_items == null ? null : (_items_view = new ListCollectionView<TValue>(_items)));
|
||||
public IObservableCollectionEx<TValue> selected_items => _selection_model?.selected_items;
|
||||
|
||||
void IObserver<TableUpdate<TValue, TKey>>.OnCompleted() { }
|
||||
void IObserver<TableUpdate<TValue, TKey>>.OnError(Exception error) { }
|
||||
void IObserver<TableUpdate<TValue, TKey>>.OnNext(TableUpdate<TValue, TKey> table) {
|
||||
foreach (var v in table.removed)
|
||||
_items.Remove(_key(v));
|
||||
foreach (var ch in table.changes)
|
||||
_items.Replace(_key(ch.Old), ch.New);
|
||||
foreach (var v in table.added)
|
||||
_items.Add(v);
|
||||
var newly_selected_keys = _desired_keys.Where(_selection_model.try_select).ToList();
|
||||
if (newly_selected_keys.Count > 0) {
|
||||
_selected_keys.AddRange(newly_selected_keys);
|
||||
_desired_keys.removeRange(newly_selected_keys);
|
||||
_raise_filter_changed();
|
||||
}
|
||||
}
|
||||
|
||||
public override TState get_state() => _get_state(_selection_model.selected_items.Select(_key));
|
||||
protected override bool _get_active() => _selection_model.selected_items.Count > 0;
|
||||
|
||||
protected override void _apply() {
|
||||
_selected_keys.SetItems(selected_items.Select(_key));
|
||||
base._apply();
|
||||
}
|
||||
|
||||
protected override void _clear() {
|
||||
_destroy_snapshot();
|
||||
_selection_model.selected_items.Clear();
|
||||
_close();
|
||||
_raise_filter_changed();
|
||||
}
|
||||
|
||||
protected override bool _filter(TValue value) => _selected_keys.Contains(_key(value));
|
||||
protected override void _load_state_internal(TState state) {
|
||||
foreach (var k in _load_state(state).empty_if_null()) {
|
||||
if (!_selection_model.try_select(k))
|
||||
_desired_keys.Add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
[Browsable(false)]
|
||||
@@ -262,4 +342,4 @@ namespace mbts.spark.filters {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace mbts.spark.filters {
|
||||
protected FilterVMBase(Func<TRow, TValue> get_row_val, string prop_name) {
|
||||
_get_row_val = get_row_val;
|
||||
this.prop_name = prop_name;
|
||||
_active = backing(nameof(active), () => HasErrors ? new bool?() : _get_active());
|
||||
_active = backing(nameof(active), () => HasErrors ? new bool?() : _get_active(), false);
|
||||
_snapshot = _get_snapshot();
|
||||
apply = new DelegateCommand(_apply);
|
||||
cancel = new DelegateCommand(_cancel);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:msp="clr-namespace:mbts.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/DoubleMinMaxFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/IntMinMaxFilterControl.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/UIntMinMaxFilterControl.xaml"/>
|
||||
@@ -146,6 +147,30 @@
|
||||
/>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="AlgoTypeTemplate">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items_view}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.GroupStyle>
|
||||
<GroupStyle ContainerStyle="{StaticResource ComboBox.DelineatedGroupContainerStyle}"/>
|
||||
</c:ListBoxEx.GroupStyle>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding name}" BadgeFill="{Binding badge_fill}" Margin="6,0,0,0"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="YesNoTemplate">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
|
||||
@@ -532,7 +532,7 @@
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgeTicks_TickUpDownNullableInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgeTicks_TickUpDownNullableUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
@@ -556,7 +556,7 @@
|
||||
<TextBlock VerticalAlignment="Center">Hedge Ticks</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="This is the number of ticks to offset the hedge."/>
|
||||
</StackPanel>
|
||||
<c:TickUpDownNullableInt x:Name="PART_HedgeTicks_TickUpDownNullableInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding hedge_ticks}"/>
|
||||
<c:TickUpDownNullableUInt x:Name="PART_HedgeTicks_TickUpDownNullableUInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding hedge_ticks}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Hedge_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
|
||||
@@ -233,6 +233,22 @@
|
||||
<TextBlock Text="{Binding Definition.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Save \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_SetDefaults_MenuItem"
|
||||
Command="{TemplateBinding SetDefaultsCommand}"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.RubberStamp}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock>
|
||||
Make current paramaters the local defaults.
|
||||
<LineBreak/>
|
||||
<Italic>These values will be lost when the ladder closes.</Italic>
|
||||
</TextBlock>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_Undo_MenuItem"
|
||||
Command="{TemplateBinding UndoCommand}"
|
||||
@@ -291,13 +307,11 @@
|
||||
CommandParameter="{Binding Definition, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
HorizontalContentAlignment="Center"
|
||||
Style="{StaticResource AlgoMenuItem}"
|
||||
ToolTip="Revert parameters to the local defaults."
|
||||
>
|
||||
<MenuItem.Header>
|
||||
<Image Source="{x:Static u:Icons.UndoCircular}"/>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.ToolTip>
|
||||
<TextBlock Text="{Binding Definition.name, RelativeSource={RelativeSource TemplatedParent}, StringFormat='{}Undo changes to \'{0}\''}"/>
|
||||
</MenuItem.ToolTip>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
x:Name="PART_IsFavorite_MenuItem"
|
||||
|
||||
@@ -124,8 +124,8 @@ namespace mbts.ui.spark.algo {
|
||||
public static readonly DependencyProperty ShowRevertDefinitionProperty =
|
||||
WPFHelper.create_dp<bool, AlgoTemplateToolBar>(
|
||||
nameof(ShowRevertDefinition),
|
||||
(o, v) => VisualStateNames.RevertDefinition.apply(o._vsm, v),
|
||||
coerce_require_persisted);
|
||||
VisualStateNames.RevertDefinition.apply/*,
|
||||
coerce_require_persisted*/);
|
||||
|
||||
public bool ShowRevertDefinition { get => (bool)GetValue(ShowRevertDefinitionProperty); set => SetValue(ShowRevertDefinitionProperty, value); }
|
||||
#endregion
|
||||
@@ -156,7 +156,7 @@ namespace mbts.ui.spark.algo {
|
||||
(o, v) => {
|
||||
o.CoerceValue(ShowRenameDefinitionProperty);
|
||||
o.CoerceValue(ShowSaveDefinitionProperty);
|
||||
o.CoerceValue(ShowRevertDefinitionProperty);
|
||||
//o.CoerceValue(ShowRevertDefinitionProperty);
|
||||
o.CoerceValue(ShowUndoRedoButtonsProperty);
|
||||
});
|
||||
|
||||
@@ -214,6 +214,12 @@ namespace mbts.ui.spark.algo {
|
||||
public ICommand SaveDefinitionCommand { get => GetValue(SaveDefinitionCommandProperty) as ICommand; set => SetValue(SaveDefinitionCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region SetDefaults
|
||||
public static readonly DependencyProperty SetDefaultsCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(SetDefaultsCommand));
|
||||
public ICommand SetDefaultsCommand { get => GetValue(SetDefaultsCommandProperty) as ICommand; set => SetValue(SetDefaultsCommandProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region Revert
|
||||
public static readonly DependencyProperty RevertCommandProperty =
|
||||
WPFHelper.create_dp<ICommand, AlgoTemplateToolBar>(nameof(RevertCommand));
|
||||
@@ -435,7 +441,7 @@ namespace mbts.ui.spark.algo {
|
||||
public static class VisualStateNames {
|
||||
public static void apply_all(AlgoTemplateToolBar toolbar) {
|
||||
UndoRedo.apply(toolbar._vsm, toolbar.ShowUndoRedoButtons);
|
||||
RevertDefinition.apply(toolbar._vsm, toolbar.ShowRevertDefinition);
|
||||
RevertDefinition.apply(toolbar, toolbar.ShowRevertDefinition);
|
||||
RenameDefinition.apply(toolbar._vsm, toolbar.ShowRenameDefinition);
|
||||
SaveDefinition.apply(toolbar._vsm, toolbar.ShowSaveDefinition);
|
||||
TemplatesDropDown.apply(toolbar._vsm, toolbar.ShowTemplatesDropDown);
|
||||
@@ -457,8 +463,8 @@ namespace mbts.ui.spark.algo {
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
|
||||
public static void apply(VisualStateModel vsm, bool active) =>
|
||||
vsm.set_state(GroupName, active ? Active : Inactive);
|
||||
public static void apply(AlgoTemplateToolBar owner, bool active) =>
|
||||
owner.Dispatcher.InvokeAsync(() => owner._vsm.set_state(GroupName, active ? Active : Inactive));
|
||||
}
|
||||
public static class RenameDefinition {
|
||||
public const string GroupName = nameof(RenameDefinition);
|
||||
|
||||
@@ -150,7 +150,8 @@
|
||||
UndoDefinitionCommand="{Binding undo_definition_cmd}"
|
||||
RedoDefinitionCommand="{Binding redo_definition_cmd}"
|
||||
ShowUndoRedoButtons="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarUndoRedoDefinitionKey}}"
|
||||
RevertDefinitionCommand="{Binding revert_definition_cmd}"
|
||||
SetDefaultsCommand="{Binding set_default_algo_params_cmd}"
|
||||
RevertDefinitionCommand="{Binding revert_algo_params_cmd}"
|
||||
ShowRevertDefinition="{DynamicResource {x:Static sp:AlgoStudioWindow.ShowAlgoToolBarRevertDefinitionKey}}"
|
||||
SaveCommand="{Binding save_template_cmd}"
|
||||
SaveDefinitionCommand="{Binding save_definition_cmd}"
|
||||
@@ -202,6 +203,7 @@
|
||||
x:Name="ParamGridView"
|
||||
Grid.Row="1"
|
||||
Margin="12,8,12,12"
|
||||
AnimateDirty="False"
|
||||
CanUserChangeAlgoRoot="False"
|
||||
AlgoParamRows="{Binding algo_param_rows}"
|
||||
Visibility="Visible"
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
<cv:KeyValue Key="{x:Static Ratio, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{u:True}"/>
|
||||
<cv:KeyValue Key="{x:Static Absolute, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{u:False}"/>
|
||||
</cv:ValueMap>
|
||||
<cv:ValueMap x:Key="QuantityCaseVisualStateConverter" Default="{x:Null}">
|
||||
<cv:KeyValue Key="{x:Static Absolute, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{x:Static Absolute, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}"/>
|
||||
<cv:KeyValue Key="{x:Static Ratio, MemberType={u:Nested sp:QuantityMeasure.QuantityOneofCase}}" Value="{x:Static Ratio, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}"/>
|
||||
</cv:ValueMap>
|
||||
<!--Component Styles-->
|
||||
<Style x:Key="AbsoluteTickUpDownStyle" TargetType="c:TickUpDownUInt">
|
||||
<Setter Property="MinWidth" Value="{StaticResource TickUpDownMinWidth}"/>
|
||||
@@ -52,13 +48,20 @@
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Grid.TemplatedParent).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={StaticResource QuantityCaseVisualStateConverter}}"/>
|
||||
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(Grid.TemplatedParent).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={x:Static uag:QuantityMeasureControl.QuantityCaseVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
|
||||
<VisualState/>
|
||||
<VisualState Name="{x:Static None, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Absolute, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
@@ -113,13 +116,20 @@
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager AttachToMembers="True" OverrideTarget="True" Target="{Binding Path='(Border.TemplatedParent)', Source={u:DirectReference Border}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={StaticResource QuantityCaseVisualStateConverter}}"/>
|
||||
<u:StateDefinition State="{Binding Path='(u:StateDefinition.Group).(u:StateGroupsManager.Target).(uag:QuantityMeasureControl.QuantityCase)', RelativeSource={RelativeSource Self}, Converter={x:Static uag:QuantityMeasureControl.QuantityCaseVisualStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
|
||||
<VisualState/>
|
||||
<VisualState Name="{x:Static None, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Absolute, MemberType={u:Nested uag:QuantityMeasureControl.VisualStateNames.QuantityCase}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_Absolute_TickUpDown" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using bts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark.algo {
|
||||
@@ -12,6 +14,10 @@ namespace mbts.ui.spark.algo {
|
||||
/// Interaction logic for QuantityMeasure.xaml
|
||||
/// </summary>
|
||||
class QuantityMeasureControl : Control {
|
||||
// converters
|
||||
public static readonly IValueConverter QuantityCaseVisualStateConverter =
|
||||
ValueConverter.create<QuantityMeasure.QuantityOneofCase, string>(VisualStateNames.QuantityCase.fmt_state_name);
|
||||
|
||||
// defaults
|
||||
static readonly QuantityMeasureImmutable _default_quantity_measure = new QuantityMeasureImmutable(new QuantityMeasure { Absolute = 0 });
|
||||
static uint _default_absolute => _default_quantity_measure.QuantityCase == bts.spark.QuantityMeasure.QuantityOneofCase.Ratio ? _default_quantity_measure.Absolute : default;
|
||||
@@ -173,8 +179,12 @@ namespace mbts.ui.spark.algo {
|
||||
public const string GroupName = nameof(QuantityCase);
|
||||
|
||||
public static readonly string
|
||||
Absolute = $"{GroupName}{nameof(Absolute)}",
|
||||
Ratio = $"{GroupName}{nameof(Ratio)}";
|
||||
None = fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase.None),
|
||||
Absolute = fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase.Absolute),
|
||||
Ratio = fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase.Ratio);
|
||||
|
||||
public static string fmt_state_name(bts.spark.QuantityMeasure.QuantityOneofCase quantity_case) =>
|
||||
$"{GroupName}{quantity_case}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace mbts.ui.converters
|
||||
{
|
||||
public class EnumCaseStringConverter<T> : IValueConverter where T : struct
|
||||
{
|
||||
namespace mbts.ui.converters {
|
||||
class EnumCaseStringConverter<T> : IValueConverter where T : struct {
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
|
||||
T t = default(T);
|
||||
if (value is T)
|
||||
t = (T)value;
|
||||
string raw_str = t.ToString();
|
||||
return raw_str.Substring(0, 1) + new String(raw_str.Skip(1).Select(Char.ToLower).ToLArray());
|
||||
string raw_str = (value is T t ? t : default).ToString();
|
||||
return raw_str.Substring(0, 1) + new string(raw_str.Skip(1).Select(char.ToLower).ToArray());
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
|
||||
T t = default(T);
|
||||
string str = value as string;
|
||||
if (str != null)
|
||||
Enum.TryParse<T>(str, true, out t);
|
||||
return t;
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) =>
|
||||
value is string str && Enum.TryParse(str, true, out T result) ? result : default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,12 +106,20 @@ namespace mbts.ui
|
||||
var effect = DragDrop.DoDragDrop(_source, data, DragDropEffects.Move);
|
||||
}
|
||||
catch (Exception error) { Log.Ui.warn("Dragdrop failed: " + error.ToString()); }
|
||||
await show_document(_source, _source_window, true);
|
||||
_source = null;
|
||||
_source_window = null;
|
||||
/// Reentrancy Risk:
|
||||
/// During the await period for <see cref="show_document(TabDocument, FloatingWindow, bool)"/>,
|
||||
/// the new window hooks up to the dragdrop events via <see cref="attach_dragdrop_events(ItemsControl)"/>.
|
||||
/// It must be assured that the new window does not get a callback into <see cref="container_PreviewMouseMove(ItemsControl, MouseEventArgs)"/>
|
||||
/// and attempt to create a second <see cref="DragDropDocumentPreview"/> for the same instance <see cref="_source"/>.
|
||||
/// For this reason, <see cref="_source"/> is set to null before <see cref="show_document(TabDocument, FloatingWindow, bool)"/> is called.
|
||||
_source_image = null;
|
||||
_target = null;
|
||||
_dddp = null;
|
||||
var src = _source;
|
||||
var src_win = _source_window;
|
||||
_source = null;
|
||||
_source_window = null;
|
||||
await show_document(src, src_win, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
static class RoutedSparkCommands {
|
||||
public static RoutedCommand LaunchAlgoStudioCommand = new RoutedCommand();
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler
|
||||
LaunchAlgoStudioExecuted = _executed(SparkTags.ShowAlgoStudioTag);
|
||||
|
||||
public static readonly CanExecuteRoutedEventHandler
|
||||
LaunchAlgoStudioCanExecute = (s, e) => e.CanExecute = true;
|
||||
|
||||
static ExecutedRoutedEventHandler _executed<T>(ITag<T> tag) =>
|
||||
(sender, e) => {
|
||||
if (sender is FrameworkElement el && el.TryFindResource("Bus") is IBus bus)
|
||||
bus.post(tag, ConvertUtils.try_convert<T>(e.Parameter));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,12 @@ namespace mbts.ui {
|
||||
|
||||
static void _on_preview_mouse_wheel(object sender, MouseWheelEventArgs e) {
|
||||
if (!e.Handled && sender is ScrollViewer sv && !_reentrancy_list.Contains(e)) {
|
||||
if (e.Source is GridViewColumnHeader && Window.GetWindow(sv) == null) {
|
||||
// This is a hack specifically for the DIYGrid filter popup (namely, the DiscreteValues ListBox-based template - it doesn't scroll otherwise)...i really, really hate this bug.
|
||||
// The end of the cycle approaches, so unf there is not time to dig deeper into this and find a cleaner fix.
|
||||
// - John, 05/06/2019
|
||||
return;
|
||||
}
|
||||
var preview_event_arg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) {
|
||||
RoutedEvent = UIElement.PreviewMouseWheelEvent,
|
||||
Source = sender
|
||||
|
||||
@@ -171,6 +171,9 @@ namespace mbts.ui.shell
|
||||
var shell_window = document.ViewModel.Model;
|
||||
var mcs = shell_window as bts.shell.IMeasureContentSize;
|
||||
if (mcs != null) {
|
||||
if (shell_window is ISlowMeasureContentSize) {
|
||||
Show();
|
||||
}
|
||||
var size =
|
||||
shell_window is ISlowWindow || shell_window is ISharedWindow ?
|
||||
await await document.ViewModel.Dispatcher.InvokeAsync(() => mcs.MeasureContentSize()) :
|
||||
|
||||
@@ -19,6 +19,21 @@ namespace bts.settings {
|
||||
var from_persist = new UserUISettings(user_list.Users.First(u => u.Login == from).Id, persist, null);
|
||||
|
||||
var workspaces = from_persist.query<WorkspaceDefinition>(WorkspacesModel.WORKSPACE_NAMESPACE, ".*").Result;
|
||||
|
||||
ConsoleEx.WriteLine("Workspace Descriptions", ConsoleColor.Cyan);
|
||||
foreach(var w in workspaces) {
|
||||
var r = from ws in w.value.windows
|
||||
group ws by ws.owner into grouping
|
||||
select new { key = grouping.Key.Replace("bts.shell.", ""), window_count = grouping.Count() };
|
||||
string desc = string.Empty;
|
||||
foreach(var i in r) {
|
||||
desc += $"{Environment.NewLine}\t{i.window_count} - {i.key}";
|
||||
}
|
||||
ConsoleEx.Write($"{w.key}", ConsoleColor.Yellow);
|
||||
ConsoleEx.WriteLine($" - {w.value.windows.Count} windows - {w.value.named_windows.Count} named windows - {desc}");
|
||||
}
|
||||
ConsoleEx.WriteLine("");
|
||||
|
||||
var selected_ws = ConsoleEx.prompt("Choose workspace:", workspaces.Select(w => w.key));
|
||||
|
||||
var to = ConsoleEx.prompt("To User", user_names);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
@@ -69,6 +70,7 @@ namespace mbts.spark
|
||||
_current_row = backing(nameof(current_row), default(TRow));
|
||||
|
||||
readonly Property<TRow> _current_row;
|
||||
public event Action<(IEnumerable<TRow> removed, IEnumerable<TRow> added)> selection_changed;
|
||||
|
||||
public new TRow current_row { get => _current_row.Value; set => _current_row.Value = value ?? selected_rows.FirstOrDefault(); }
|
||||
public override IObservable<TRow> current_row_observable => _current_row.Select(v => v.newval);
|
||||
@@ -77,35 +79,10 @@ namespace mbts.spark
|
||||
RowSelectionHelper.apply_changes(selected_rows, e);
|
||||
if (selected_rows.Count == 0) _current_row.Value = null;
|
||||
else if (_current_row.Value == null) _current_row.Value = selected_rows[0];
|
||||
selection_changed?.Invoke((e.RemovedItems.OfTypeSafe<TRow>(), e.AddedItems.OfTypeSafe<TRow>()));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SecurityGrid<TRow, TRowModel, TCol> : SecurityGridBase<TRow, TRowModel>, IHandleCellSelection where TRow : class, ISecurityRow
|
||||
{
|
||||
protected SecurityGrid(IObservable<IEnumerable<TRowModel>> row_models, IEnumerable<SortingCriteria> sorting_criteria)
|
||||
: this(RXCollection.Create(row_models), sorting_criteria) { }
|
||||
|
||||
protected SecurityGrid(IObservable<IEnumerable<TRowModel>> row_models, IEqualityComparer<TRowModel> comparer, IEnumerable<SortingCriteria> sorting_criteria)
|
||||
: this(RXCollection.Create(row_models, comparer), sorting_criteria) { }
|
||||
|
||||
protected SecurityGrid(IRXCollection<TRowModel> row_models, IEnumerable<SortingCriteria> sorting_criteria) : base(row_models, sorting_criteria) =>
|
||||
_current_cell = backing<DataGridCellInfo>(nameof(current_cell));
|
||||
|
||||
readonly Property<DataGridCellInfo> _current_cell;
|
||||
IDictionary<TRow, HashSet<TCol>> _selection_map = new Dictionary<TRow, HashSet<TCol>>();
|
||||
|
||||
public DataGridCellInfo current_cell {
|
||||
get => _current_cell.Value;
|
||||
set { if (value.IsValid) _current_cell.Value = value; }
|
||||
}
|
||||
public override TRow current_row => _current_cell.Value.Item as TRow ?? selected_rows.FirstOrDefault();
|
||||
public override IObservable<TRow> current_row_observable => _current_cell.Select(v => v.newval.Item as TRow);
|
||||
public IDictionary<TRow, HashSet<TCol>> get_selection_map() => new Dictionary<TRow, HashSet<TCol>>(_selection_map);
|
||||
|
||||
void IHandleCellSelection.cell_selection_changed(IList<DataGridCellInfo> added, IList<DataGridCellInfo> removed) =>
|
||||
CellSelectionMapper.apply_changes(_selection_map, added, removed, selected_rows);
|
||||
}
|
||||
|
||||
static class SecurityGridExtensions
|
||||
{
|
||||
public static IObservable<SecurityMD?> current_security_md_observable<TRow, TRowModel>(this SecurityGridBase<TRow, TRowModel> security_grid, IBus bus)
|
||||
@@ -122,23 +99,14 @@ namespace mbts.spark
|
||||
where TRow : class, ISecurityRow =>
|
||||
security_grid.selected_securities.ObserveSelf();
|
||||
|
||||
public static IObservable<IReadOnlyList<SecurityImmutable>> sorted_selected_securities_observable<TRow, TRowModel>(this SecurityGridBase<TRow, TRowModel> security_grid)
|
||||
where TRow : class, ISecurityRow =>
|
||||
security_grid.selected_rows
|
||||
.ObserveSelf()
|
||||
.Select(rows =>
|
||||
rows.Select(
|
||||
row => (security: row.security, row_index: security_grid.rows_view.indexOf(row))
|
||||
)
|
||||
.OrderBy(sec_vs_index => sec_vs_index.row_index)
|
||||
.Select(sec_vs_index => sec_vs_index.security)
|
||||
.ToReadOnlyList()
|
||||
);
|
||||
|
||||
|
||||
|
||||
public static IObservable<IReadOnlyList<SecurityMD>> selected_security_mds_observable<TRow, TRowModel>(this SecurityMDGrid<TRow, TRowModel> security_md_grid)
|
||||
where TRow : class, ISecurityMDRow =>
|
||||
security_md_grid.selected_rows.ObserveSelf(true).Select(rows => rows.Select(r => r.md).ToReadOnlyList());
|
||||
Observable.Create<IReadOnlyList<SecurityMD>>(o => {
|
||||
var sec_md_list = security_md_grid.selected_rows.WrapSelectorRO(r => r.md);
|
||||
o.OnNext(sec_md_list);
|
||||
security_md_grid.selection_changed += on_selection_changed;
|
||||
return Disposable.Create(() => security_md_grid.selection_changed += on_selection_changed);
|
||||
void on_selection_changed((IEnumerable<TRow> removed, IEnumerable<TRow> added) change) => o.OnNext(sec_md_list);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,16 +21,4 @@ namespace mbts.spark
|
||||
public IObservable<SecurityMD?> current_security_md =>
|
||||
current_row_observable.Select(r => r?.md).DistinctUntilChanged(EqualityComparer.CreateNullable<SecurityMD, uint>(md => md.security.Id));
|
||||
}
|
||||
|
||||
abstract class SecurityMDGrid<TRow, TRowModel, TCol> : SecurityGrid<TRow, TRowModel, TCol> where TRow : class, ISecurityMDRow
|
||||
{
|
||||
protected SecurityMDGrid(IObservable<IEnumerable<TRowModel>> row_models, IEnumerable<SortingCriteria> sorting_criteria)
|
||||
: base(row_models, sorting_criteria) { }
|
||||
|
||||
protected SecurityMDGrid(IObservable<IEnumerable<TRowModel>> row_models, IEqualityComparer<TRowModel> comparer, IEnumerable<SortingCriteria> sorting_criteria)
|
||||
: base(row_models, comparer, sorting_criteria) { }
|
||||
|
||||
public IObservable<SecurityMD?> current_security_md =>
|
||||
current_row_observable.Select(r => r?.md).DistinctUntilChanged(EqualityComparer.CreateNullable<SecurityMD, uint>(md => md.security.Id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.md;
|
||||
using mbts.config;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
public struct MBOPopupData {
|
||||
public readonly Side side;
|
||||
public readonly IReadOnlyList<OrderPositionImmutable> orders;
|
||||
|
||||
public MBOPopupData(Side side, IReadOnlyList<OrderPositionImmutable> orders) {
|
||||
this.side = side;
|
||||
this.orders = orders;
|
||||
}
|
||||
}
|
||||
public struct MBOPopupDrawParams {
|
||||
public MBOPopupData data;
|
||||
public double width => 300.0;
|
||||
public double height;
|
||||
public FormattedText header;
|
||||
|
||||
public static readonly Thickness margin = new Thickness(10, 5, 5, 5);
|
||||
|
||||
//columns
|
||||
public double row_height => header.Height + 2;
|
||||
public double bar_height => 0.6 * row_height;
|
||||
|
||||
public List<FormattedText> row_numbers;
|
||||
public double numbers_width;
|
||||
public List<FormattedText> row_labels;
|
||||
public double scale;
|
||||
}
|
||||
|
||||
public class MBOPopupModel {
|
||||
const double font_size = 9.0;
|
||||
readonly MBOService _mbo;
|
||||
readonly SecurityImmutable _security;
|
||||
readonly MBOSecurityQueryImmutable _query;
|
||||
readonly Dictionary<string, FormattedText> _cache;
|
||||
int _max_order_size = 1;
|
||||
double _height = 0;
|
||||
double _number_width = 0;
|
||||
|
||||
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query) {
|
||||
_mbo = mbo;
|
||||
_security = security;
|
||||
_query = query;
|
||||
|
||||
// prime the cache with numbers on the left hand side
|
||||
_cache =
|
||||
Enumerable.Range(1, 1+(int)default_query.Limit)
|
||||
.AsParallel()
|
||||
.Select(i => TextUtils.GetFormattedText(i.ToString(), font_size))
|
||||
.ToDictionary(ft => ft.Text);
|
||||
}
|
||||
|
||||
FormattedText format_text(string s, bool bold=false) {
|
||||
// cache FormattedText for the life of the popup
|
||||
if (_cache.TryGetValue(s, out FormattedText ft)) {
|
||||
return ft;
|
||||
}
|
||||
else
|
||||
ft = bold ? TextUtils.GetFormattedTextBold(s, font_size) : TextUtils.GetFormattedText(s, font_size);
|
||||
_cache.TryAdd(s, ft);
|
||||
return ft;
|
||||
}
|
||||
|
||||
public async Task<Result<MBOPopupData>> load_orders() {
|
||||
if (_mbo == null) return new Result<MBOPopupData>(ApiError.NoServer);
|
||||
var data = await _mbo.get_orders_for_security_async(_query);
|
||||
if (data.success) {
|
||||
var side = data.value.side();
|
||||
IEnumerable<OrderPosition> orders = new OrderPosition[0];
|
||||
if (side == Side.Buy) orders = data.value.Bid;
|
||||
if (side == Side.Sell) orders = data.value.Ask;
|
||||
return new Result<MBOPopupData>(new MBOPopupData(side, orders.Select(o => (OrderPositionImmutable)o).ToList()));
|
||||
}
|
||||
else {
|
||||
return new Result<MBOPopupData>(data.code);
|
||||
}
|
||||
}
|
||||
|
||||
string buy_sell_text(Side side) {
|
||||
switch (side) {
|
||||
case Side.Buy: return "BUY";
|
||||
case Side.Sell: return "SELL";
|
||||
default:
|
||||
case Side.Undefined: return "";
|
||||
}
|
||||
}
|
||||
|
||||
public MBOPopupDrawParams compute_layout(MBOPopupData p) {
|
||||
var pdp = new MBOPopupDrawParams { data = p };
|
||||
pdp.header = format_text(
|
||||
$"{buy_sell_text(p.side)} {_security.security_desc()} @ {_security.ContractSpec.format(_query.PriceLevel)}", true);
|
||||
|
||||
var margin = MBOPopupDrawParams.margin;
|
||||
var text_margin = margin.Left + margin.Right;
|
||||
|
||||
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString())).ToList();
|
||||
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
|
||||
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString())).ToList();
|
||||
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
|
||||
|
||||
var available_width = pdp.width - pdp.numbers_width - max_label_width;
|
||||
_max_order_size = Math.Max(p.orders.Max(o => o.Size), _max_order_size);
|
||||
pdp.scale = available_width / _max_order_size;
|
||||
|
||||
pdp.height = _height =
|
||||
Math.Max(_height,
|
||||
pdp.header.Height + margin.Top + margin.Bottom + (pdp.row_height * p.orders.Count) + margin.Bottom);
|
||||
|
||||
return pdp;
|
||||
}
|
||||
|
||||
#region static
|
||||
public static readonly MBOSecurityQueryImmutable default_query = (MBOSecurityQueryImmutable)
|
||||
new MBOSecurityQuery {
|
||||
Limit = Configuration.get<bts.config.LadderKnobs>().max_mbo_orders
|
||||
};
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Collections.Generic;
|
||||
using bts.api.orderman;
|
||||
using System.Linq;
|
||||
using bts.md;
|
||||
using bts;
|
||||
using mbts.ui;
|
||||
using System.Windows.Input;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using mbts.ui.utility.text;
|
||||
using bts.api;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class MBOPopupView : UIElement, IRequestViewClose {
|
||||
readonly Brush _background = BTSColors.LadderSpecial.default_column_background;
|
||||
readonly MBOPopupModel _model;
|
||||
MBOPopupDrawParams _dp;
|
||||
|
||||
public MBOPopupView(MBOPopupModel model, MBOPopupData p) {
|
||||
_model = model;
|
||||
_dp = model.compute_layout(p);
|
||||
IsVisibleChanged += (o, _) => poll_orders();
|
||||
}
|
||||
|
||||
private async void poll_orders() {
|
||||
while (IsVisible) {
|
||||
var result = await _model.load_orders();
|
||||
if (result.success && result.value.orders.Count > 0) {
|
||||
Log.Ui.diag($"MBOpopup got {result.value.orders.Count} orders");
|
||||
_dp = _model.compute_layout(result.value);
|
||||
InvalidateMeasure();
|
||||
InvalidateVisual();
|
||||
}
|
||||
else RequestClose?.Invoke(this, null);
|
||||
await Task.Delay(250);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
protected override Size MeasureCore(Size availableSize) {
|
||||
return new Size(_dp.width, _dp.height);
|
||||
}
|
||||
|
||||
static Thickness margin => MBOPopupDrawParams.margin;
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
base.OnRender(dc);
|
||||
// draw background
|
||||
dc.DrawRectangle(_background, null, new Rect(0, 0, _dp.width, _dp.height));
|
||||
// draw header
|
||||
dc.DrawText(_dp.header, new Point(margin.Left, margin.Top));
|
||||
// draw grid
|
||||
draw_grid(dc, _dp, _dp.header.Height + margin.Top + margin.Bottom);
|
||||
}
|
||||
|
||||
static Brush brush_for(Side side, OrderPositionImmutable order) {
|
||||
if (order.OrderId != 0) return Brushes.Black;
|
||||
switch (side) {
|
||||
case Side.Buy: return BTSColors.MarketData.bid_brush;
|
||||
case Side.Sell: return BTSColors.MarketData.ask_brush;
|
||||
default:
|
||||
return Brushes.DarkGray;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_grid(DrawingContext dc, MBOPopupDrawParams dp, double offset) {
|
||||
for (int x = 0; x < dp.data.orders.Count(); ++x) {
|
||||
// draw numbers
|
||||
var number = dp.row_numbers[x];
|
||||
var left_offset = dp.numbers_width - number.Width - 5;
|
||||
dc.DrawText(number, new Point(left_offset, offset));
|
||||
// draw rectangles
|
||||
var order = dp.data.orders[x];
|
||||
var rect = new Rect(dp.numbers_width, offset + (dp.row_height-dp.bar_height)/2, order.Size * dp.scale, dp.bar_height);
|
||||
dc.DrawRectangle(brush_for(dp.data.side, order), null,rect);
|
||||
// draw sizes
|
||||
var label = dp.row_labels[x];
|
||||
dc.DrawText(label, new Point(rect.Right+margin.Right, offset));
|
||||
offset += dp.row_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using bts.api;
|
||||
using bts.md;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class PositionDataExtensions {
|
||||
public static Side side(this PositionData pd) {
|
||||
var bidlen = pd.Bid.Count;
|
||||
var asklen = pd.Ask.Count;
|
||||
if (bidlen > 0 && asklen == 0) {
|
||||
return Side.Buy;
|
||||
}
|
||||
else if (asklen > 0 && bidlen == 0) {
|
||||
return Side.Sell;
|
||||
}
|
||||
return Side.Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark {
|
||||
static class SecurityDataHelper {
|
||||
static class SecurityDataHelper {
|
||||
// Ice
|
||||
public static string market_description(this SecurityImmutable security) => security.HasIce ? security.Ice.MarketDescription : string.Empty;
|
||||
public static string product_name(this SecurityImmutable security) => security.HasIce ? security.Ice.ProductName : string.Empty;
|
||||
public static string cleared_alias(this SecurityImmutable security) => security.HasIce ? security.Ice.ClearedAlias : string.Empty;
|
||||
public static string hub_alias(this SecurityImmutable security) => security.HasIce ? security.Ice.HubAlias : string.Empty;
|
||||
public static string strip_name(this SecurityImmutable security) => security.HasIce ? security.Ice.StripName : string.Empty;
|
||||
public static string mic_code(this SecurityImmutable security) => security.HasIce ? security.Ice.MicCode : string.Empty;
|
||||
|
||||
public static class EqualityComparer {
|
||||
public static readonly IEqualityComparer<SecurityMD> MDComparer = bts.utils.EqualityComparer.Create<SecurityMD>(Equals, GetHashCode);
|
||||
public static bool Equals(SecurityMD x, SecurityMD y) => x.security.Id == y.security.Id;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
class AutoClosePopup : System.Windows.Controls.Primitives.Popup {
|
||||
public AutoClosePopup() {
|
||||
StaysOpen = false;
|
||||
CommandBindings.Add(new CommandBinding(
|
||||
ApplicationCommands.Close,
|
||||
(o,_) => { IsOpen = false; }
|
||||
));
|
||||
InputBindings.Add(new KeyBinding(ApplicationCommands.Close, Key.Escape, ModifierKeys.None));
|
||||
}
|
||||
|
||||
public new FrameworkElement Child {
|
||||
get => base.Child as FrameworkElement;
|
||||
set {
|
||||
var old_child = Child;
|
||||
base.Child = value;
|
||||
hook_child_events(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void hook_child_events(FrameworkElement fe) {
|
||||
MouseDown += OnMouseDown;
|
||||
fe.MouseDown += OnMouseDown;
|
||||
fe.PreviewMouseDown += OnMouseDown;
|
||||
if (fe is IRequestViewClose vc) {
|
||||
vc.RequestClose += (o,_) => IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
if (sender == this && e.MiddleButton == MouseButtonState.Pressed) IsOpen = false;
|
||||
else {
|
||||
if (!e.Handled) {
|
||||
IsOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@ namespace mbts.co2
|
||||
[DllImport("libspark")]
|
||||
internal unsafe static extern void md_consumer_get_system_md_symbols(raw_usersymbol_callback cb);
|
||||
|
||||
|
||||
[DllImport("libspark")]
|
||||
internal unsafe static extern void dump_co2_memory_to_disk(IntPtr consumer, string path);
|
||||
|
||||
|
||||
//
|
||||
// Relay control
|
||||
|
||||
@@ -37,7 +37,10 @@ namespace mbts.co2
|
||||
unsafe void init() {
|
||||
_raw_sec_cb = (key, buf, len, handle) => {
|
||||
var security = Co2NativeMethods.deserialize<Security>(buf, len);
|
||||
var next = new SecurityAndHandle() { security = security, handle = new MDConsumerHandle(handle) };
|
||||
var next = new SecurityAndHandle() {
|
||||
security = security,
|
||||
handle = new MDConsumerHandle(_consumer, security, handle)
|
||||
};
|
||||
_securities.Add(next);
|
||||
_on_security?.Invoke(new List<SecurityAndHandle>(1) { next });
|
||||
};
|
||||
@@ -67,7 +70,7 @@ namespace mbts.co2
|
||||
public MDConsumerHandle try_get_handle(security_key key) {
|
||||
Co2NativeMethods.BlitPointerTuple raw;
|
||||
if (Co2NativeMethods.md_consumer_get_handle(_consumer, key, out raw)) {
|
||||
return new MDConsumerHandle(raw);
|
||||
return new MDConsumerHandle(this._consumer, null, raw);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -77,7 +80,10 @@ namespace mbts.co2
|
||||
List<SecurityAndHandle> result = new List<SecurityAndHandle>();
|
||||
Co2NativeMethods.raw_security_callback cb = (key, buf, len, handle) => {
|
||||
var security = Co2NativeMethods.deserialize<Security>(buf, len);
|
||||
result.Add(new SecurityAndHandle() { security = new Security(security), handle = new MDConsumerHandle(handle) });
|
||||
result.Add(
|
||||
new SecurityAndHandle() {
|
||||
security = new Security(security),
|
||||
handle = new MDConsumerHandle(_consumer, security, handle) });
|
||||
};
|
||||
Co2NativeMethods.md_consumer_walk_security_list(_consumer, cb);
|
||||
return result;
|
||||
|
||||
@@ -156,13 +156,22 @@ namespace mbts.co2 {
|
||||
//
|
||||
// Internal
|
||||
|
||||
internal MDConsumerHandle(Co2NativeMethods.BlitPointerTuple tuple) {
|
||||
internal MDConsumerHandle(
|
||||
IntPtr consumer,
|
||||
Security security,
|
||||
Co2NativeMethods.BlitPointerTuple tuple) {
|
||||
_header = (ColdHeader*)tuple.cold_header.ToPointer();
|
||||
_cold = (ColdEntry*)tuple.cold_data.ToPointer();
|
||||
|
||||
if (has_depth) {
|
||||
_hot_depth = (HotDepthEntry*)tuple.hot.ToPointer();
|
||||
Log.assert(_hot_depth->key == _cold->security_key);
|
||||
if (_hot_depth->key != _cold->security_key) {
|
||||
var temp_path = Path.Combine(Path.GetTempPath(), "btsshm");
|
||||
Co2NativeMethods.dump_co2_memory_to_disk(consumer, temp_path);
|
||||
Log.assert(
|
||||
_hot_depth->key == _cold->security_key,
|
||||
$"Please attach the contents of '${temp_path}' to the bug report you are about to file. security:{security?.Id} cold:{_cold->security_key} hot:{_hot->key}");
|
||||
}
|
||||
}
|
||||
else {
|
||||
_hot = (HotEntry*)tuple.hot.ToPointer();
|
||||
|
||||
@@ -67,8 +67,8 @@ namespace bts.api.core {
|
||||
while (true) {
|
||||
var response = await check_stream_access(method_id, qualifier);
|
||||
if (response.success) {
|
||||
if (response.value.Authorized) {
|
||||
// success. Break loop and subscribe
|
||||
if (response.value.Authorized) {
|
||||
// success. Break loop and subscribe
|
||||
break;
|
||||
}
|
||||
else {
|
||||
@@ -94,7 +94,8 @@ namespace bts.api.core {
|
||||
// Authorized. Stream events
|
||||
unsubscribe_temp_sub();
|
||||
return SubscribeNative(method, qualifier, parser, converter, observer);
|
||||
});
|
||||
});
|
||||
Log.QBus.diag($"Subscribing for sub: '{method}/{qualifier}'");
|
||||
return result;
|
||||
}
|
||||
private Task<Result<StreamAccessResponse>> check_stream_access(int method_id, string qualifier) {
|
||||
@@ -145,6 +146,7 @@ namespace bts.api.core {
|
||||
};
|
||||
var subscription = ApiNativeMethods.api_client_subscribe(_client, method, qualifier, cb, IntPtr.Zero);
|
||||
return FinalizeableAnonymousDisposable.Create((disposing) => {
|
||||
Log.QBus.diag($"Unsubscribing for sub: '{method}/{qualifier}'");
|
||||
ApiNativeMethods.api_client_unsubscribe(_client, subscription);
|
||||
subscription = IntPtr.Zero;
|
||||
GC.KeepAlive(cb);
|
||||
|
||||
@@ -206,6 +206,8 @@ namespace bts.api.core {
|
||||
}
|
||||
|
||||
public class TableUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
// warning: this does not have a key_extractor
|
||||
public static readonly TableUpdate<T, TKey> empty = new TableUpdate<T, TKey>(ImmutableDictionary<TKey, T>.Empty, Array.Empty<T>(), Array.Empty<Change<T>>(), Array.Empty<T>(), null);
|
||||
|
||||
readonly ImmutableDictionary<TKey, T> _state;
|
||||
|
||||
@@ -254,6 +256,7 @@ namespace bts.api.core {
|
||||
if (new_state.TryGetValue(kv.Key, out T new_val)) {
|
||||
if (!value_comparer.Equals(kv.Value, new_val)) {
|
||||
changes.Add(new Change<T>(kv.Value, new_val));
|
||||
builder[kv.Key] = new_val;
|
||||
}
|
||||
} else {
|
||||
removed.Add(kv.Value);
|
||||
@@ -427,6 +430,16 @@ namespace bts.api.core {
|
||||
}
|
||||
|
||||
public static class ObservableUpdateExtensions {
|
||||
public static IEnumerable<T> Old<T>(this IEnumerable<Change<T>> changes) {
|
||||
foreach (var ch in changes)
|
||||
yield return ch.Old;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> New<T>(this IEnumerable<Change<T>> changes) {
|
||||
foreach (var ch in changes)
|
||||
yield return ch.New;
|
||||
}
|
||||
|
||||
internal static bool has_changes<T, TKey>(this TableUpdate<T, TKey> table) where TKey : IEquatable<TKey> =>
|
||||
table.added.Any() || table.changes.Any() || table.removed.Any();
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ namespace bts.utils {
|
||||
for (int i = 1; i < num2; i++) if ((num1 * i) % num2 == 0) return i * num1;
|
||||
return num1 * num2;
|
||||
}
|
||||
|
||||
public static bool is_within_range(this double value, double min, double max) =>
|
||||
value >= min && value <= max;
|
||||
|
||||
public static int within_range(int min, int max, int value) {
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
|
||||
@@ -27,11 +27,11 @@ namespace System.Collections.Generic {
|
||||
public static IEnumerable<T> Intersect<T, TKey>(this IEnumerable<T> enumerable, IEnumerable<TKey> second, Func<T, TKey> key_selector) =>
|
||||
enumerable.Where(t => second.Contains(key_selector(t)));
|
||||
|
||||
public static T[] ToLArray<T>(this IEnumerable<T> enumerable) =>
|
||||
enumerable.ToList().ToArray();
|
||||
public static IEnumerable<T> OfTypeSafe<T>(this IEnumerable source) =>
|
||||
source == null ? Enumerable.Empty<T>() : source.OfType<T>();
|
||||
|
||||
public static TAccumulate AggregateWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> aggregate, Func<TAccumulate, T, bool> predicate) =>
|
||||
AggregateWhile(source, seed, aggregate, predicate, (curr, el, next) => curr);
|
||||
AggregateWhile(source, seed, aggregate, predicate, (curr, el, next) => curr);
|
||||
|
||||
public static TAccumulate AggregateWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> aggregate, Func<TAccumulate, T, bool> predicate, TAccumulate early_break_rv) =>
|
||||
AggregateWhile(source, seed, aggregate, predicate, (curr, el, next) => early_break_rv);
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ namespace bts.collections {
|
||||
yield return v;
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
public void update(IEnumerable<T> added, IEnumerable<T> changed, IEnumerable<TKey> removed) {
|
||||
foreach (var k in removed) {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.test {
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class TestAttribute : Attribute {
|
||||
public uint Timeout = 10;
|
||||
public uint Timeout = 10; //timeout in seconds
|
||||
}
|
||||
|
||||
// Use constructor for setup
|
||||
|
||||
@@ -2,10 +2,121 @@
|
||||
using bts.collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.utils {
|
||||
public static class UpdateExtensions {
|
||||
public static IObservable<TableUpdate<T, TKey>> RootDependent<T, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, T> with_root)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
RootDependent(source, key, parent_key, with_root, v => v, EqualityComparer<T>.Default, default).SelectKeyValue(kv => kv.Value, key);
|
||||
|
||||
// TODO: unit tests
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> RootDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
Func<T, TKey> parent_key,
|
||||
Func<T, T, TResult> get_result,
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
IEqualityComparer<TResult> result_comparer,
|
||||
TKey no_parent_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
|
||||
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
|
||||
var state = DictionaryHelper.empty<TKey, T>();
|
||||
var output = TableUpdate<KeyValuePair<TKey, TResult>, TKey>.mk_empty(kv => kv.Key);
|
||||
var parent_to_children = new SetMap<TKey, T>();
|
||||
return source.Subscribe(update);
|
||||
//
|
||||
void update(TableUpdate<T, TKey> input) {
|
||||
state = input.state;
|
||||
var builder = output.to_builder();
|
||||
// execute all parent_to_children changes first, so descendent-propagation will be efficient (otherwise propagation might be needlessly performed on values that are yet to be removed, or performed with the wrong root, if the real root is yet to be added).
|
||||
foreach (var v in input.removed.Concat(input.changes.Select(ch => ch.Old))) {
|
||||
var k = input.key_extractor(v);
|
||||
parent_to_children.remove(parent_key(v), v);
|
||||
}
|
||||
foreach (var v in input.added.Concat(input.changed)) {
|
||||
var pk = parent_key(v);
|
||||
if (!pk.Equals(no_parent_key))
|
||||
parent_to_children.add(pk, v);
|
||||
}
|
||||
foreach (var v in input.removed) {
|
||||
var k = input.key_extractor(v);
|
||||
builder.remove(k);
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
var ck = key(child);
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(ck, get_result_no_parent(child)));
|
||||
foreach (var d in descendents(ck))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, child)));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var v in input.changed) {
|
||||
var k = input.key_extractor(v);
|
||||
var root = get_root(v);
|
||||
TResult result;
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, result));
|
||||
}
|
||||
foreach (var v in input.added) {
|
||||
var k = input.key_extractor(v);
|
||||
var pk = parent_key(v);
|
||||
TResult result;
|
||||
if (pk.Equals(no_parent_key)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
|
||||
} else {
|
||||
parent_to_children.add(pk, v);
|
||||
var root = get_root(v);
|
||||
if (key(root).Equals(k)) {
|
||||
result = get_result_no_parent(v);
|
||||
foreach (var d in descendents(k))
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(key(d), get_result(d, v)));
|
||||
} else {
|
||||
result = get_result(v, root);
|
||||
}
|
||||
}
|
||||
builder.add_or_update(new KeyValuePair<TKey, TResult>(k, result));
|
||||
}
|
||||
var new_output = builder.to_table_checked(value_comparer);
|
||||
if (new_output.has_changes())
|
||||
observer.OnNext(output = new_output);
|
||||
}
|
||||
T get_root(T value) {
|
||||
var pk = parent_key(value);
|
||||
while (!pk.Equals(no_parent_key) && state.TryGetValue(pk, out var parent)) {
|
||||
value = parent;
|
||||
pk = parent_key(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
IEnumerable<T> descendents(TKey k) {
|
||||
if (parent_to_children.try_lookup(k, out ISet<T> children)) {
|
||||
foreach (var child in children) {
|
||||
yield return child;
|
||||
foreach (var d in descendents(key(child)))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> MergeTable<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, IObservable<TableUpdate<T, TKey>> other) where TKey : IEquatable<TKey> =>
|
||||
source.AggregateLatest(other, _Merge<T, TKey>.source_change_del, _Merge<T, TKey>.other_change_del, () => TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty, TableUpdate<T, TKey>.empty);
|
||||
|
||||
public static IObservable<TableUpdate<KeyValuePair<TKey, TResult>, TKey>> ParentDependent<T, TKey, TResult>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
Func<T, TKey> key,
|
||||
@@ -24,7 +135,6 @@ namespace bts.utils {
|
||||
Func<T, TResult> get_result_no_parent,
|
||||
IEqualityComparer<TResult> result_comparer,
|
||||
TKey no_parent_key)
|
||||
where TResult : IEquatable<TResult>
|
||||
where TKey : IEquatable<TKey> =>
|
||||
Observable.Create<TableUpdate<KeyValuePair<TKey, TResult>, TKey>>(observer => {
|
||||
var value_comparer = EqualityComparer.Create<KeyValuePair<TKey, TResult>, TResult>(kv => kv.Value, result_comparer);
|
||||
@@ -65,12 +175,23 @@ namespace bts.utils {
|
||||
}
|
||||
var result = builder.to_table_checked(value_comparer);
|
||||
if (result.has_changes())
|
||||
observer.OnNext(output = builder.to_table());
|
||||
observer.OnNext(output = result);
|
||||
}
|
||||
TResult lookup_result(T child) {
|
||||
var pk = parent_key(child);
|
||||
return !pk.Equals(no_parent_key) && state.TryGetValue(pk, out T parent) ? get_result(child, parent) : get_result_no_parent(child);
|
||||
}
|
||||
});
|
||||
|
||||
static class _Merge<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public static readonly Func<TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>, TableUpdate<T, TKey>> source_change_del = _source_changed_impl;
|
||||
public static readonly Func<TableUpdate<T, TKey>, TableUpdate<T, TKey>, Change<TableUpdate<T, TKey>>, TableUpdate<T, TKey>> other_change_del = _other_changed_impl;
|
||||
|
||||
static TableUpdate<T, TKey> _source_changed_impl(TableUpdate<T, TKey> target, Change<TableUpdate<T, TKey>> source_change, TableUpdate<T, TKey> other_table) =>
|
||||
TableUpdate<T, TKey>.update(target, source_change.New.added, source_change.New.changes, source_change.New.removed, source_change.New.key_extractor);
|
||||
|
||||
static TableUpdate<T, TKey> _other_changed_impl(TableUpdate<T, TKey> target, TableUpdate<T, TKey> source_table, Change<TableUpdate<T, TKey>> other_change) =>
|
||||
TableUpdate<T, TKey>.update(target, other_change.New.added, other_change.New.changes, other_change.New.removed, other_change.New.key_extractor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
<None Include="$(OutputPath)\net471\spark_excel_example_2018.xlsm" pack="true" PackagePath=".\excel\spark_excel_example_2018.xlsm" />
|
||||
<None Include="$(OutputPath)\net471\spark_excel-addin64-packed.xll" pack="true" PackagePath=".\excel\spark_excel-addin64-packed.xll" />
|
||||
<None Include="$(OutputPath)\net471\spark_excel-addin64.dna" pack="true" PackagePath=".\excel\spark_excel-addin64.dna" />
|
||||
<None Include="../../../doc/user/content/bts_spark_sdk/Client/**" pack="true" PackagePath=".\doc\" />
|
||||
<None Include="doc/**" pack="true" PackagePath=".\doc\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences" Condition= " '$(SparkRoot)' != '' ">
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts.api;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.api.test;
|
||||
using bts.test;
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using mbts.tests;
|
||||
using bts.co2;
|
||||
using bts.api.orderman.algo;
|
||||
|
||||
namespace bts.tests {
|
||||
[TestGroup(TestType = TestType.System)]
|
||||
public class AlgoDefinitionTests : IDisposable {
|
||||
List<IDisposable> _resources;
|
||||
ServiceContext _context;
|
||||
//OrderService _order_service;
|
||||
AdminService _admin_service;
|
||||
AlgoDefinitionService _algo_definition_service;
|
||||
IEnumerable<AlgoDefinitionRecordImmutable> _non_deleted_defs;
|
||||
Random _rng = new Random();
|
||||
|
||||
public AlgoDefinitionTests() {
|
||||
_context = new ServiceContext();
|
||||
//_order_service = _context.locate<OrderService>();
|
||||
_admin_service = _context.locate<AdminService>();
|
||||
_algo_definition_service = _context.locate<AlgoDefinitionService>();
|
||||
|
||||
_resources = new List<IDisposable> {
|
||||
_context,
|
||||
_admin_service,
|
||||
_algo_definition_service,
|
||||
_algo_definition_service.subscribei_algo_definitions().Subscribe(defs => _non_deleted_defs = defs.all.Where( x => x.Metadata.IsActive))
|
||||
};
|
||||
|
||||
var auth = _context.login("bts", "");
|
||||
auth.Wait(3000);
|
||||
}
|
||||
|
||||
[Test(Timeout = 25)]
|
||||
public async Task test_crud_algo_definitions() {
|
||||
var created_algo_records = new List<AlgoDefinitionRecord>();
|
||||
var watch = new System.Diagnostics.Stopwatch();
|
||||
watch.Start();
|
||||
var user_data = await AdminUtilities.get_user_data(_admin_service, "bts");
|
||||
int num_created = 0;
|
||||
if (user_data.HasValue) {
|
||||
// create 100 algo defs
|
||||
while (num_created < 100) {
|
||||
var result = await _algo_definition_service.save_algo_definition_async(
|
||||
new AlgoDefinitionUpdateRequest {
|
||||
UserId = user_data.Value.Id,
|
||||
Key = 0,
|
||||
Algo = generate_random_algo_definition()
|
||||
}
|
||||
);
|
||||
Assert.IsTrue(result.value.Success, "There was a problem creating an algo defintion");
|
||||
created_algo_records.Add(result.value.Record);
|
||||
num_created++;
|
||||
}
|
||||
|
||||
// update the 100 we just created algo defs
|
||||
foreach(var r in created_algo_records) {
|
||||
var def = r.Algo;
|
||||
def.Name += " modified";
|
||||
await _algo_definition_service.save_algo_definition_async(
|
||||
new AlgoDefinitionUpdateRequest {
|
||||
UserId = user_data.Value.Id,
|
||||
Key = r.Key,
|
||||
Algo = def
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete all non-deleted algo defs
|
||||
Assert.IsTrue(_non_deleted_defs.Count() > 0, "There are no algo definitions to delete.");
|
||||
foreach (var i in _non_deleted_defs.Select(x => x.Key).ToList()) {
|
||||
var result = await _algo_definition_service.remove_algo_definition_async(new AlgoDefinitionRemoveRequest { Key = i });
|
||||
Assert.IsTrue(result.value.Success, "There was a problem removing an algo definition");
|
||||
}
|
||||
Assert.IsTrue(_non_deleted_defs.Count() == 0, "There was a problem removing all algo definitions");
|
||||
|
||||
watch.Stop();
|
||||
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
|
||||
}
|
||||
}
|
||||
|
||||
// not quite random yet
|
||||
private AlgoDefinition generate_random_algo_definition() {
|
||||
return new AlgoDefinition {
|
||||
Definition = new CustomOrderDefinition {
|
||||
Iceberg = new IcebergOrderDefinition {
|
||||
ShowQuantity = _rng.NextDouble() * 100,
|
||||
Child = new CustomOrderDefinition {
|
||||
Limit = new LimitOrderDefinition { }
|
||||
}
|
||||
},
|
||||
},
|
||||
Name = $"Test Algo Def: {generate_name(_rng, _rng.Next(4, 15))}"
|
||||
};
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/14687658/random-name-generator-in-c-sharp
|
||||
public static string generate_name(Random r, int len) {
|
||||
string[] consonants = { "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "l", "n", "p", "q", "r", "s", "sh", "zh", "t", "v", "w", "x" };
|
||||
string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" };
|
||||
string name = "";
|
||||
name += consonants[r.Next(consonants.Length)].ToUpper();
|
||||
name += vowels[r.Next(vowels.Length)];
|
||||
int b = 2; //b tells how many times a new letter has been added. It's 2 right now because the first two letters are already in the name.
|
||||
while (b < len) {
|
||||
name += consonants[r.Next(consonants.Length)];
|
||||
b++;
|
||||
name += vowels[r.Next(vowels.Length)];
|
||||
b++;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(_resources);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using bts.api;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.test;
|
||||
using bts.api.orderman;
|
||||
using mbts;
|
||||
using bts.co2;
|
||||
using bts.tests.utils;
|
||||
|
||||
namespace bts.tests {
|
||||
|
||||
[TestGroup(TestType = TestType.System)]
|
||||
class IcebergAlgoTests : TestBase {
|
||||
public IcebergAlgoTests() { }
|
||||
|
||||
[Test(Timeout = 25)]
|
||||
public async Task test_ib_create_and_takeout_market() {
|
||||
// find a security with no market
|
||||
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
|
||||
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
|
||||
|
||||
// create a market in the instrument
|
||||
List<Result<Response>> orders_response_list = new List<Result<Response>>();
|
||||
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
|
||||
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 50));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 55.05, 50));
|
||||
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
|
||||
foreach (var i in orders_response_list) {
|
||||
var iret = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
|
||||
test_orders.AddRange(iret);
|
||||
}
|
||||
|
||||
// create an IB that will cross this market
|
||||
var ib_order_def = OrderDefinitionUtilities.create_iceberg_order_def(s.Value.Id, Side.Buy, _test_account, _test_user, 3);
|
||||
var ib_order_result = await OrderUtilities.send_order(_order_service, ib_order_def, 55.05, 30);
|
||||
var ib_filtered_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 1,
|
||||
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
var ib_child_orders = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 10,
|
||||
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
|
||||
Assert.IsTrue(ib_filtered_order.Count() == 1, "This IB order should have been 'Completed'");
|
||||
Assert.IsTrue(ib_child_orders.Count() == 10, "There should have been 10 completed child orders");
|
||||
|
||||
var filled_childen = (await OrderUtilities.wait_for_filtered_fills(
|
||||
_order_service,
|
||||
rows => rows.Where(x => ib_child_orders.Select(o => o.Key).Contains(x.OrderKey)).Count() == 10,
|
||||
rows => rows.Where(x => ib_child_orders.Select(o => o.Key).Contains(x.OrderKey)))).result ?? Enumerable.Empty<FillImmutable>();
|
||||
|
||||
Assert.IsTrue(filled_childen.Count() == 10, "There should have been 10 fills");
|
||||
Assert.IsTrue(filled_childen.All(x =>
|
||||
TestUtils.test_price_equality(x.LastPrice, 55.05, s.Value.ContractSpec.ticksize(x.LastPrice)) && x.LastShares == 3), "All child fills should have been filled at price 55.50 with quantity 3");
|
||||
|
||||
// cleanup
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
|
||||
}
|
||||
|
||||
[Test(Timeout = 25)]
|
||||
public async Task test_ib_avg_fill() {
|
||||
// find a security with no market
|
||||
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
|
||||
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
|
||||
|
||||
// create a market in the instrument
|
||||
List<Result<Response>> orders_response_list = new List<Result<Response>>();
|
||||
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
|
||||
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 56.02, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 56.01, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 56.00, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 55.50, 5));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, 55.01, 20));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.99, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.98, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.97, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.96, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.95, 1));
|
||||
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
|
||||
foreach (var i in orders_response_list) {
|
||||
var iret = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
|
||||
test_orders.AddRange(iret);
|
||||
}
|
||||
|
||||
// create an IB that will cross this market
|
||||
var ib_order_def = OrderDefinitionUtilities.create_iceberg_order_def(s.Value.Id, Side.Buy, _test_account, _test_user, 1);
|
||||
var ib_order_result = await OrderUtilities.send_order(_order_service, ib_order_def, 56.02, 26);
|
||||
var ib_filtered_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 1,
|
||||
rows => rows.Where(x => x.Key == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
var ib_child_orders = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey && x.HasState && x.State.Status == OrderStatus.Completed).Count() == 10,
|
||||
rows => rows.Where(x => x.ParentKey == ib_order_result.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
|
||||
Assert.IsTrue(ib_filtered_order.Count() == 1, "This IB order should have been 'Completed'");
|
||||
Assert.IsTrue(ib_filtered_order.All(x =>
|
||||
TestUtils.test_price_equality(x.State.AvgPrice, 55.1423, s.Value.ContractSpec.ticksize(x.State.AvgPrice))), "IB avg price should be 55.1423");
|
||||
|
||||
// cleanup
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.test;
|
||||
using bts.tests.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.tests {
|
||||
|
||||
/// <summary>
|
||||
/// For buy orders:
|
||||
/// triggers when: curr_bid.price > initial_bid && current_bid.qty >= join_threshold.
|
||||
/// i.e. When the join is first entered, it looks at the market to capture the initial bid and
|
||||
/// then it waits until the current bid is above that level AND that the qty at that level is greater
|
||||
/// that some threshold.
|
||||
/// </summary>
|
||||
|
||||
[TestGroup(TestType = TestType.System)]
|
||||
public class JoinAlgoTests : TestBase {
|
||||
public JoinAlgoTests() { }
|
||||
|
||||
[Test(Timeout = 25)]
|
||||
public async Task test_join_price() {
|
||||
double initial_price = 54.99;
|
||||
double ask_price = 55.01;
|
||||
|
||||
|
||||
// find a security with no market
|
||||
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
|
||||
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
|
||||
|
||||
// create a market in the instrument
|
||||
List<Result<Response>> orders_response_list = new List<Result<Response>>();
|
||||
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
|
||||
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 10));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price, 1));
|
||||
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
|
||||
foreach (var i in orders_response_list) {
|
||||
var iret = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
|
||||
test_orders.AddRange(iret);
|
||||
}
|
||||
|
||||
// create a join order
|
||||
var join_order_def = OrderDefinitionUtilities.create_join_order_definition(s.Value.Id, Side.Buy, _test_account, _test_user, new spark.QuantityMeasure() { Absolute = 1 });
|
||||
var join_order_response = await OrderUtilities.send_order(_order_service, join_order_def, initial_price, 1);
|
||||
var join_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_order.Count() == 1, "The join order should have 'working'");
|
||||
test_orders.Add(join_order.First());
|
||||
|
||||
// verify the join order's child is working at the specefied price
|
||||
// note: the price doesn't actually matter in the join order
|
||||
var join_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
|
||||
o.HasState && (o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should have been 'working' at {initial_price}");
|
||||
|
||||
// now we move the best bid to become the best offer
|
||||
var original_bid_order = test_orders.Where(o => o.Def.Side == Side.Buy).First();
|
||||
var bid_order_response = await OrderUtilities.modify_order(_order_service, original_bid_order, ask_price);
|
||||
var modified_bid_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey && o.HasState &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price))).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(modified_bid_order.Count() == 1, "The working bid should have become the best offer");
|
||||
|
||||
// and we finally verify that the child order has joined the market at the appropriate price
|
||||
join_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
|
||||
o.HasState && (o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should now have been 'working' at {ask_price}");
|
||||
test_orders.Add(join_child_order.First());
|
||||
|
||||
// cleanup
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
|
||||
|
||||
}
|
||||
|
||||
[Test(Timeout = 25)]
|
||||
public async Task test_join_qty_threshold() {
|
||||
double initial_price = 54.99;
|
||||
double ask_price = 55.01;
|
||||
uint ask_size = 1;
|
||||
uint qty_threshold = 15;
|
||||
|
||||
|
||||
// find a security with no market
|
||||
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
|
||||
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
|
||||
|
||||
// create a market in the instrument
|
||||
List<Result<Response>> orders_response_list = new List<Result<Response>>();
|
||||
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
|
||||
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 55.00, 10));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price, ask_size));
|
||||
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
|
||||
foreach (var i in orders_response_list) {
|
||||
var iret = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
|
||||
test_orders.AddRange(iret);
|
||||
}
|
||||
|
||||
// create a join order
|
||||
var join_order_def = OrderDefinitionUtilities.create_join_order_definition(s.Value.Id, Side.Buy, _test_account, _test_user, new spark.QuantityMeasure() { Absolute = qty_threshold });
|
||||
var join_order_response = await OrderUtilities.send_order(_order_service, join_order_def, initial_price, 1);
|
||||
var join_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == join_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_order.Count() == 1, "The join order should have 'working'");
|
||||
test_orders.Add(join_order.First());
|
||||
|
||||
// verify the join order's child is working at the specefied price
|
||||
// note: the price doesn't actually matter in the join order
|
||||
var join_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
|
||||
o.HasState && (o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should have been 'working' at {initial_price}");
|
||||
|
||||
// now we move the best bid to become the best offer
|
||||
var top_bid_order = test_orders.Where(o => o.Def.Side == Side.Buy).First();
|
||||
var bid_order_response = await OrderUtilities.modify_order(_order_service, top_bid_order, ask_price);
|
||||
var modified_bid_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey && o.HasState &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price))).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(modified_bid_order.Count() == 1, "The working bid should have become the best offer");
|
||||
|
||||
// verify the join order's child is *still* working at the initial price
|
||||
// b/c the quantity threshold has not yet been met
|
||||
join_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
|
||||
o.HasState && (o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should still have been 'working' at {initial_price}");
|
||||
|
||||
// now we modify our top bid's volume to meet the join's threshold
|
||||
// note that we must add the intial ask_size to the qty_threshold b/c we have partially filled our bid order
|
||||
// when we moved the bid up.
|
||||
var target_qty = qty_threshold + ask_size;
|
||||
bid_order_response = await OrderUtilities.modify_order(_order_service, modified_bid_order.First(), target_qty);
|
||||
modified_bid_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey && o.HasState && o.State.Quantity == target_qty).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == bid_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(modified_bid_order.Count() == 1, $"The working bid should have its quantity modified to {target_qty}");
|
||||
|
||||
// now that the qty threshold has been met, verify that the child order has joined the market at the appropriate price
|
||||
join_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key &&
|
||||
o.HasState && (o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == join_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(join_child_order.Count() == 1, $"The join child order should now have been 'working' at {ask_price}");
|
||||
test_orders.Add(join_child_order.First());
|
||||
|
||||
// cleanup
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.test;
|
||||
using bts.tests.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.tests {
|
||||
|
||||
[TestGroup(TestType = TestType.System)]
|
||||
public class LeanAlgoTests : TestBase {
|
||||
|
||||
public LeanAlgoTests() { }
|
||||
|
||||
[Test(Timeout = 25)]
|
||||
public async Task test_lean_price() {
|
||||
double initial_price = 54.98;
|
||||
double ask_price_1 = 55.00;
|
||||
double ask_price_2 = 55.01;
|
||||
uint ask_size = 10;
|
||||
uint lean_order_size = 30;
|
||||
uint cross_threshold = ask_size - 1;
|
||||
|
||||
// find a security with no market
|
||||
var s = await MarketDataUtilities.get_security(_md_relay, "CL", SecurityType.Future, update => update.Direct.Asks.Count == 0 && update.Direct.Bids.Count == 0);
|
||||
Assert.IsTrue(s.HasValue, "Could not find a security with no maket");
|
||||
|
||||
// create a market in the instrument
|
||||
List<Result<Response>> orders_response_list = new List<Result<Response>>();
|
||||
var bid_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Buy, _test_account, _test_user);
|
||||
var ask_order_def = OrderDefinitionUtilities.create_limit_order_def(s.Value.Id, Side.Sell, _test_account, _test_user);
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, bid_order_def, 54.99, 1));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_1, ask_size));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_2, ask_size));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_2 + 0.01, ask_size));
|
||||
orders_response_list.Add(await OrderUtilities.send_order(_order_service, ask_order_def, ask_price_2 + 0.02, ask_size));
|
||||
List<OrderRecordImmutable> test_orders = new List<OrderRecordImmutable>();
|
||||
foreach (var i in orders_response_list) {
|
||||
var iret = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == i.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(iret.Count() == 1, "This order should have been 'working'");
|
||||
test_orders.AddRange(iret);
|
||||
}
|
||||
|
||||
// create a lean order, 2 tick chase (default)
|
||||
var lean_order_def = OrderDefinitionUtilities.create_lean_order_definition(s.Value.Id, Side.Buy, _test_account, _test_user, new spark.QuantityMeasure() { Absolute = cross_threshold });
|
||||
var lean_order_response = await OrderUtilities.send_order(_order_service, lean_order_def, initial_price, lean_order_size);
|
||||
var lean_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == lean_order_response.value.OrderKey && o.HasState && o.State.Status == OrderStatus.Working).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == lean_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(lean_order.Count() == 1, "The lean order should have 'working'");
|
||||
test_orders.Add(lean_order.First());
|
||||
|
||||
// verify the lean order's child is working at the specified price
|
||||
// note: the price doesn't actually matter in the lean order
|
||||
var lean_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == lean_order.First().Key &&
|
||||
o.HasState && (o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, initial_price, s.Value.ContractSpec.ticksize(o.State.Price)))).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == lean_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(lean_child_order.Count() == 1, $"The lean child order should have been 'working' at {initial_price}");
|
||||
|
||||
// now we modify the opposite inside market qty to breach the lean threshold
|
||||
var target_ask_order = test_orders.Where(o => o.Def.Side == Side.Sell &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price_1, s.Value.ContractSpec.ticksize(o.State.Price))).First();
|
||||
var ask_order_response = await OrderUtilities.modify_order(_order_service, target_ask_order, cross_threshold);
|
||||
var modified_ask_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey && o.HasState &&
|
||||
o.State.Quantity == cross_threshold).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(modified_ask_order.Count() == 1, "The opposite inside market should have been adjusted to the Lean's cross threshold");
|
||||
|
||||
// verify that the child order has leaned into the market at the appropriate price and been parially filled appropriately
|
||||
// this is the initial trigger payup
|
||||
lean_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == lean_order.First().Key &&
|
||||
o.HasState && (
|
||||
o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price_1, s.Value.ContractSpec.ticksize(o.State.Price)) &&
|
||||
o.State.FilledQuantity == 9)
|
||||
).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == lean_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(lean_child_order.Count() == 1, $"Initial trigger: The lean child order should now have been 'working' at {ask_price_1}");
|
||||
test_orders.Add(lean_child_order.First());
|
||||
|
||||
// now we modify the opposite inside market qty again to start checking that the case is working
|
||||
target_ask_order = test_orders.Where(o => o.Def.Side == Side.Sell &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price_2, s.Value.ContractSpec.ticksize(o.State.Price))).First();
|
||||
ask_order_response = await OrderUtilities.modify_order(_order_service, target_ask_order, cross_threshold);
|
||||
modified_ask_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey && o.HasState &&
|
||||
o.State.Quantity == cross_threshold).Count() == 1,
|
||||
rows => rows.Where(o => o.Key == ask_order_response.value.OrderKey))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(modified_ask_order.Count() == 1, "The opposite inside market should have been adjusted to the Lean's cross threshold");
|
||||
|
||||
// verify that the child order has chased and that it's filled quantity has increased
|
||||
lean_child_order = (await OrderUtilities.wait_for_filtered_orders(
|
||||
_order_service,
|
||||
rows => rows.Where(o => o.ParentKey == lean_order.First().Key &&
|
||||
o.HasState && (
|
||||
o.State.Status == OrderStatus.Working &&
|
||||
TestUtils.test_price_equality(o.State.Price, ask_price_2, s.Value.ContractSpec.ticksize(o.State.Price)) &&
|
||||
o.State.FilledQuantity == 9)
|
||||
).Count() == 1,
|
||||
rows => rows.Where(o => o.ParentKey == lean_order.First().Key))).result ?? Enumerable.Empty<OrderRecordImmutable>();
|
||||
Assert.IsTrue(lean_child_order.Count() == 1, $"First Chase: The lean child order should now have been 'working' at {ask_price_1}");
|
||||
test_orders.Add(lean_child_order.First());
|
||||
|
||||
// TODO:
|
||||
// verify the parent order also reflects the increase filled quantity from the child order
|
||||
|
||||
// TODO:
|
||||
// instigate another chase
|
||||
|
||||
// TODO:
|
||||
// verify parent order again reflects
|
||||
|
||||
// TODO:
|
||||
// instigate another chanse
|
||||
|
||||
// TODO:
|
||||
// verify we are *not* chasing any longer, verify appropriate order statuses
|
||||
|
||||
|
||||
|
||||
// cleanup
|
||||
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, test_orders), "Unable to cancel test orders");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user