Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f790b8f59b | ||
|
|
fbed8e5428 | ||
|
|
524f3be25a | ||
|
|
9823b44083 | ||
|
|
30feda72bb | ||
|
|
2d0e63546d | ||
|
|
cc02fa1c84 | ||
|
|
6139d53e49 | ||
|
|
7f3c2e75a3 | ||
|
|
0f61e24ae5 | ||
|
|
8da2ebee5c | ||
|
|
a1faf685eb | ||
|
|
9e2b01a723 | ||
|
|
d03c74f270 | ||
|
|
834a28916b | ||
|
|
43ccb4e3e7 | ||
|
|
f199cb4157 | ||
|
|
5a3bfd7f81 | ||
|
|
bf6461c126 | ||
|
|
8892505d11 | ||
|
|
5d0a29432a | ||
|
|
cd01c7ae5a | ||
|
|
bf19ec0fda | ||
|
|
1a0158817e | ||
|
|
53853591fe | ||
|
|
80d4899f2c | ||
|
|
75f15c50d8 | ||
|
|
f197f213f3 | ||
|
|
a7e1b71883 | ||
|
|
f9f22e1e36 | ||
|
|
cda50f562d | ||
|
|
4010298a46 | ||
|
|
48e701ba00 | ||
|
|
cb07abbf42 | ||
|
|
3c8e2c636f | ||
|
|
6a3b4ebe75 | ||
|
|
80256fbe12 | ||
|
|
a0a5d82139 | ||
|
|
ee6b00b9f2 | ||
|
|
9a494ce24f | ||
|
|
ae78ebac9a | ||
|
|
5c11eecbd1 | ||
|
|
78a47c33c7 | ||
|
|
9dcd80e3db | ||
|
|
f7563413bd | ||
|
|
29c418db42 | ||
|
|
f177efde70 | ||
|
|
3b83d105a0 | ||
|
|
34aafd79cd | ||
|
|
d3de164c53 | ||
|
|
1d1c0d6b85 | ||
|
|
2f127ecc18 | ||
|
|
3fc080d02c | ||
|
|
d86543261c | ||
|
|
1a7fcb2027 |
No files matched your search
+2
-6
@@ -4,7 +4,8 @@ cmake-build-debug/
|
||||
.idea/
|
||||
.dir-locals.el
|
||||
*.pyc
|
||||
*.vscode/*
|
||||
.vscode/*
|
||||
!.vscode/*.json
|
||||
.vs
|
||||
obj/
|
||||
*.csproj.user
|
||||
@@ -36,8 +37,3 @@ trace_data/
|
||||
*.rej
|
||||
/build
|
||||
.clangd/
|
||||
/.cache/
|
||||
.venv/**
|
||||
install/**
|
||||
.devcontainer/**
|
||||
/src/Spark.Admin.WebApp/src/bin/
|
||||
Vendored
+107
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceRoot}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceRoot}/externals/linux/include"
|
||||
],
|
||||
"defines": [],
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"browse": {
|
||||
"path": [
|
||||
"/usr/include",
|
||||
"/usr/local/include",
|
||||
"${workspaceRoot}",
|
||||
"${workspaceFolder}/src",
|
||||
"${workspaceRoot}/externals/linux/include"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": ""
|
||||
},
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"compileCommands": "${workspaceFolder}/build/ninja_debug/compile_commands.json"
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"includePath": [
|
||||
"${workspaceRoot}/src",
|
||||
"${workspaceRoot}/build/src",
|
||||
"${workspaceRoot}/build/ninja_debug/src",
|
||||
"${workspaceRoot}/externals/windows/include",
|
||||
"${workspaceRoot}/externals/linux/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/c++/6",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/x86_64-linux-gnu/c++/6",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/c++/6/backward",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/lib/gcc/x86_64-linux-gnu/6/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/local/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/x86_64-linux-gnu",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include"
|
||||
],
|
||||
"defines": [
|
||||
"__linux__",
|
||||
"__x86_64__"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${workspaceRoot}/src",
|
||||
"${workspaceRoot}/build/src",
|
||||
"${workspaceRoot}/build/ninja_debug/src",
|
||||
"${workspaceRoot}/externals/windows/include",
|
||||
"${workspaceRoot}/externals/linux/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/c++/6",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/x86_64-linux-gnu/c++/6",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/c++/6/backward",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/lib/gcc/x86_64-linux-gnu/6/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/local/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/x86_64-linux-gnu",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/*"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": ""
|
||||
},
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17"
|
||||
},
|
||||
{
|
||||
"name": "WindowsQuick",
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"includePath": [
|
||||
"${workspaceRoot}/src",
|
||||
"${workspaceRoot}/build/src",
|
||||
"${workspaceRoot}/build/ninja_debug/src",
|
||||
"${workspaceRoot}/externals/windows/include",
|
||||
"${workspaceRoot}/externals/linux/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/c++/6",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/x86_64-linux-gnu/c++/6",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/c++/6/backward",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/lib/gcc/x86_64-linux-gnu/6/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/local/include",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include/x86_64-linux-gnu",
|
||||
"${workspaceRoot}/externals/windows/vscode/usr/include"
|
||||
],
|
||||
"defines": [
|
||||
"__linux__",
|
||||
"__x86_64__"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${workspaceRoot}/src"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": "${workspaceRoot}/.vscode/browse.vc.db"
|
||||
},
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
Vendored
+395
@@ -0,0 +1,395 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Spark Local",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"run",
|
||||
// "test",
|
||||
// "-a",
|
||||
// "[ilink3]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceRoot}/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SparkVM Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Skip STL",
|
||||
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"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"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "(gdb) Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/ninja_debug/server/sparkd/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceRoot}/build/ninja_debug/src/server/sparkd",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Client Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "SDK Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/spark_sdk.exe",
|
||||
"args": [
|
||||
"md",
|
||||
"GLBX",
|
||||
"Future",
|
||||
"CL",
|
||||
"CLZ9",
|
||||
"--user=bts",
|
||||
"--pass="
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Excel Launch",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "C:/Program Files/Microsoft Office/Office14/EXCEL.EXE",
|
||||
"args": [
|
||||
"${workspaceRoot}/build/Debug/bin/net471/spark_excel-addin64-packed.xll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"ApiUpdateTests"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"name": "Spreader Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Admin Service Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[admin_service]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Limiter Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[limiter]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/src/server/sparkd/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "enable pretty-printer",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MD Tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/ninja_debug/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[algo]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/ninja_debug/server/",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"preLaunchTask": "",
|
||||
"logging": {
|
||||
"engineLogging": false
|
||||
},
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Skip STL",
|
||||
"text": "skip -gfi /usr/include/c++/7/bits/*.h",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SparkVM attach",
|
||||
"type": "cppdbg",
|
||||
"request": "attach",
|
||||
"program": "/home/bts/spark/build/ninja_debug/src/server/sparkd/sparkd",
|
||||
"processId": "${command:pickRemoteProcess}",
|
||||
"MIMode": "gdb",
|
||||
"pipeTransport": {
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"pipeProgram": "c:\\program files\\git\\usr\\bin\\ssh.exe",
|
||||
"pipeArgs": [
|
||||
"bts@sparkvm"
|
||||
],
|
||||
"debuggerPath": "/usr/bin/gdb"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/home/bts/spark/": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Python",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"stopOnEntry": false,
|
||||
"pythonPath": "${config:python.pythonPath}",
|
||||
"program": "${file}",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"envFile": "${workspaceRoot}/.env",
|
||||
"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
|
||||
},
|
||||
]
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"utility": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"list": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"vector": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xstring": "cpp",
|
||||
"system_error": "cpp",
|
||||
"array": "cpp",
|
||||
"string_view": "cpp"
|
||||
"xutility": "cpp",
|
||||
"*.ipp": "cpp"
|
||||
}
|
||||
}
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "client_build",
|
||||
"type": "shell",
|
||||
"command": ".\\tools\\buildspark.cmd",
|
||||
"problemMatcher":"$msCompile",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "server_build",
|
||||
"type": "shell",
|
||||
"command": ".\\tools\\buildsparkd.cmd",
|
||||
"problemMatcher": {
|
||||
"base": "$gcc",
|
||||
"owner": "cpp",
|
||||
"fileLocation": [
|
||||
"relative",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"pattern": {
|
||||
"regexp": "(.?)(home)(.?)(bts)(.?)(spark)(.?)(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)",
|
||||
"file": 8,
|
||||
"line": 9,
|
||||
"column": 10,
|
||||
"severity": 11,
|
||||
"message": 12
|
||||
}
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "new"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "linux build (Debug)",
|
||||
"type": "shell",
|
||||
"command": "cd build/ninja_debug; time ninja",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+32
-9
@@ -1,6 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(spark)
|
||||
|
||||
set (USE_ONIX "TRUE")
|
||||
|
||||
if (WIN32)
|
||||
set (platform "windows")
|
||||
else ()
|
||||
@@ -12,22 +14,46 @@ set (SPARK_PYTHON "${EXTERNALS_ROOT}/python")
|
||||
set (SPARK_BIN "${EXTERNALS_ROOT}/bin")
|
||||
set (SPARK_INCLUDE "${EXTERNALS_ROOT}/include")
|
||||
set (SPARK_LIB "${EXTERNALS_ROOT}/lib/${CMAKE_BUILD_TYPE}")
|
||||
set (BUILD_SDK OFF CACHE BOOL "Build the sdk")
|
||||
|
||||
include_directories (BEFORE SYSTEM ${SPARK_INCLUDE} ${EXTERNALS_ROOT}/include/zlib)
|
||||
link_directories (${SPARK_LIB})
|
||||
|
||||
if (WIN32)
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
/MP \
|
||||
/WX /wd4251 /wd4250 /wd4251 /wd4244 /wd4267 /wd4355 /wd4275 /wd4345 \
|
||||
/DCURL_STATICLIB \
|
||||
/D_WINSOCK_DEPRECATED_NO_WARNINGS \
|
||||
/D__TBB_NO_IMPLICIT_LINKAGE \
|
||||
/DBOOST_ALL_NO_LIB \
|
||||
/D_SCL_SECURE_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS /D_WIN32_WINNT=0x0600")
|
||||
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} /MT")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF /LTCG")
|
||||
|
||||
add_subdirectory(src/lib)
|
||||
else ()
|
||||
# so that sparkd looks for libspark.so in its directory
|
||||
# so that sparkd looks for libspark.so in its directory
|
||||
set (CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set (CMAKE_INSTALL_RPATH ".")
|
||||
|
||||
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} \
|
||||
-fPIC -Wall -pedantic -Wextra -Werror \
|
||||
-Wno-missing-braces -Wno-ignored-qualifiers \
|
||||
--std=c++17 -fdiagnostics-color")
|
||||
|
||||
if (${USE_ONIX})
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_ONIX")
|
||||
endif()
|
||||
|
||||
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -DNDEBUG -O3")
|
||||
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wno-undef")
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/lib)
|
||||
# add_subdirectory(src/web)
|
||||
add_subdirectory(src/web)
|
||||
add_subdirectory(doc/user)
|
||||
if(BUILD_SDK)
|
||||
add_subdirectory(src/sdk)
|
||||
endif()
|
||||
add_subdirectory(src/sdk)
|
||||
add_subdirectory(src/data_files)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/server
|
||||
@@ -43,7 +69,4 @@ else ()
|
||||
DEPENDS sparkd data_files_dir)
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libACE-5.7.5.so" DESTINATION bin)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0" DESTINATION bin)
|
||||
endif()
|
||||
@@ -1,179 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "spark",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"environment": {
|
||||
"SPARK_CMAKE_CXX_FLAGS": "/MP /WX /wd4250 /wd4251 /wd4244 /wd4267 /wd4355 /wd4275 /wd4345 /bigobj /EHsc /DCURL_STATICLIB /D_WINSOCK_DEPRECATED_NO_WARNINGS /D__TBB_NO_IMPLICIT_LINKAGE /DBOOST_ALL_NO_LIB /std:c++17 /D_SCL_SECURE_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS /D_WIN32_WINNT=0x0600",
|
||||
"DOTNET_TARGET_FRAMEWORK": "net471"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_FLAGS": "$env{SPARK_CMAKE_CXX_FLAGS}",
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
|
||||
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
|
||||
"CMAKE_CXX_STANDARD": "17",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
|
||||
"DOTNET_BINARY_DIR": "${sourceDir}/build/$<CONFIG>/bin/$env{DOTNET_TARGET_FRAMEWORK}",
|
||||
"EXTERNALS_ROOT": "${sourceDir}/externals/windows",
|
||||
"SPARK_BIN": "${sourceDir}/externals/windows/bin",
|
||||
"SPARK_INCLUDE": "${sourceDir}/externals/windows/include",
|
||||
"SPARK_LIB": "${sourceDir}/externals/windows/lib/$<CONFIG>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sparkd",
|
||||
"hidden": true,
|
||||
"description": "Sparkd common config",
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"generator": "Ninja",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_WITH_INSTALL_RPATH": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"CMAKE_CXX_STANDARD": "17",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-Wno-undef",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||
"CMAKE_INSTALL_RPATH": ".",
|
||||
"EXTERNALS_ROOT": "${sourceDir}/externals/linux",
|
||||
"SPARK_BIN": "${sourceDir}/externals/linux/bin",
|
||||
"SPARK_INCLUDE": "${sourceDir}/externals/linux/include"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sparkd-debug-clang",
|
||||
"displayName": "Debug (clang)",
|
||||
"description": "Debug build compiled w/ clang",
|
||||
"inherits": "sparkd",
|
||||
"binaryDir": "${sourceDir}/build/clang/debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "-g -fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers -Wno-deprecated-copy -Wno-unused-but-set-variable -Wno-c++17-extensions --std=c++17 -fdiagnostics-color -DDEBUG",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"SPARK_LIB": "${sourceDir}/externals/linux/lib/Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sparkd-release-clang",
|
||||
"displayName": "Release (clang)",
|
||||
"description": "Release build compiled w/ clang",
|
||||
"binaryDir": "${sourceDir}/build/clang/release",
|
||||
"inherits": "sparkd",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_CXX_FLAGS_RELEASE": "-fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers -Wno-deprecated-copy -Wno-c++17-extensions --std=c++17 -fdiagnostics-color -fno-omit-frame-pointer -DNDEBUG -O3",
|
||||
"SPARK_LIB": "${sourceDir}/externals/linux/lib/Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sparkd-debug-gcc",
|
||||
"displayName": "Debug (gcc)",
|
||||
"description": "Debug build compiled w/ gcc",
|
||||
"inherits": "sparkd",
|
||||
"binaryDir": "${sourceDir}/build/gcc/debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_C_COMPILER": "gcc",
|
||||
"CMAKE_CXX_COMPILER": "g++",
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "-g -fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers -Wno-deprecated-copy -Wno-catch-value --std=c++17 -fdiagnostics-color -DDEBUG",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": {
|
||||
"type": "BOOL",
|
||||
"value": true
|
||||
},
|
||||
"SPARK_LIB": "${sourceDir}/externals/linux/lib/Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sparkd-release-gcc",
|
||||
"displayName": "Release (gcc)",
|
||||
"description": "Release build compiled w/ gcc",
|
||||
"inherits": "sparkd",
|
||||
"binaryDir": "${sourceDir}/build/gcc/release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_C_COMPILER": "/usr/local/bin/gcc",
|
||||
"CMAKE_CXX_COMPILER": "/usr/local/bin/g++",
|
||||
"CMAKE_CXX_FLAGS_RELEASE": "-fPIC -Wall -pedantic -Wextra -Werror -Wno-missing-braces -Wno-ignored-qualifiers -Wno-deprecated-copy -Wno-catch-value --std=c++17 -fdiagnostics-color -fno-omit-frame-pointer -DNDEBUG -O3",
|
||||
"SPARK_LIB": "${sourceDir}/externals/linux/lib/Release"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "spark",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "spark-debug",
|
||||
"displayName": "Debug",
|
||||
"inherits": "spark",
|
||||
"configurePreset": "spark",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "spark-release",
|
||||
"displayName": "Release",
|
||||
"inherits": "spark",
|
||||
"configurePreset": "spark",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "sparkd",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "sparkd-debug-clang",
|
||||
"configurePreset": "sparkd-debug-clang",
|
||||
"displayName": "Debug (clang)",
|
||||
"inherits": "sparkd"
|
||||
},
|
||||
{
|
||||
"name": "sparkd-debug-gcc",
|
||||
"configurePreset": "sparkd-debug-gcc",
|
||||
"displayName": "Debug (gcc)",
|
||||
"inherits": "sparkd"
|
||||
},
|
||||
{
|
||||
"name": "sparkd-release-clang",
|
||||
"configurePreset": "sparkd-release-clang",
|
||||
"displayName": "Release (clang)",
|
||||
"inherits": "sparkd"
|
||||
},
|
||||
{
|
||||
"name": "sparkd-release-gcc",
|
||||
"configurePreset": "sparkd-release-gcc",
|
||||
"displayName": "Release (gcc)",
|
||||
"inherits": "sparkd"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
set(sources /home/ppinto/Dropbox/bts/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
|
||||
+1
-1
Submodule docker/linux updated: 26de8f7b3b...6f0dd9324b.
+1
-1
Submodule externals updated: e9120dabfe...2ea5e355a6.
@@ -1,17 +0,0 @@
|
||||
astroid==2.7.3
|
||||
autopep8==1.5.7
|
||||
cffi==1.14.6
|
||||
colorama==0.4.4
|
||||
isort==5.9.3
|
||||
lazy-object-proxy==1.6.0
|
||||
mccabe==0.6.1
|
||||
platformdirs==2.3.0
|
||||
protobuf==3.19.1
|
||||
pybars3==0.9.7
|
||||
pycodestyle==2.7.0
|
||||
pycparser==2.20
|
||||
pylint==2.10.2
|
||||
PyMeta3==0.5.1
|
||||
toml==0.10.2
|
||||
wrapt==1.12.1
|
||||
yapf==0.31.0
|
||||
+232
-168
@@ -1,168 +1,232 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2024
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sparkcmd", "src\client\sparkcmd\sparkcmd.csproj", "{387566C6-C6F6-412C-A30C-9855BBCB4816}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
MinSizeRel|Any CPU = MinSizeRel|Any CPU
|
||||
MinSizeRel|x64 = MinSizeRel|x64
|
||||
MinSizeRel|x86 = MinSizeRel|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
RelWithDebInfo|Any CPU = RelWithDebInfo|Any CPU
|
||||
RelWithDebInfo|x64 = RelWithDebInfo|x64
|
||||
RelWithDebInfo|x86 = RelWithDebInfo|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.Build.0 = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x86.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x86.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x86.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x86.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x86.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {422D1620-0166-4C26-9A81-CA8A2EB1AB58}
|
||||
EndGlobalSection
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2024
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{3335AAF0-489D-359D-8305-9B7A44E2C760}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sparkcmd", "src\client\sparkcmd\sparkcmd.csproj", "{387566C6-C6F6-412C-A30C-9855BBCB4816}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\spark_core\spark_core.csproj", "{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "build\ZERO_CHECK.vcxproj", "{2256015B-E7D5-3614-8F39-814CA68A4444}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760} = {3335AAF0-489D-359D-8305-9B7A44E2C760}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_excel", "src\client\spark_excel\spark_excel.csproj", "{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
MinSizeRel|Any CPU = MinSizeRel|Any CPU
|
||||
MinSizeRel|x64 = MinSizeRel|x64
|
||||
MinSizeRel|x86 = MinSizeRel|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
RelWithDebInfo|Any CPU = RelWithDebInfo|Any CPU
|
||||
RelWithDebInfo|x64 = RelWithDebInfo|x64
|
||||
RelWithDebInfo|x86 = RelWithDebInfo|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x64.Build.0 = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x64.Build.0 = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x64.ActiveCfg = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x64.Build.0 = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.Release|x86.ActiveCfg = Release|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{3335AAF0-489D-359D-8305-9B7A44E2C760}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x64.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Release|x86.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.MinSizeRel|x86.Build.0 = Debug|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x64.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.Release|x86.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{387566C6-C6F6-412C-A30C-9855BBCB4816}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x64.Build.0 = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x64.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.Release|x86.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{6E449EB6-20AC-4FA6-B8D9-10F4BB7E1FEE}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x64.Build.0 = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|Any CPU.ActiveCfg = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x64.ActiveCfg = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x64.Build.0 = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.Release|x86.ActiveCfg = Release|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|Any CPU.ActiveCfg = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{2256015B-E7D5-3614-8F39-814CA68A4444}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x64.Build.0 = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.Release|x86.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{6963C314-83C4-4667-B0D0-C2F7AE208C29}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x64.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.MinSizeRel|x86.Build.0 = Debug|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x86.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x64.Build.0 = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|Any CPU.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|Any CPU.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.MinSizeRel|x86.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x64.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.Release|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|Any CPU.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|Any CPU.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.ActiveCfg = Release|x64
|
||||
{BE9F27C3-B583-47DF-95FA-1F8BBC89DCCB}.RelWithDebInfo|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {422D1620-0166-4C26-9A81-CA8A2EB1AB58}
|
||||
EndGlobalSection
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace RiskUI {
|
||||
/// <summary>
|
||||
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
|
||||
///
|
||||
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
|
||||
/// Add this XmlNamespace attribute to the root element of the markup file where it is
|
||||
/// to be used:
|
||||
///
|
||||
/// xmlns:MyNamespace="clr-namespace:RiskUI"
|
||||
///
|
||||
///
|
||||
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
|
||||
/// Add this XmlNamespace attribute to the root element of the markup file where it is
|
||||
/// to be used:
|
||||
///
|
||||
/// xmlns:MyNamespace="clr-namespace:RiskUI;assembly=RiskUI"
|
||||
///
|
||||
/// You will also need to add a project reference from the project where the XAML file lives
|
||||
/// to this project and Rebuild to avoid compilation errors:
|
||||
///
|
||||
/// Right click on the target project in the Solution Explorer and
|
||||
/// "Add Reference"->"Projects"->[Select this project]
|
||||
///
|
||||
///
|
||||
/// Step 2)
|
||||
/// Go ahead and use your control in the XAML file.
|
||||
///
|
||||
/// <MyNamespace:CustomControl1/>
|
||||
///
|
||||
/// </summary>
|
||||
public class CustomControl1 : Control {
|
||||
static CustomControl1() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
<RootNamespace>bts</RootNamespace>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<Version>$(major).$(minor).$(patch)</Version>
|
||||
<Version Condition="'$(version)'=='..'">1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../spark_api/spark_api.csproj" />
|
||||
<ProjectReference Include="../Spark/Spark.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:RiskUI">
|
||||
<Style TargetType="{x:Type local:CustomControl1}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,24 +1,33 @@
|
||||
using bts;
|
||||
using bts.admin.risk;
|
||||
using bts.api;
|
||||
using bts.api.admin;
|
||||
using bts.api.core;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
public readonly UserTuple user;
|
||||
|
||||
readonly AdminService _service;
|
||||
readonly ErrorReporter _error_reporter;
|
||||
IDisposable _dispose;
|
||||
IReadOnlyDictionary<uint, string> _account_map = DictionaryHelper.empty<uint, string>();
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, UserTuple user) {
|
||||
AdminModel(AdminService service, UserTuple user, SecurityStore security_store, ErrorReporter error_reporter) {
|
||||
this.user = user;
|
||||
_service = service;
|
||||
_error_reporter = error_reporter;
|
||||
// accounts
|
||||
on_accounts = service.subscribei_admin_data()
|
||||
.Select(up => up.all.TryGetFirst(out var ad) && ad.Users.Where(u => u.Name == user.name).TryGetSingle(out var usr) && usr.HasUserData && usr.UserData.HasAccountPermissions ?
|
||||
@@ -31,6 +40,34 @@ namespace mbts.spark {
|
||||
new Dictionary<uint, string>(0))
|
||||
.Replay(1)
|
||||
.RefCount();
|
||||
// account risk limits
|
||||
account_security_risk_limits = service.subscribei_admin_data().Where(ad => ad.all.Any())
|
||||
.Select(upd => {
|
||||
AdminDataImmutable admin_data = upd.all.First();
|
||||
return admin_data.Accounts.SelectMany(acc =>
|
||||
(acc.HasProprietaryRiskLimits ? acc.ProprietaryRiskLimits.SecurityLimits : Enumerable.Empty<SecurityRiskLimitUnitImmutable>())
|
||||
.Select(api_limit => AccountSecurityRiskLimitsDataHelper.to_client_data(acc.Id, api_limit)))
|
||||
.ToDictionary(AccountSecurityRiskLimitsDataHelper.key_fn);
|
||||
})
|
||||
.Table(AccountSecurityRiskLimitsDataHelper.key_fn);
|
||||
// this isn't the most efficient join, but the SecurityStore join is more complex than a simple
|
||||
// left join operation, because missing securities are retrieved asynchronously, then merged back into the output sequence.
|
||||
// Until it is known that this inefficiency is problematic, best to leave the battle-tested SecurityStore code alone.
|
||||
account_security_risk_limits = security_store.join<
|
||||
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>,
|
||||
AccountSecurityRiskLimits,
|
||||
AccountSecurityKey,
|
||||
TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>>(account_security_risk_limits,
|
||||
cli_lim => cli_lim.security_key,
|
||||
table => (table.state.Values, table.added, table.changes, table.removed, AccountSecurityRiskLimitsDataHelper.key_fn),
|
||||
(all, added, changes, removed, key_fn) =>
|
||||
new TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>(
|
||||
ImmutableDictionary.CreateRange<AccountSecurityKey, AccountSecurityRiskLimits>(
|
||||
all.Select(tup => new KeyValuePair<AccountSecurityKey, AccountSecurityRiskLimits>(tup.val.key, risk_lim_with_sec(tup)))),
|
||||
added.Select(risk_lim_with_sec),
|
||||
changes.Select(ch => new Change<AccountSecurityRiskLimits>(risk_lim_with_sec(ch.Old), risk_lim_with_sec(ch.New))),
|
||||
removed.Select(risk_lim_with_sec),
|
||||
AccountSecurityRiskLimitsDataHelper.key_fn));
|
||||
// users
|
||||
on_users = Observable.Return(DictionaryHelper.wrap_value(user.id, user.name));
|
||||
all_user_tuples = user.permissions == UserTuple.Permissions.Admin ?
|
||||
@@ -45,12 +82,16 @@ namespace mbts.spark {
|
||||
.Cache() :
|
||||
Observable.Return(TableUpdate<UserTuple, uint>.mk_empty(UserTuple.key_fn).with_added(new[] { user }));
|
||||
_dispose = on_accounts.Subscribe(map => _account_map = map);
|
||||
// local methods
|
||||
AccountSecurityRiskLimits risk_lim_with_sec((SecurityImmutable? sec, AccountSecurityRiskLimits val) tup) =>
|
||||
tup.val.with(security: tup.sec);
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts { get; }
|
||||
public IObservable<TableUpdate<UserTuple, uint>> all_user_tuples { get; }
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToListSorted(a => a.name));
|
||||
public IObservable<TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>> account_security_risk_limits { get; }
|
||||
|
||||
public IObservable<Update<T, TKey>> permission_filter<T, TKey>(IObservable<Update<T, TKey>> positions, Func<T, uint> get_account_id) where TKey : IEquatable<TKey> =>
|
||||
user.permissions == UserTuple.Permissions.Admin ?
|
||||
@@ -68,7 +109,86 @@ namespace mbts.spark {
|
||||
public AccountTuple? get_default_account() =>
|
||||
_account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).TryFirst();
|
||||
|
||||
public async Task<Result<AdminServiceOperationResult>[]> set_risk_limits(IReadOnlyDictionary<uint, IEnumerable<(uint, RiskLimits)>> state) {
|
||||
var results = await Task.WhenAll(state.Select(impl));
|
||||
return results;
|
||||
async Task<Result<AdminServiceOperationResult>> impl(KeyValuePair<uint, IEnumerable<(uint, RiskLimits)>> kv) {
|
||||
var prlimits = new ProprietaryRiskLimits();
|
||||
foreach (var (sec, lim) in kv.Value) {
|
||||
var api_lim = AccountSecurityRiskLimitsDataHelper.from_client_data(sec, lim);
|
||||
prlimits.SecurityLimits.Add(api_lim);
|
||||
}
|
||||
return await _service.update_proprietary_risk_limits_async(new ProprietaryRiskLimitsAPIArgument {
|
||||
AccountId = kv.Key,
|
||||
ProprietaryRiskLimits = prlimits
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Task<(bool success, string error_msg)> add_or_update_risk_limits(uint account_key, IEnumerable<(uint security_key, RiskLimits limits)> new_limits) {
|
||||
return _modify_risk_limits("Updating Risk Limits", account_key, add_limits);
|
||||
//
|
||||
ProprietaryRiskLimits add_limits(ProprietaryRiskLimits prlimits) {
|
||||
foreach (var (security_key, limits) in new_limits) {
|
||||
var api_limits = AccountSecurityRiskLimitsDataHelper.from_client_data(security_key, limits);
|
||||
if (prlimits.SecurityLimits.TryGetFirstIndex(l => l.SecurityKey == security_key, out int index)) {
|
||||
prlimits.SecurityLimits[index] = api_limits;
|
||||
} else {
|
||||
prlimits.SecurityLimits.Add(api_limits);
|
||||
}
|
||||
}
|
||||
return prlimits;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<(bool success, string error_msg)> delete_risk_limits(uint account_key, IEnumerable<uint> security_keys) {
|
||||
return _modify_risk_limits("Deleting Risk Limits", account_key, remove_secs);
|
||||
//
|
||||
ProprietaryRiskLimits remove_secs(ProprietaryRiskLimits prlimits) {
|
||||
var sec_key_set = new HashSet<uint>(security_keys);
|
||||
for (int i = 0; i < prlimits.SecurityLimits.Count;) {
|
||||
if (sec_key_set.Contains(prlimits.SecurityLimits[i].SecurityKey)) {
|
||||
prlimits.SecurityLimits.RemoveAt(i);
|
||||
} else i++;
|
||||
}
|
||||
return prlimits;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
async Task<(bool success, string error_msg)> _modify_risk_limits(string op_desc, uint account_key, Func<ProprietaryRiskLimits, ProprietaryRiskLimits> modify) {
|
||||
var api_limits = new ProprietaryRiskLimits();
|
||||
var snapshot = await _get_current_admin_data_async();
|
||||
if (!snapshot.HasValue)
|
||||
return (false, "Timed out waiting for data from server.");
|
||||
var mutable_snap = snapshot.Value.CloneToMutable();
|
||||
if (!snapshot.Value.Accounts.TryGetFirst(acc => acc.Id == account_key, out var target_acci)) {
|
||||
return (false, $"Account id '{account_key}' not found.");
|
||||
}
|
||||
var prlimits = target_acci.HasProprietaryRiskLimits ? target_acci.ProprietaryRiskLimits.CloneToMutable() : new ProprietaryRiskLimits();
|
||||
prlimits = modify(prlimits);
|
||||
if (prlimits is null) {
|
||||
return (false, "Client-side modification function returned null.");
|
||||
}
|
||||
var req = new ProprietaryRiskLimitsAPIArgument {
|
||||
AccountId = account_key,
|
||||
ProprietaryRiskLimits = prlimits
|
||||
};
|
||||
var reply = await _error_reporter.safe_execute<AdminServiceOperationResult>(
|
||||
() => _service.update_proprietary_risk_limits_async(req),
|
||||
r => r.IsSuccess,
|
||||
r => r.ErrorText,
|
||||
op_desc);
|
||||
return (reply.success, reply.value.ErrorText);
|
||||
}
|
||||
|
||||
async Task<AdminDataImmutable?> _get_current_admin_data_async() =>
|
||||
await _service.subscribei_admin_data()
|
||||
.Where(upd => upd.all.Any())
|
||||
.Select(upd => (AdminDataImmutable?)upd.all.First())
|
||||
.FirstOrDefaultAsync(TimeSpan.FromSeconds(5));
|
||||
//async Task<AdminDataImmutable?>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using bts.api;
|
||||
using bts.api.admin;
|
||||
using bts.utils;
|
||||
using System;
|
||||
|
||||
namespace bts.admin.risk {
|
||||
readonly struct AccountSecurityKey : IEquatable<AccountSecurityKey> {
|
||||
public AccountSecurityKey(uint account, uint security) {
|
||||
this.account = account;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public uint account { get; }
|
||||
public uint security { get; }
|
||||
|
||||
public bool Equals(AccountSecurityKey other) =>
|
||||
security == other.security &&
|
||||
account == other.account;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AccountSecurityKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(security.GetHashCode(), account.GetHashCode());
|
||||
|
||||
public static bool operator ==(AccountSecurityKey a, AccountSecurityKey b) => a.Equals(b);
|
||||
public static bool operator !=(AccountSecurityKey a, AccountSecurityKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
readonly struct AccountSecurityRiskLimits : IEquatable<AccountSecurityRiskLimits> {
|
||||
readonly AccountTuple? _account;
|
||||
|
||||
public AccountSecurityRiskLimits(AccountSecurityKey key, RiskLimits limits, SecurityImmutable? security = null, AccountTuple? account = null) {
|
||||
this.key = key;
|
||||
this.limits = limits;
|
||||
this.security = security;
|
||||
_account = account;
|
||||
}
|
||||
|
||||
public AccountSecurityKey key { get; }
|
||||
public uint account_key => key.account;
|
||||
public uint security_key => key.security;
|
||||
public string account_name => _account?.name;
|
||||
public SecurityImmutable? security { get; }
|
||||
public RiskLimits limits { get; }
|
||||
|
||||
public bool Equals(AccountSecurityRiskLimits other) =>
|
||||
key.Equals(other.key) &&
|
||||
limits.Equals(other.limits);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is AccountSecurityRiskLimits other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(key.GetHashCode(), limits.GetHashCode());
|
||||
}
|
||||
|
||||
readonly struct RiskLimits : IEquatable<RiskLimits> {
|
||||
public RiskLimits(
|
||||
bool is_position_limit_enabled,
|
||||
uint max_long_position,
|
||||
uint max_short_position,
|
||||
bool is_clip_limit_enabled,
|
||||
uint clip_limit,
|
||||
bool is_price_limit_enabled,
|
||||
double min_price,
|
||||
double max_price) {
|
||||
//
|
||||
this.is_position_limit_enabled = is_position_limit_enabled;
|
||||
this.max_long_position = max_long_position;
|
||||
this.max_short_position = max_short_position;
|
||||
this.is_clip_limit_enabled = is_clip_limit_enabled;
|
||||
this.clip_limit = clip_limit;
|
||||
this.is_price_limit_enabled = is_price_limit_enabled;
|
||||
this.min_price = min_price;
|
||||
this.max_price = max_price;
|
||||
}
|
||||
|
||||
// position
|
||||
public bool is_position_limit_enabled { get; }
|
||||
public uint max_long_position { get; }
|
||||
public uint max_short_position { get; }
|
||||
// clip
|
||||
public bool is_clip_limit_enabled { get; }
|
||||
public uint clip_limit { get; }
|
||||
// price
|
||||
public bool is_price_limit_enabled { get; }
|
||||
public double min_price { get; }
|
||||
public double max_price { get; }
|
||||
|
||||
public bool Equals(RiskLimits other) =>
|
||||
is_position_limit_enabled == other.is_position_limit_enabled &&
|
||||
max_long_position == other.max_long_position &&
|
||||
max_short_position == other.max_short_position &&
|
||||
is_clip_limit_enabled == other.is_clip_limit_enabled &&
|
||||
clip_limit == other.clip_limit &&
|
||||
is_price_limit_enabled == other.is_price_limit_enabled &&
|
||||
min_price == other.min_price &&
|
||||
max_price == other.max_price;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is RiskLimits other && Equals(other);
|
||||
|
||||
public override int GetHashCode() {
|
||||
const int non_bin_bits = 28;
|
||||
// Evenly split the non-binary props across the first 28 bits.
|
||||
// The last 3 bits are used to represent each of the 'is_enabled' (binary) props.
|
||||
var hash = EqualityComparer.auto_phase(new[] {
|
||||
max_long_position.GetHashCode(),
|
||||
max_short_position.GetHashCode(),
|
||||
clip_limit.GetHashCode(),
|
||||
min_price.GetHashCode(),
|
||||
max_price.GetHashCode()
|
||||
}, non_bin_bits);
|
||||
if (is_position_limit_enabled)
|
||||
hash |= 1 << (non_bin_bits + 1);
|
||||
if (is_clip_limit_enabled)
|
||||
hash |= 1 << (non_bin_bits + 2);
|
||||
if (is_price_limit_enabled)
|
||||
hash |= 1 << (non_bin_bits + 3);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
static class AccountSecurityRiskLimitsDataHelper {
|
||||
public static readonly Func<AccountSecurityRiskLimits, AccountSecurityKey> key_fn =
|
||||
a => a.key;
|
||||
|
||||
// Don't make this a constructor. Subscribers to this data are discouraged
|
||||
// from using the server side data (client/server coupling should be minimal).
|
||||
public static AccountSecurityRiskLimits to_client_data(uint account_key, SecurityRiskLimitUnitImmutable api_data) =>
|
||||
new AccountSecurityRiskLimits(
|
||||
new AccountSecurityKey(account_key, api_data.SecurityKey),
|
||||
new RiskLimits(
|
||||
api_data.EnforceNetPosLimits,
|
||||
api_data.NetLongPositionLimit,
|
||||
api_data.NetShortPositionLimit,
|
||||
api_data.EnforceClipLimits,
|
||||
api_data.ClipSize,
|
||||
api_data.EnforcePriceBands,
|
||||
api_data.MinSellPrice,
|
||||
api_data.MaxBuyPrice));
|
||||
|
||||
public static AccountSecurityRiskLimits with(this AccountSecurityRiskLimits cli_data, RiskLimits? limits = null, AccountTuple? account = null, SecurityImmutable? security = null, AccountSecurityKey? key = null) =>
|
||||
new AccountSecurityRiskLimits(
|
||||
key ?? cli_data.key,
|
||||
limits ?? cli_data.limits,
|
||||
account: account ?? new AccountTuple(cli_data.account_key, cli_data.account_name),
|
||||
security: security ?? cli_data.security);
|
||||
|
||||
public static RiskLimits with(this RiskLimits limits,
|
||||
bool? is_position_limit_enabled = null,
|
||||
uint? max_long_position = null,
|
||||
uint? max_short_position = null,
|
||||
bool? is_clip_limit_enabled = null,
|
||||
uint? clip_limit = null,
|
||||
bool? is_price_limit_enabled = null,
|
||||
double? min_price = null,
|
||||
double? max_price = null) =>
|
||||
new RiskLimits(
|
||||
is_position_limit_enabled ?? limits.is_position_limit_enabled,
|
||||
max_long_position ?? limits.max_long_position,
|
||||
max_short_position ?? limits.max_short_position,
|
||||
is_clip_limit_enabled ?? limits.is_clip_limit_enabled,
|
||||
clip_limit ?? limits.clip_limit,
|
||||
is_price_limit_enabled ?? limits.is_price_limit_enabled,
|
||||
min_price ?? limits.min_price,
|
||||
max_price ?? limits.max_price);
|
||||
|
||||
public static SecurityRiskLimitUnit from_client_data(uint security_key, RiskLimits limits) =>
|
||||
new SecurityRiskLimitUnit {
|
||||
SecurityKey = security_key,
|
||||
NetLongPositionLimit = limits.max_long_position,
|
||||
NetShortPositionLimit = limits.max_short_position,
|
||||
MaxBuyPrice = limits.max_price,
|
||||
MinSellPrice = limits.min_price,
|
||||
ClipSize = limits.clip_limit,
|
||||
EnforceNetPosLimits = limits.is_position_limit_enabled,
|
||||
EnforceClipLimits = limits.is_clip_limit_enabled,
|
||||
EnforcePriceBands = limits.is_price_limit_enabled
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -276,7 +276,7 @@ namespace mbts.spark {
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
sides = new[] { side },
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
security_criteria = SecurityCriteria.from_securities(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace mbts.spark {
|
||||
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));
|
||||
_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Select(r => r.security));
|
||||
}
|
||||
return _selected_security_criteria_backing;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
var comp = comp_builder.to_comparer();
|
||||
_sorted_selected_security_criteria_backing = new SecurityCriteria(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
_sorted_selected_security_criteria_backing = SecurityCriteria.from_securities(_vm.selected_rows.Ordered(comp).Select(r => r.security));
|
||||
}
|
||||
}
|
||||
return _sorted_selected_security_criteria_backing;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.co2;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
@@ -50,18 +49,6 @@ namespace bts.shell.time_and_sales {
|
||||
public void raise_invalidated() =>
|
||||
invalidated?.Invoke();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(TimeAndSalesRowVM),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(time),
|
||||
nameof(price),
|
||||
nameof(size),
|
||||
nameof(aggressor_side),
|
||||
nameof(security_key),
|
||||
});
|
||||
|
||||
public bool Equals(TimeAndSalesRowVM other) =>
|
||||
other != null &&
|
||||
row_key == other.row_key;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
@@ -125,8 +124,6 @@ namespace bts.shell.time_and_sales {
|
||||
show_in_context_menu = _can_pause_updates
|
||||
};
|
||||
_commands = new SparkCommand[] {
|
||||
// csv export
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(TimeAndSalesRowVM.csv_ordered_properties), "TaS")),
|
||||
// ladder
|
||||
SparkCommands.ShowLadderAutoLookup(SparkCommands.KeyGestures.ShowLadder)
|
||||
.complete(current_sec_key_src, sec_keys => sec_keys.Count > 0),
|
||||
@@ -138,7 +135,7 @@ namespace bts.shell.time_and_sales {
|
||||
.complete(selected_rows_src, always_true, get_fills_startup_data),
|
||||
// mview
|
||||
SparkCommands.ShowMarketView
|
||||
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria),
|
||||
.complete(selected_rows_src, always_true, rows => rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria),
|
||||
// pause
|
||||
toggle_is_paused_cmd,
|
||||
// change securities
|
||||
@@ -183,7 +180,7 @@ namespace bts.shell.time_and_sales {
|
||||
|
||||
FillsSettings get_fills_startup_data(ImmutableHashSet<TimeAndSalesRowVM> rows) =>
|
||||
new FillsSettings(new FillsFilterStates {
|
||||
security = rows.Count > 0 ? new SecurityCriteria(rows.Select(r => r.security)) : _security_criteria
|
||||
security = rows.Count > 0 ? SecurityCriteria.from_securities(rows.Select(r => r.security)) : _security_criteria
|
||||
});
|
||||
|
||||
string get_is_paused_cmd_title() =>
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace mbts.spark {
|
||||
), 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))
|
||||
security_criteria = SecurityCriteria.from_securities(_vm.value.row_vms.Select(r => r.security))
|
||||
};
|
||||
return ob_settings;
|
||||
}), SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace mbts.spark {
|
||||
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)
|
||||
security_criteria = SecurityCriteria.from_securities(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using bts.collections;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.collections;
|
||||
using mbts.persist;
|
||||
using mbts.spark.filters;
|
||||
@@ -334,27 +333,6 @@ namespace mbts.spark {
|
||||
public override bool Equals(object obj) => Equals(obj as Row);
|
||||
public override int GetHashCode() => _fill.FillKey.GetHashCode() ^ _order.Key.GetHashCode();
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(typeof(Row),
|
||||
new string[] {
|
||||
nameof(security),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(side),
|
||||
nameof(fill_time),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(size),
|
||||
nameof(price),
|
||||
nameof(fill_kind),
|
||||
nameof(fill_status),
|
||||
nameof(fill_key),
|
||||
nameof(order_key),
|
||||
nameof(security_key)
|
||||
});
|
||||
|
||||
public event Action invalidated;
|
||||
void IRowVM.raise_invalidated() => invalidated?.Invoke();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using bts.shell;
|
||||
using bts.shell.fills;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
@@ -62,7 +61,6 @@ namespace mbts.spark {
|
||||
_view = new FillsView { DataContext = _vm };
|
||||
|
||||
_commands = new List<SparkCommand> {
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(FillsVM.Row.csv_ordered_properties), "Fills")),
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
|
||||
@@ -302,7 +302,7 @@ namespace mbts.spark {
|
||||
|
||||
var state = new bts.shell.orderbook.OrderBookStartupData(
|
||||
new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(_owner.security),
|
||||
security_criteria = SecurityCriteria.from_securities(_owner.security),
|
||||
price = price_filter,
|
||||
statuses = new[] { OrderStatus.Working },
|
||||
order_types = AlgoID.exchange_algo_keys
|
||||
|
||||
@@ -583,7 +583,7 @@ namespace mbts.spark {
|
||||
void _show_book() {
|
||||
var ob_settings = bts.shell.orderbook.OrderBookStartupData.Default;
|
||||
ob_settings.filters = new bts.shell.orderbook.FilterStates {
|
||||
security_criteria = new SecurityCriteria(security)
|
||||
security_criteria = SecurityCriteria.from_securities(security)
|
||||
};
|
||||
_bus.post(SparkTags.ShowOrderBookTag, ob_settings);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using bts.api.orderman;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using bts.utils.csv;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
@@ -12,7 +11,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
@@ -62,35 +60,6 @@ namespace mbts.spark {
|
||||
public event Action<(OrderSecurityRootPair oldval, OrderSecurityRootPair newval)> updated;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public static System.Collections.Specialized.OrderedDictionary csv_ordered_properties =>
|
||||
CsvHelper.create_ordering_maintainer(
|
||||
typeof(OrderBookRowVM),
|
||||
new string[] {
|
||||
nameof(security_text),
|
||||
nameof(symbol),
|
||||
nameof(time_created),
|
||||
nameof(last_change),
|
||||
nameof(user),
|
||||
nameof(account),
|
||||
nameof(exchange),
|
||||
nameof(time_in_force),
|
||||
nameof(order_type),
|
||||
nameof(root_algo_display_name),
|
||||
nameof(order_sub_type),
|
||||
nameof(side),
|
||||
nameof(quantity),
|
||||
nameof(filled_quantity),
|
||||
nameof(price),
|
||||
nameof(avg_price),
|
||||
nameof(last_price),
|
||||
nameof(piq),
|
||||
nameof(status),
|
||||
// last message (could contains commas, so skipping)
|
||||
nameof(security_key),
|
||||
nameof(order_key),
|
||||
nameof(parent_order_key),
|
||||
});
|
||||
|
||||
// Core
|
||||
public OrderSecurityRootPair order_tuple { get; private set; }
|
||||
public OrderRecordImmutable order => order_tuple.order.order;
|
||||
|
||||
@@ -6,7 +6,6 @@ using bts.shell.fills;
|
||||
using bts.shell.orderbook;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using bts.utils.csv;
|
||||
using mbts.persist;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
@@ -232,7 +231,6 @@ namespace mbts.spark {
|
||||
toggle_collapse_all_command,
|
||||
toggle_expand_all_command,
|
||||
show_grid_settings_command,
|
||||
new SparkCommandCommand("Export to csv", Icons.Export, FileDialogCommand.export_csv(() => _vm.rows.ToCsv(OrderBookRowVM.csv_ordered_properties), "OrderBook")),
|
||||
// hidden cmds (i.e., keyboard shortcuts at window level)
|
||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.ModifyOrders, null, _vm.modify_selected_orders_cmd, OrderBookView.KeyboardShortcuts.ModifyOrders)),
|
||||
hidden_cmd(new SparkCommandCommand(OrderBookView.CommandDescriptions.RevertOrders, null, _vm.revert_selected_orders_cmd, OrderBookView.KeyboardShortcuts.RevertOrders)),
|
||||
|
||||
@@ -633,7 +633,7 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.Exchange: filter.exchange = new[] { (Venue)group.Name }; break;
|
||||
case ColumnKeys.Security:
|
||||
if (group.Name is SecurityImmutable sec)
|
||||
filter.security = new SecurityCriteria(sec);
|
||||
filter.security = SecurityCriteria.from_securities(sec);
|
||||
break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
@@ -696,7 +696,7 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.Exchange: filter.exchanges = new[] { (Venue)g.Name }; break;
|
||||
case ColumnKeys.Security:
|
||||
if (g.Name is SecurityImmutable sec)
|
||||
filter.security_criteria = new SecurityCriteria(sec);
|
||||
filter.security_criteria = SecurityCriteria.from_securities(sec);
|
||||
break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using bts.admin.risk;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using Component = bts.shell.risk_management.RiskLimitsImportConflictsView.RiskLimitsComponent;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
static class RiskLimitsImporter {
|
||||
/// <summary>
|
||||
/// Parses the specified file and passes the results to the <paramref name="on_parsed"/> delegate.
|
||||
/// If the parser encounters improperly formatted values, a dialog is automatically opened
|
||||
/// </summary>
|
||||
/// <returns>An IDisposable that will close the import-conflicts-dialog (if it was launched).</returns>
|
||||
public static IDisposable import_limits(string file_name, IReadOnlyDictionary<uint, SecurityMD> security_map, IReadOnlyDictionary<uint, string> account_map, Action<IEnumerable<ParsedRiskLimits>> on_parsed) {
|
||||
if (string.IsNullOrEmpty(file_name))
|
||||
throw new ArgumentException("File name is null or empty.", nameof(file_name));
|
||||
if (security_map is null)
|
||||
throw new ArgumentNullException(nameof(security_map));
|
||||
if (account_map is null)
|
||||
throw new ArgumentNullException(nameof(account_map));
|
||||
if (on_parsed is null)
|
||||
throw new ArgumentNullException(nameof(on_parsed));
|
||||
IReadOnlyList<ParsedRiskLimits> parsed_limit_items;
|
||||
try {
|
||||
parsed_limit_items = RiskLimitsParser.parse_rows(
|
||||
File.ReadAllLines(file_name),
|
||||
MappedDictionary.create(security_map, sec_md => sec_md.security),
|
||||
account_map)
|
||||
.ToList();
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Error($"Error importing limits: {ex}.", RiskManagementWindow.window_type_display_name);
|
||||
return null;
|
||||
}
|
||||
var conflicts = RiskLimitsParser.get_conflicts(parsed_limit_items);
|
||||
if (conflicts.Count > 0) {
|
||||
// init vm
|
||||
var vm = new RiskLimitsImportVM(parsed_limit_items.Count) {
|
||||
security_map = security_map
|
||||
};
|
||||
// add rows
|
||||
foreach (var kv in conflicts) {
|
||||
var resolve_del = RiskLimitsParser.assign_component(kv.Key);
|
||||
foreach (var text_groups in kv.Value) {
|
||||
var eval = get_error_value(kv.Key);
|
||||
var row = new RiskLimitsImportVM.Row(kv.Key, text_groups.Key, text_groups.Value, eval, eval);
|
||||
vm.rows.Add(row);
|
||||
vm.import_request += () => {
|
||||
foreach (var pmf in text_groups.Value) {
|
||||
if (resolve_del(pmf.result, row.value) is AccountSecurityRiskLimits resolved)
|
||||
pmf.result = resolved;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
var win = new RiskLimitsImportConflictsDialog {
|
||||
DataContext = vm,
|
||||
Title = RiskManagementWindow.window_type_display_name,
|
||||
Topmost = true
|
||||
};
|
||||
IDisposable dispose = null;
|
||||
vm.RequestClose += (s, e) => Disposable.dispose(ref dispose);
|
||||
vm.import_request += () => {
|
||||
on_parsed(parsed_limit_items);
|
||||
Disposable.dispose(ref dispose);
|
||||
};
|
||||
Disposable.dispose(ref dispose);
|
||||
vm.accounts.SetItems(account_map.Select(kv => new AccountTuple(kv.Key, kv.Value)).Cast<AccountTuple?>());
|
||||
dispose = Disposable.Create(
|
||||
vm,
|
||||
Disposable.Create(close));
|
||||
win.move_to_mouse(-200, -75);
|
||||
win.Show();
|
||||
return dispose;
|
||||
|
||||
void close() => WindowUtilities.force_close(win);
|
||||
object get_error_value(Component component) {
|
||||
switch (component) {
|
||||
case Component.MaxNetLongPosition:
|
||||
case Component.MaxNetShortPosition:
|
||||
case Component.ClipLimit:
|
||||
return 0;
|
||||
case Component.MinPrice:
|
||||
case Component.MaxPrice:
|
||||
return double.NaN;
|
||||
case Component.IsPositionLimitEnabled:
|
||||
case Component.IsClipLimitEnabled:
|
||||
case Component.IsPriceLimitEnabled:
|
||||
return false;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
on_parsed(parsed_limit_items);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using bts.admin.risk;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using RiskLimitsComponent = bts.shell.risk_management.RiskLimitsImportConflictsView.RiskLimitsComponent;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class ParsedRiskLimits {
|
||||
public readonly Guid unpersisted_id = Guid.NewGuid();
|
||||
public AccountSecurityRiskLimits result { get; set; }
|
||||
|
||||
public (string text, bool success) security { get; set; }
|
||||
public (string text, bool success) account { get; set; }
|
||||
public (string text, bool success) max_long_position { get; set; }
|
||||
public (string text, bool success) max_short_position { get; set; }
|
||||
public (string text, bool success) is_position_limit_enabled { get; set; }
|
||||
public (string text, bool success) clip_limit { get; set; }
|
||||
public (string text, bool success) is_clip_limit_enabled { get; set; }
|
||||
public (string text, bool success) min_price { get; set; }
|
||||
public (string text, bool success) max_price { get; set; }
|
||||
public (string text, bool success) is_price_limit_enabled { get; set; }
|
||||
|
||||
public bool Equals(ParsedRiskLimits other) => other != null && unpersisted_id == other.unpersisted_id;
|
||||
public override bool Equals(object obj) => Equals(obj as ParsedRiskLimits);
|
||||
public override int GetHashCode() => unpersisted_id.GetHashCode();
|
||||
}
|
||||
|
||||
static class RiskLimitsParser {
|
||||
public const string file_extension = "csv";
|
||||
|
||||
public static Func<AccountSecurityRiskLimits, object, object> assign_component(RiskLimitsComponent component) {
|
||||
switch (component) {
|
||||
case RiskLimitsComponent.Security: return (mf, val) => mf.with(key: new AccountSecurityKey(mf.account_key, ConvertUtils.try_convert<SecurityImmutable?>(val)?.Id ?? 0));
|
||||
case RiskLimitsComponent.Account: return (mf, val) => mf.with(key: new AccountSecurityKey(ConvertUtils.try_convert<AccountTuple?>(val)?.id ?? 0, mf.key.security));
|
||||
case RiskLimitsComponent.MaxNetLongPosition: return (mf, val) => mf.with(mf.limits.with(max_long_position: ConvertUtils.try_convert<uint>(val)));
|
||||
case RiskLimitsComponent.MaxNetShortPosition: return (mf, val) => mf.with(mf.limits.with(max_short_position: ConvertUtils.try_convert<uint>(val)));
|
||||
case RiskLimitsComponent.IsPositionLimitEnabled: return (mf, val) => mf.with(mf.limits.with(is_price_limit_enabled: ConvertUtils.try_convert<bool>(val)));
|
||||
case RiskLimitsComponent.ClipLimit: return (mf, val) => mf.with(mf.limits.with(clip_limit: ConvertUtils.try_convert<uint>(val)));
|
||||
case RiskLimitsComponent.IsClipLimitEnabled: return (mf, val) => mf.with(mf.limits.with(is_price_limit_enabled: ConvertUtils.try_convert<bool>(val)));
|
||||
case RiskLimitsComponent.MinPrice: return (mf, val) => mf.with(mf.limits.with(min_price: ConvertUtils.try_convert(val, double.NaN)));
|
||||
case RiskLimitsComponent.MaxPrice: return (mf, val) => mf.with(mf.limits.with(max_price: ConvertUtils.try_convert(val, double.NaN)));
|
||||
case RiskLimitsComponent.IsPriceLimitEnabled: return (mf, val) => mf.with(mf.limits.with(is_price_limit_enabled: ConvertUtils.try_convert<bool>(val)));
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableDictionary<RiskLimitsComponent, ImmutableDictionary<string, ImmutableList<ParsedRiskLimits>>> get_conflicts(IEnumerable<ParsedRiskLimits> parsed_limits) {
|
||||
// Add all the components up front (a simple optimizatino since there aren't many),
|
||||
// so there is no need to check for there existence as various conflicts are added.
|
||||
// After all conflicts have been added, remove the empty entries.
|
||||
var missing_components = EnumHelper.get_values<RiskLimitsComponent>()
|
||||
.ToImmutableDictionary(c => c, c => ImmutableDictionary<string, ImmutableList<ParsedRiskLimits>>.Empty)
|
||||
.ToBuilder();
|
||||
foreach (var pmf in parsed_limits) {
|
||||
if (!pmf.security.success) {
|
||||
add_missing(RiskLimitsComponent.Security, pmf.security.text, pmf);
|
||||
}
|
||||
if (!pmf.account.success) {
|
||||
add_missing(RiskLimitsComponent.Account, pmf.account.text, pmf);
|
||||
}
|
||||
if (!pmf.max_long_position.success) {
|
||||
add_missing(RiskLimitsComponent.MaxNetLongPosition, pmf.max_long_position.text, pmf);
|
||||
}
|
||||
if (!pmf.max_short_position.success) {
|
||||
add_missing(RiskLimitsComponent.MaxNetShortPosition, pmf.max_short_position.text, pmf);
|
||||
}
|
||||
if (!pmf.is_position_limit_enabled.success) {
|
||||
add_missing(RiskLimitsComponent.IsPositionLimitEnabled, pmf.is_position_limit_enabled.text, pmf);
|
||||
}
|
||||
if (!pmf.clip_limit.success) {
|
||||
add_missing(RiskLimitsComponent.ClipLimit, pmf.clip_limit.text, pmf);
|
||||
}
|
||||
if (!pmf.is_clip_limit_enabled.success) {
|
||||
add_missing(RiskLimitsComponent.IsClipLimitEnabled, pmf.is_clip_limit_enabled.text, pmf);
|
||||
}
|
||||
if (!pmf.min_price.success) {
|
||||
add_missing(RiskLimitsComponent.MinPrice, pmf.min_price.text, pmf);
|
||||
}
|
||||
if (!pmf.max_price.success) {
|
||||
add_missing(RiskLimitsComponent.MaxPrice, pmf.max_price.text, pmf);
|
||||
}
|
||||
if (!pmf.is_price_limit_enabled.success) {
|
||||
add_missing(RiskLimitsComponent.IsPriceLimitEnabled, pmf.is_price_limit_enabled.text, pmf);
|
||||
}
|
||||
}
|
||||
// remove empty component conflicts
|
||||
var empty_keys = missing_components.Where(kv => kv.Value.Count == 0).Select(kv => kv.Key).ToList();
|
||||
missing_components.RemoveRange(empty_keys);
|
||||
return missing_components.ToImmutable();
|
||||
// local methods
|
||||
void add_missing(RiskLimitsComponent component, string text, ParsedRiskLimits prl) {
|
||||
if (!missing_components.TryGetValue(component, out var conflicts)) {
|
||||
conflicts = ImmutableDictionary<string, ImmutableList<ParsedRiskLimits>>.Empty;
|
||||
}
|
||||
if (!conflicts.TryGetValue(text, out var affected)) {
|
||||
affected = ImmutableList<ParsedRiskLimits>.Empty;
|
||||
}
|
||||
missing_components[component] = conflicts.SetItem(text, affected.Add(prl));
|
||||
}
|
||||
}
|
||||
|
||||
// This implementation prioritizes simplicity over speed.
|
||||
// The security desc lookup could be better optimized, but the speed at which limits are imported is probably not a paramount concern.
|
||||
public static IEnumerable<ParsedRiskLimits> parse_rows(IEnumerable<string> rows, IReadOnlyDictionary<uint, SecurityImmutable> security_map, IReadOnlyDictionary<uint, string> account_map) =>
|
||||
parse_rows(rows,
|
||||
reverse_lookup(MappedDictionary.create(security_map, s => s.ToString())),
|
||||
reverse_lookup(account_map));
|
||||
|
||||
public static IEnumerable<ParsedRiskLimits> parse_rows(IEnumerable<string> rows, Func<string, uint> security_id_lookup, Func<string, uint> account_id_lookup) {
|
||||
var columns = new[] {
|
||||
(Headers.Security, Cells.security(security_id_lookup)),
|
||||
(Headers.Account, Cells.account(account_id_lookup)),
|
||||
(Headers.MaxNetLongPosition, Cells.max_long_position),
|
||||
(Headers.MaxNetShortPosition, Cells.max_short_position),
|
||||
(Headers.IsPositionLimitEnabled, Cells.is_position_limit_enabled),
|
||||
(Headers.ClipLimit, Cells.clip_limit),
|
||||
(Headers.IsClipLimitEnabled, Cells.is_clip_limit_enabled),
|
||||
(Headers.MinPrice, Cells.min_price),
|
||||
(Headers.MaxPrice, Cells.max_price),
|
||||
(Headers.IsPriceLimitEnabled, Cells.is_price_limit_enabled)
|
||||
}.ToDictionary(col => col.Item1, col => col.Item2.with_null_check());
|
||||
return RowParser.parse_rows(rows, columns, Cells.delimiter);
|
||||
}
|
||||
|
||||
public static Func<string, TKey> reverse_lookup<TKey>(IReadOnlyDictionary<TKey, string> map) {
|
||||
var lookup = map.ToLookup(kv => kv.Value, kv => kv.Key);
|
||||
return cell_text => lookup.Contains(cell_text) ? lookup[cell_text].First() : default;
|
||||
}
|
||||
|
||||
static class Headers {
|
||||
public const string
|
||||
Security = "Security",
|
||||
Account = "Account",
|
||||
MaxNetLongPosition = "Max Long",
|
||||
MaxNetShortPosition = "Max Short",
|
||||
IsPositionLimitEnabled = "Enforce Position",
|
||||
ClipLimit = "Clip",
|
||||
IsClipLimitEnabled = "Enforce Clip",
|
||||
MinPrice = "Min Price",
|
||||
MaxPrice = "Max Price",
|
||||
IsPriceLimitEnabled = "Enforce Price";
|
||||
}
|
||||
|
||||
static class Cells {
|
||||
public const char delimiter = ',';
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedRiskLimits> security(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedRiskLimits pmf, string text) {
|
||||
pmf.result = pmf.result.with(key: new AccountSecurityKey(pmf.result.key.account, reverse_id_lookup(text)));
|
||||
pmf.security = (text, pmf.result.key.security != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static RowParser.AssignColumnValue<ParsedRiskLimits> account(Func<string, uint> reverse_id_lookup) {
|
||||
return impl;
|
||||
void impl(ref ParsedRiskLimits pmf, string text) {
|
||||
pmf.result = pmf.result.with(key: new AccountSecurityKey(reverse_id_lookup(text), pmf.result.key.security));
|
||||
pmf.account = (text, pmf.result.key.account != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void max_long_position(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (uint.TryParse(cell_text, out uint v)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(max_long_position: v));
|
||||
pmf.max_long_position = (cell_text, true);
|
||||
} else {
|
||||
pmf.max_long_position = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void max_short_position(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (uint.TryParse(cell_text, out uint v)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(max_short_position: v));
|
||||
pmf.max_short_position = (cell_text, true);
|
||||
} else {
|
||||
pmf.max_short_position = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void is_position_limit_enabled(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (_try_parse_yes_no(cell_text, out var result)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(is_position_limit_enabled: result));
|
||||
pmf.is_position_limit_enabled = (cell_text, true);
|
||||
} else {
|
||||
pmf.is_position_limit_enabled = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clip_limit(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (uint.TryParse(cell_text, out uint v)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(clip_limit: v));
|
||||
pmf.clip_limit = (cell_text, true);
|
||||
} else {
|
||||
pmf.clip_limit = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void is_clip_limit_enabled(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (_try_parse_yes_no(cell_text, out var result)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(is_clip_limit_enabled: result));
|
||||
pmf.is_clip_limit_enabled = (cell_text, true);
|
||||
} else {
|
||||
pmf.is_clip_limit_enabled = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
static bool _try_parse_yes_no(string text, out bool result) {
|
||||
switch (text?.Trim()?.ToLower()) {
|
||||
case "y": result = true; break;
|
||||
case "n": result = false; break;
|
||||
default: result = default; return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void min_price(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (double.TryParse(cell_text, out var v)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(min_price: v));
|
||||
pmf.min_price = (cell_text, true);
|
||||
} else {
|
||||
pmf.min_price = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void max_price(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (double.TryParse(cell_text, out var v)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(max_price: v));
|
||||
pmf.max_price = (cell_text, true);
|
||||
} else {
|
||||
pmf.max_price = (cell_text, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void is_price_limit_enabled(ref ParsedRiskLimits pmf, string cell_text) {
|
||||
if (_try_parse_yes_no(cell_text, out var result)) {
|
||||
pmf.result = pmf.result.with(pmf.result.limits.with(is_price_limit_enabled: result));
|
||||
pmf.is_price_limit_enabled = (cell_text, true);
|
||||
} else {
|
||||
pmf.is_price_limit_enabled = (cell_text, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
using bts.admin.risk;
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using mbts;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class RiskManagementWindow : SparkWindow<RiskManagementSettings>, ISharedWindow {
|
||||
public const string
|
||||
window_type_id = "RiskManagement",
|
||||
window_type_display_name = "Risk Management";
|
||||
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
readonly AdminModel _model;
|
||||
FrameworkElement _view;
|
||||
IReadOnlyList<SparkCommand> _commands;
|
||||
IDisposable _settings_sub, _securities_sub, _cmd_subscriptions;
|
||||
RiskManagementSettings _settings;
|
||||
RiskManagementVM _vm;
|
||||
SimpleGridSettingsManagerVM<RiskManagementControl.Columns> _grid_settings_manager => _vm.grid_settings_manager;
|
||||
SparkMenuDisplayVM _menu_display_vm;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public RiskManagementWindow(
|
||||
IBus bus,
|
||||
AdminModel model,
|
||||
IWindowSettingsManager<GridSettings> settings_manager,
|
||||
ISaveWindow<RiskManagementSettings> isw,
|
||||
RiskManagementSettings settings)
|
||||
: base(window_type_display_name, bus, isw, Icons.Risk) {
|
||||
//
|
||||
_settings_manager = settings_manager;
|
||||
_settings = settings;
|
||||
_model = model;
|
||||
thread_id = mbts.config.Configuration.get<bts.config.MultiThreadedWindows>().risk_management ? window_type_id : null;
|
||||
_securities_sub = bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec_map => _security_map = sec_map);
|
||||
}
|
||||
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override string window_type => window_type_id;
|
||||
|
||||
public override RiskManagementSettings SaveState =>
|
||||
(_vm?.create() ?? RiskManagementSettings.Default).with(menu_display: _menu_display_vm.safe_create());
|
||||
|
||||
public override UIElement Content => _view;
|
||||
|
||||
public override double MinWidth =>
|
||||
RiskManagementSettings.DefaultGrid.columns.Where(c => c.is_visible).Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id { get; }
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _cmd_subscriptions);
|
||||
_commands.empty_if_null().OfType<IDisposable>().DisposeAll();
|
||||
Disposable.dispose(ref _securities_sub);
|
||||
Disposable.dispose(ref _settings_sub);
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
// assert nothing has been initialized yet
|
||||
Log.assert(_menu_display_vm == null, "MenuDisplay viewmodel has already been initialized.");
|
||||
Log.assert(_vm == null, "Viewmodel has already been initialized.");
|
||||
Log.assert(_settings != null, "Settings is null.");
|
||||
Log.assert(_settings_sub == null, "Grid settings subscription has already been initialized.");
|
||||
// initialize everything: menu_display_vm, vm, settings subscription, and view
|
||||
_menu_display_vm = _init_menu_display_vm(_settings.menu_display);
|
||||
_vm = _init_vm(_bus.subscribe(SparkTags.SecuritiesTag), _model, _settings);
|
||||
_settings_sub = _settings_manager.subscribe_current()
|
||||
.OnDispatcher()
|
||||
.Subscribe(gs => _vm.load_from(gs));
|
||||
(_commands, _cmd_subscriptions) = _mk_commands();
|
||||
_view = new RiskManagementControl { DataContext = _vm };
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
|
||||
static SparkMenuDisplayVM _init_menu_display_vm(SparkMenuDisplaySettings settings) {
|
||||
var vm = new SparkMenuDisplayVM();
|
||||
vm.load_from(settings);
|
||||
return vm;
|
||||
}
|
||||
|
||||
static RiskManagementVM _init_vm(IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities_source, AdminModel model, RiskManagementSettings settings) {
|
||||
var vm = new RiskManagementVM(model.account_security_risk_limits, model.on_account_tuples);
|
||||
vm.request_update_limit += update_limit;
|
||||
vm.load_from(settings);
|
||||
var vm_sec_sub = securities_source.OnDispatcher().Subscribe(sec_map => vm.security_map = sec_map);
|
||||
vm.OnDisposed += _ => Disposable.dispose(ref vm_sec_sub);
|
||||
return vm;
|
||||
void update_limit(AccountSecurityRiskLimits lim) {
|
||||
model.set_risk_limits(
|
||||
MappedDictionary.create(
|
||||
vm.build_account_limits_map(), acc_sec_lims => acc_sec_lims.Select(asl => (asl.security_key, asl.limits))))
|
||||
.ignore();
|
||||
}
|
||||
}
|
||||
|
||||
(List<SparkCommand> cmds, IDisposable subscriptions) _mk_commands() {
|
||||
var subs = new List<IDisposable>();
|
||||
var cmds = new List<SparkCommand>();
|
||||
cmds.Add(new SparkCommandCommand("Add New Limits", Icons.AddBoxes, new DialogCommand(() => {
|
||||
if (_vm is null)
|
||||
return null;
|
||||
var account_id = _vm.selected_account?.id ?? 0;
|
||||
var init_state = new RiskLimitsModification(
|
||||
is_position_limit_enabled: true,
|
||||
is_clip_limit_enabled: true,
|
||||
is_price_limit_enabled: true);
|
||||
return mk_row_editor_dialog(init_state, commit, true);
|
||||
void commit(RiskLimitsModification result) {
|
||||
impl();
|
||||
async void impl() {
|
||||
var target_secs = await result.security_criteria.securities(_bus, TimeSpan.FromSeconds(5));
|
||||
if (target_secs.Count > 0) {
|
||||
var new_limits = result.apply(new RiskLimits());
|
||||
await _model.add_or_update_risk_limits(account_id, target_secs.Keys.Select(k => (k, new_limits)));
|
||||
}
|
||||
}
|
||||
}
|
||||
})));
|
||||
var edit_cmd = new DialogCommand(() => {
|
||||
if (!(_vm?.selected_account).HasValue)
|
||||
return null;
|
||||
var account_id = _vm.selected_account.Value.id;
|
||||
var targets = _vm.selected_rows.Select(r => r.data).ToList();
|
||||
var init_state = RiskLimitsModification.aggregate_common_props(targets);
|
||||
return mk_row_editor_dialog(init_state, commit, false);
|
||||
void commit(RiskLimitsModification mod) =>
|
||||
_vm.modify_rows(targets.WrapSelector(t => t.key, null), mod);
|
||||
}, () => _vm.selected_rows.Count > 0, true, off_x: -300, off_y: -60);
|
||||
_vm.selected_rows_changed += edit_cmd.RaiseCanExecuteChanged;
|
||||
cmds.Add(new SparkCommandCommand("Edit Selected Rows", Icons.ModifyOrder, edit_cmd));
|
||||
var revert_selected_rows_cmd = ObservantCommand.create(() => _vm?.revert(_vm.selected_rows), out var revert_selected_rows_observer);
|
||||
subs.Add(_vm.subscribe_selected_rows()
|
||||
.Select(srs => srs.Count > 0)
|
||||
.And(_vm.subscribe_has_dirty_rows())
|
||||
.OnDispatcher()
|
||||
.Subscribe(revert_selected_rows_observer));
|
||||
cmds.Add(new SparkCommandCommand("Revert Selected Rows", Icons.UndoCircular, revert_selected_rows_cmd));
|
||||
|
||||
cmds.Add(new SparkCommandCommand("Import Limits", Icons.Import,
|
||||
FileDialogCommand.open_file(file => _vm?.import_limits(file), extensions: RiskLimitsParser.file_extension)));
|
||||
|
||||
var save_cmd = new SparkCommandCommand("Save", Icons.Disk, ObservantCommand.create(() => {
|
||||
//_vm.selected_account.Value.id, _vm.rows.Where(r => r.)
|
||||
var lims = _vm.build_account_limits_map();
|
||||
var mlims = MappedDictionary.create(lims, l => l.Select(lim => (lim.security_key, lim.limits)));
|
||||
_model.set_risk_limits(mlims).ignore();
|
||||
}, out var save_observer));
|
||||
cmds.Add(save_cmd);
|
||||
subs.Add(_vm.subscribe_has_dirty_rows().OnDispatcher().Subscribe(save_observer));
|
||||
|
||||
var delete_cmd = new DelegateCommand(() => {
|
||||
if (!(_vm?.selected_account).HasValue)
|
||||
return;
|
||||
var to_remove = _vm.selected_rows;
|
||||
var account_id = _vm.selected_account.Value.id;
|
||||
if (BTSMessageBox.Confirm($"Are you sure you want to delete the selected limits ({to_remove.Count})?", "Delete Limits")) {
|
||||
// remove unpersisted limits first (i.e., imports), then delete the persisted ones.
|
||||
foreach (var r in to_remove) {
|
||||
if (!r.is_persisted) {
|
||||
_vm.rows.RemoveSorted(r, _vm.row_comparer);
|
||||
to_remove = to_remove.Remove(r);
|
||||
}
|
||||
}
|
||||
_model.delete_risk_limits(account_id, to_remove.Select(r => r.security_key)).ignore();
|
||||
}
|
||||
}, () => _vm.selected_rows.Count > 0);
|
||||
_vm.selected_rows_changed += delete_cmd.RaiseCanExecuteChanged;
|
||||
|
||||
cmds.Add(new SparkCommandCommand("Delete", Icons.Trash, delete_cmd));
|
||||
// wrap the grid settings view factory in a dialogCommand here since the vm is destroyed/recreated when the security criteria changes
|
||||
cmds.Add(SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
if (_vm is null)
|
||||
return null;
|
||||
_settings_manager.instance_settings = _vm.create().grid;
|
||||
var settings_vm = new ShellSettingsDialogVM<GridSettings, SimpleGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.risk_management_grid,
|
||||
UserSettings.FileExtensions.GRID,
|
||||
_grid_settings_manager.create_vm
|
||||
);
|
||||
settings_vm.SubscribeDisposed(literal_title_source.OnDispatcher().Subscribe(t => settings_vm.title = t));
|
||||
var dialog = new ShellSettingsDialog { DataContext = settings_vm };
|
||||
settings_vm.RequestClose += (s, e) => WindowUtilities.force_close(dialog);
|
||||
return dialog;
|
||||
})));
|
||||
cmds.Add(_menu_display_vm.spark_cmd);
|
||||
return (cmds, Disposable.wrap_collection(subs));
|
||||
|
||||
// although only the api only requires security keys,
|
||||
// the actual securities are used in this method so the user can see them in the view
|
||||
Window mk_row_editor_dialog(RiskLimitsModification init_state, Action<RiskLimitsModification> commit, bool is_add) {
|
||||
var limits_vm = new RiskLimitsModificationVM(is_add) {
|
||||
security_map = _security_map
|
||||
};
|
||||
limits_vm.load_from(init_state);
|
||||
limits_vm.request_commit += () => commit(limits_vm.create());
|
||||
var (title, icon) = is_add ? ("New Limits", Icons.AddBoxes) : ("Edit Limits", Icons.ModifyOrder);
|
||||
var dialog = new RiskLimitParametersDialog {
|
||||
DataContext = limits_vm,
|
||||
Icon = icon,
|
||||
Title = title
|
||||
};
|
||||
dialog.SetBinding(ContentControl.ContentProperty, new Binding());
|
||||
WindowUtilities.listen_for_close_requests(dialog);
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using bts.api;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class RiskManagementWindowFactory : ShellWindowFactory<RiskManagementSettings> {
|
||||
readonly IBus _bus;
|
||||
readonly AdminModel _model;
|
||||
readonly UserSettingsHub _settings_hub;
|
||||
|
||||
public RiskManagementWindowFactory(IShellModule owner, IBus bus, UserSettingsHub settings_hub, AdminModel model) : base(owner, "RiskManagement") {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_settings_hub = settings_hub;
|
||||
_dispose = bus.subscribe(SparkTags.ShowRiskManagement)
|
||||
.OnDispatcher()
|
||||
.Subscribe(show);
|
||||
|
||||
async void show(RiskManagementSettings settings) {
|
||||
var win = await load(settings);
|
||||
bus.post(SparkTags.ShowWindow, win);
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(RiskManagementSettings state) =>
|
||||
state is null ?
|
||||
throw new ArgumentNullException(nameof(state)) :
|
||||
Task.FromResult<IShellWindow>(new RiskManagementWindow(_bus, _model, _settings_hub.create_risk_management_settings_manager(state.grid), this, state));
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
using bts.admin.risk;
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
[ProtoContract]
|
||||
class RiskManagementFilterSettings : IEquatable<RiskManagementFilterSettings> {
|
||||
static readonly IEqualityComparer<RiskManagementFilterSettings> _comparer =
|
||||
ObjectSnapshot.property_equality_comparer<RiskManagementFilterSettings>();
|
||||
|
||||
public RiskManagementFilterSettings() { }
|
||||
|
||||
public RiskManagementFilterSettings(
|
||||
string security_regex,
|
||||
string security_key_regex,
|
||||
UIntMinMaxState max_long_position,
|
||||
UIntMinMaxState max_short_position,
|
||||
bool? is_position_limit_enabled,
|
||||
UIntMinMaxState clip_limit,
|
||||
bool? is_clip_limit_enabled,
|
||||
DoubleMinMaxState min_price,
|
||||
DoubleMinMaxState max_price,
|
||||
bool? is_price_limit_enabled) {
|
||||
// security
|
||||
this.security_regex = security_regex;
|
||||
this.security_key_regex = security_key_regex;
|
||||
// position
|
||||
this.max_long_position = max_long_position;
|
||||
this.max_short_position = max_short_position;
|
||||
this.is_position_limit_enabled = is_position_limit_enabled;
|
||||
// clip
|
||||
this.clip_limit = clip_limit;
|
||||
this.is_clip_limit_enabled = is_clip_limit_enabled;
|
||||
// price
|
||||
this.min_price = min_price;
|
||||
this.max_price = max_price;
|
||||
this.is_price_limit_enabled = is_price_limit_enabled;
|
||||
}
|
||||
|
||||
// security
|
||||
[ProtoMember(1)]
|
||||
public string security_regex { get; }
|
||||
[ProtoMember(2)]
|
||||
public string security_key_regex { get; }
|
||||
// position
|
||||
[ProtoMember(3)]
|
||||
public UIntMinMaxState max_long_position { get; }
|
||||
[ProtoMember(4)]
|
||||
public UIntMinMaxState max_short_position { get; }
|
||||
[ProtoMember(5)]
|
||||
public bool? is_position_limit_enabled { get; }
|
||||
// clip
|
||||
[ProtoMember(6)]
|
||||
public UIntMinMaxState clip_limit { get; }
|
||||
[ProtoMember(7)]
|
||||
public bool? is_clip_limit_enabled { get; }
|
||||
// price
|
||||
[ProtoMember(8)]
|
||||
public DoubleMinMaxState min_price { get; }
|
||||
[ProtoMember(9)]
|
||||
public DoubleMinMaxState max_price { get; }
|
||||
[ProtoMember(10)]
|
||||
public bool? is_price_limit_enabled { get; }
|
||||
|
||||
// 'is limit enabled' items (for filter dropdown)
|
||||
public bool[] is_position_limit_enabled_items => yes_no_filter_items(is_position_limit_enabled);
|
||||
public bool[] is_clip_limit_enabled_items => yes_no_filter_items(is_clip_limit_enabled);
|
||||
public bool[] is_price_limit_enabled_items => yes_no_filter_items(is_price_limit_enabled);
|
||||
|
||||
public static bool[] yes_no_filter_items(bool? state) =>
|
||||
state.HasValue ? new[] { state.Value } : Array.Empty<bool>();
|
||||
|
||||
public static bool? yes_no_filter_state(IEnumerable<bool> selected_items) {
|
||||
var set = new HashSet<bool>(selected_items);
|
||||
return set.Count == 1 ? set.First() : (bool?)null;
|
||||
}
|
||||
|
||||
public Func<AccountSecurityRiskLimits, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<AccountSecurityRiskLimits>();
|
||||
// security
|
||||
pb.add_regex(security_regex, l => l.security.ToString());
|
||||
pb.add_regex(security_key_regex, l => l.security_key.ToString());
|
||||
// position
|
||||
pb.add_numeric(max_long_position, l => l.limits.max_long_position);
|
||||
pb.add_numeric(max_short_position, l => l.limits.max_short_position);
|
||||
pb.add_discrete_values(is_position_limit_enabled_items, l => l.limits.is_position_limit_enabled);
|
||||
// clip
|
||||
pb.add_numeric(clip_limit, l => l.limits.clip_limit);
|
||||
pb.add_discrete_values(is_clip_limit_enabled_items, l => l.limits.is_clip_limit_enabled);
|
||||
// price
|
||||
pb.add_numeric(min_price, l => l.limits.min_price);
|
||||
pb.add_numeric(max_price, l => l.limits.max_price);
|
||||
pb.add_discrete_values(is_price_limit_enabled_items, l => l.limits.is_price_limit_enabled);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
|
||||
public RiskManagementFilterSettings with(
|
||||
string security_regex = null,
|
||||
string security_key_regex = null,
|
||||
UIntMinMaxState max_long_position = null,
|
||||
UIntMinMaxState max_short_position = null,
|
||||
OptionalNullable<bool>? is_position_limit_enabled = null,
|
||||
UIntMinMaxState clip_limit = null,
|
||||
OptionalNullable<bool>? is_clip_limit_enabled = null,
|
||||
DoubleMinMaxState min_price = null,
|
||||
DoubleMinMaxState max_price = null,
|
||||
OptionalNullable<bool>? is_price_limit_enabled = null) =>
|
||||
//
|
||||
new RiskManagementFilterSettings(
|
||||
// security
|
||||
security_regex ?? this.security_regex,
|
||||
security_key_regex ?? this.security_key_regex,
|
||||
// position
|
||||
max_long_position ?? this.max_long_position,
|
||||
max_short_position ?? this.max_short_position,
|
||||
is_position_limit_enabled.HasValue ? is_position_limit_enabled.Value.value : this.is_position_limit_enabled,
|
||||
// clip
|
||||
clip_limit ?? this.clip_limit,
|
||||
is_clip_limit_enabled.HasValue ? is_clip_limit_enabled.Value.value : this.is_clip_limit_enabled,
|
||||
// price
|
||||
min_price ?? this.min_price,
|
||||
max_price ?? this.max_price,
|
||||
is_price_limit_enabled.HasValue ? is_price_limit_enabled.Value.value : this.is_price_limit_enabled);
|
||||
|
||||
public bool Equals(RiskManagementFilterSettings other) =>
|
||||
_comparer.Equals(this, other);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is RiskManagementFilterSettings other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
_comparer.GetHashCode(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Columns = bts.shell.risk_management.RiskManagementControl.Columns;
|
||||
using ColumnWidths = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
|
||||
using ColumnHeaders = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
[ProtoContract]
|
||||
class RiskManagementSettings {
|
||||
[ProtoMember(3)]
|
||||
SortingCriteria[] _sorting_criteria;
|
||||
|
||||
public RiskManagementSettings() { }
|
||||
public RiskManagementSettings(
|
||||
GridSettings grid = null,
|
||||
RiskManagementFilterSettings filters = default,
|
||||
IEnumerable<SortingCriteria> sorting_criteria = null,
|
||||
uint account_id = 0,
|
||||
SparkMenuDisplaySettings? menu_display = null) {
|
||||
//
|
||||
this.grid = grid;
|
||||
this.filters = filters ?? new RiskManagementFilterSettings();
|
||||
_sorting_criteria = sorting_criteria?.ToArray();
|
||||
this.account_id = account_id;
|
||||
this.menu_display = menu_display ?? SparkMenuDisplaySettings.Default;
|
||||
}
|
||||
|
||||
public static RiskManagementSettings Default => new RiskManagementSettings(grid: DefaultGrid);
|
||||
public static GridSettings DefaultGrid => new GridSettings(columns: DefaultColumns.all);
|
||||
|
||||
[ProtoMember(1)]
|
||||
public GridSettings grid { get; }
|
||||
[ProtoMember(2)]
|
||||
public RiskManagementFilterSettings filters { get; }
|
||||
[ProtoMember(4)]
|
||||
public uint account_id { get; }
|
||||
[ProtoMember(5)]
|
||||
public SparkMenuDisplaySettings menu_display { get; }
|
||||
|
||||
public IReadOnlyList<SortingCriteria> sorting_criteria =>
|
||||
_sorting_criteria ?? Array.Empty<SortingCriteria>();
|
||||
|
||||
public RiskManagementSettings with(
|
||||
GridSettings grid = null,
|
||||
RiskManagementFilterSettings filters = default,
|
||||
IEnumerable<SortingCriteria> sorting_criteria = null,
|
||||
uint? account_id = null,
|
||||
SparkMenuDisplaySettings? menu_display = null) =>
|
||||
//
|
||||
new RiskManagementSettings(
|
||||
grid ?? this.grid,
|
||||
filters ?? this.filters,
|
||||
sorting_criteria ?? this.sorting_criteria,
|
||||
account_id ?? this.account_id,
|
||||
menu_display ?? this.menu_display);
|
||||
|
||||
public static class DefaultColumns {
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), ColumnHeaders.Security, width: ColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), ColumnHeaders.SecurityKey, width: ColumnWidths.SecurityKey, is_visible: false);
|
||||
// position
|
||||
public static GridColumnSettings max_long => new GridColumnSettings(Columns.MaxNetLongPosition.ToString(), "Max Long", width: 60, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings max_short => new GridColumnSettings(Columns.MaxNetShortPosition.ToString(), "Max Short", width: 60, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings is_position_limit_enabled => new GridColumnSettings(Columns.IsPositionLimitEnabled.ToString(), "Enforce Pos", width: 70, horizontal_alignment: HorizontalAlignment.Center);
|
||||
// clip
|
||||
public static GridColumnSettings clip_limit => new GridColumnSettings(Columns.ClipLimit.ToString(), "Clip Limit", horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings is_clip_limit_enabled => new GridColumnSettings(Columns.IsClipLimitEnabled.ToString(), "Enforce Clip", width: 70, horizontal_alignment: HorizontalAlignment.Center);
|
||||
// price
|
||||
public static GridColumnSettings min_price => new GridColumnSettings(Columns.MinPrice.ToString(), "Min Price", horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings max_price => new GridColumnSettings(Columns.MaxPrice.ToString(), "Max Price", horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings is_price_limit_enabled => new GridColumnSettings(Columns.IsPriceLimitEnabled.ToString(), "Enforce Price", width: 75, horizontal_alignment: HorizontalAlignment.Center);
|
||||
|
||||
public static IEnumerable<GridColumnSettings> all {
|
||||
get {
|
||||
yield return security;
|
||||
yield return max_long;
|
||||
yield return max_short;
|
||||
yield return is_position_limit_enabled;
|
||||
yield return clip_limit;
|
||||
yield return is_clip_limit_enabled;
|
||||
yield return min_price;
|
||||
yield return max_price;
|
||||
yield return is_price_limit_enabled;
|
||||
yield return security_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
using mbts;
|
||||
using mbts.collections;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Component = bts.shell.risk_management.RiskLimitsImportConflictsView.RiskLimitsComponent;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class RiskLimitsImportVM : DisposableViewModelBase, IRequestViewClose {
|
||||
readonly RefCountMap<ParsedRiskLimits> _conflicts = new RefCountMap<ParsedRiskLimits>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public RiskLimitsImportVM(int num_limits) {
|
||||
rows_view = CollectionViewSource.GetDefaultView(rows);
|
||||
ok_cmd = new DelegateCommand(() => import_request?.Invoke());
|
||||
cancel_cmd = new DelegateCommand(() => RequestClose?.Invoke(this, new EventArgs()));
|
||||
this.num_limits = num_limits;
|
||||
_dispose = ObservableAutoWrapper.connect_item_hooks(rows, r =>
|
||||
r.subscribe_has_error().Subscribe(has_error => {
|
||||
if (has_error) {
|
||||
if (r.affected_limits.Count(_conflicts.increment_adds_key) == 0)
|
||||
return;
|
||||
} else if (r.affected_limits.Count(_conflicts.decrement_removes_key) == 0)
|
||||
return;
|
||||
RaisePropertyChanged(nameof(num_successfully_parsed_limits));
|
||||
}));
|
||||
}
|
||||
|
||||
public event Action import_request;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public ObservableCollection<Row> rows { get; } = new ObservableCollection<Row>();
|
||||
public ICollectionView rows_view { get; }
|
||||
public int num_limits { get; }
|
||||
public int num_successfully_parsed_limits => num_limits - _conflicts.Count;
|
||||
// misc items sources
|
||||
public IEnumerable<Side> sides => new[] { Side.Buy, Side.Sell };
|
||||
public ObservableCollection<AccountTuple?> accounts { get; } = new ObservableCollection<AccountTuple?>();
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
if (!ReferenceEquals(_security_map, value)) {
|
||||
_security_map = value;
|
||||
_resolve_securities();
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
// commands
|
||||
public ICommand ok_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
|
||||
void _resolve_securities() {
|
||||
if (_security_map == null)
|
||||
return;
|
||||
var reverse_lookup = RiskLimitsParser.reverse_lookup(MappedDictionary.create(_security_map, v => v.security.ToString()));
|
||||
foreach (var r in rows) {
|
||||
if (r.component == Component.Security && r.has_error) {
|
||||
var id = reverse_lookup(r.text);
|
||||
if (id != 0 && _security_map.TryGetValue(id, out var smd))
|
||||
r.value = smd.security;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Row : ViewModelBase, ITemplateSelectorKey {
|
||||
readonly DelegateProperty<bool> _has_error;
|
||||
object _value;
|
||||
string _text;
|
||||
|
||||
public Row(Component component, string text, IReadOnlyCollection<ParsedRiskLimits> affected_limits, object error_value, object value) {
|
||||
this.component = component;
|
||||
this.text = text;
|
||||
this.affected_limits = affected_limits;
|
||||
_value = value;
|
||||
_has_error = backing(nameof(has_error), () => Equals(this.value, error_value));
|
||||
}
|
||||
|
||||
public Component component { get; }
|
||||
public string text { get => _text; set => Set(ref _text, value); }
|
||||
public IReadOnlyCollection<ParsedRiskLimits> affected_limits { get; }
|
||||
public object value {
|
||||
get => _value;
|
||||
set {
|
||||
if (Set(ref _value, value))
|
||||
_has_error.refresh();
|
||||
}
|
||||
}
|
||||
public bool has_error => _has_error.Value;
|
||||
string ITemplateSelectorKey.key => component.ToString();
|
||||
|
||||
public IObservable<bool> subscribe_has_error() => _has_error.observe_current(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using bts.admin.risk;
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class RiskLimitsModificationVM : ViewModelBase, IValueFactory<RiskLimitsModification>, IRequestViewClose {
|
||||
readonly DeferrableActionWrapper _result_changed;
|
||||
RiskLimitsModification _modifications;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_map = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public RiskLimitsModificationVM(bool can_edit_security) {
|
||||
_result_changed = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
this.can_edit_security = can_edit_security;
|
||||
commit_cmd = new DelegateCommand(commit);
|
||||
cancel_cmd = new DelegateCommand(close);
|
||||
//
|
||||
void commit() {
|
||||
request_commit?.Invoke();
|
||||
close();
|
||||
}
|
||||
void close() =>
|
||||
RequestClose?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event Action result_changed, request_commit;
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
public SecurityImmutable? security { get => _modifications.security; set => _set_limits(_modifications.with(security: value)); }
|
||||
public SecurityCriteria security_criteria {
|
||||
get => _modifications.security_criteria ?? (security.HasValue ? SecurityCriteria.from_securities(security.Value) : null);
|
||||
set => _set_limits(_modifications.with(security_criteria: value));
|
||||
}
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map {
|
||||
get => _security_map;
|
||||
set {
|
||||
_security_map = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
public bool can_edit_security { get; }
|
||||
// position
|
||||
public uint? max_long_position { get => _modifications.max_long_position; set => _set_limits(_modifications.with(max_long_position: value)); }
|
||||
public uint? max_short_position { get => _modifications.max_short_position; set => _set_limits(_modifications.with(max_short_position: value)); }
|
||||
public bool? is_position_limit_enabled { get => _modifications.is_position_limit_enabled; set => _set_limits(_modifications.with(is_position_limit_enabled: value)); }
|
||||
// clip
|
||||
public bool? is_clip_limit_enabled { get => _modifications.is_clip_limit_enabled; set => _set_limits(_modifications.with(is_clip_limit_enabled: value)); }
|
||||
public uint? clip_limit { get => _modifications.clip_limit; set => _set_limits(_modifications.with(clip_limit: value)); }
|
||||
// price
|
||||
public double? max_price { get => _modifications.max_price; set => _set_limits(_modifications.with(max_price: value)); }
|
||||
public double? min_price { get => _modifications.min_price; set => _set_limits(_modifications.with(min_price: value)); }
|
||||
public bool? is_price_limit_enabled { get => _modifications.is_price_limit_enabled; set => _set_limits(_modifications.with(is_price_limit_enabled: value)); }
|
||||
|
||||
public ICommand commit_cmd { get; }
|
||||
public ICommand cancel_cmd { get; }
|
||||
|
||||
public RiskLimitsModification create() =>
|
||||
_modifications;
|
||||
|
||||
public void load_from(RiskLimitsModification value) {
|
||||
using (_result_changed.defer_execution())
|
||||
using (detect_property_changes())
|
||||
_modifications = value;
|
||||
}
|
||||
|
||||
void _set_limits(RiskLimitsModification new_modifications, [CallerMemberName] string prop = "") =>
|
||||
_set_limits(new_modifications, new[] { prop });
|
||||
|
||||
void _set_limits(RiskLimitsModification new_modifications, params string[] invalidate_props) {
|
||||
if (!_modifications.Equals(new_modifications)) {
|
||||
_modifications = new_modifications;
|
||||
RaisePropertyChanged(invalidate_props);
|
||||
_result_changed.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct RiskLimitsModification {
|
||||
public RiskLimitsModification(
|
||||
SecurityImmutable? security = null,
|
||||
SecurityCriteria security_criteria = null,
|
||||
AccountTuple? account = null,
|
||||
uint? max_long_position = null,
|
||||
uint? max_short_position = null,
|
||||
bool? is_position_limit_enabled = null,
|
||||
uint? clip_limit = null,
|
||||
bool? is_clip_limit_enabled = null,
|
||||
double? min_price = null,
|
||||
double? max_price = null,
|
||||
bool? is_price_limit_enabled = null) {
|
||||
//
|
||||
this.security = security;
|
||||
this.security_criteria = security_criteria;
|
||||
this.account = account;
|
||||
this.max_long_position = max_long_position;
|
||||
this.max_short_position = max_short_position;
|
||||
this.is_position_limit_enabled = is_position_limit_enabled;
|
||||
this.clip_limit = clip_limit;
|
||||
this.is_clip_limit_enabled = is_clip_limit_enabled;
|
||||
this.min_price = min_price;
|
||||
this.max_price = max_price;
|
||||
this.is_price_limit_enabled = is_price_limit_enabled;
|
||||
}
|
||||
|
||||
public SecurityImmutable? security { get; }
|
||||
public SecurityCriteria security_criteria { get; }
|
||||
public AccountTuple? account { get; }
|
||||
// position
|
||||
public uint? max_long_position { get; }
|
||||
public uint? max_short_position { get; }
|
||||
public bool? is_position_limit_enabled { get; }
|
||||
// clip
|
||||
public uint? clip_limit { get; }
|
||||
public bool? is_clip_limit_enabled { get; }
|
||||
// price
|
||||
public double? min_price { get; }
|
||||
public double? max_price { get; }
|
||||
public bool? is_price_limit_enabled { get; }
|
||||
|
||||
public static RiskLimitsModification aggregate_common_props(IEnumerable<AccountSecurityRiskLimits> limit_items) =>
|
||||
new RiskLimitsModification(
|
||||
_common_value(limit_items, l => l.security, s => s),
|
||||
null,
|
||||
_common_value(limit_items, l => new AccountTuple(l.account_key, l.account_name)),
|
||||
_common_value(limit_items, l => l.limits.max_long_position),
|
||||
_common_value(limit_items, l => l.limits.max_short_position),
|
||||
_common_value(limit_items, l => l.limits.is_position_limit_enabled),
|
||||
_common_value(limit_items, l => l.limits.clip_limit),
|
||||
_common_value(limit_items, l => l.limits.is_clip_limit_enabled),
|
||||
_common_value(limit_items, l => l.limits.min_price),
|
||||
_common_value(limit_items, l => l.limits.max_price),
|
||||
_common_value(limit_items, l => l.limits.is_price_limit_enabled));
|
||||
|
||||
public RiskLimits apply(RiskLimits target) =>
|
||||
new RiskLimits(
|
||||
is_position_limit_enabled: is_position_limit_enabled ?? target.is_position_limit_enabled,
|
||||
max_long_position: max_long_position ?? target.max_long_position,
|
||||
max_short_position: max_short_position ?? target.max_short_position,
|
||||
is_clip_limit_enabled: is_clip_limit_enabled ?? target.is_clip_limit_enabled,
|
||||
clip_limit: clip_limit ?? target.clip_limit,
|
||||
is_price_limit_enabled: is_price_limit_enabled ?? target.is_price_limit_enabled,
|
||||
min_price: min_price ?? target.min_price,
|
||||
max_price: max_price ?? target.max_price);
|
||||
|
||||
public RiskLimitsModification with(RiskLimitsModification other_mod) =>
|
||||
with(
|
||||
other_mod.security.HasValue ? other_mod.security.Value : security,
|
||||
other_mod.account.HasValue ? other_mod.account.Value : account,
|
||||
security_criteria: other_mod.security_criteria ?? security_criteria,
|
||||
max_long_position: other_mod.max_long_position ?? max_long_position,
|
||||
max_short_position: other_mod.max_short_position ?? max_short_position,
|
||||
is_position_limit_enabled: other_mod.is_position_limit_enabled ?? is_position_limit_enabled,
|
||||
clip_limit: other_mod.clip_limit ?? clip_limit,
|
||||
is_clip_limit_enabled: other_mod.is_clip_limit_enabled ?? is_clip_limit_enabled,
|
||||
min_price: other_mod.min_price ?? min_price,
|
||||
max_price: other_mod.max_price ?? max_price,
|
||||
is_price_limit_enabled: other_mod.is_price_limit_enabled ?? is_price_limit_enabled);
|
||||
//new RiskLimitsModification(security: other_mod.security ?? security,
|
||||
// security_criteria: other_mod.security ?? security);
|
||||
|
||||
public RiskLimitsModification with(
|
||||
OptionalNullable<SecurityImmutable>? security = null,
|
||||
OptionalNullable<AccountTuple>? account = null,
|
||||
SecurityCriteria security_criteria = null,
|
||||
OptionalNullable<uint>? max_long_position = null,
|
||||
OptionalNullable<uint>? max_short_position = null,
|
||||
OptionalNullable<bool>? is_position_limit_enabled = null,
|
||||
OptionalNullable<uint>? clip_limit = null,
|
||||
OptionalNullable<bool>? is_clip_limit_enabled = null,
|
||||
OptionalNullable<double>? min_price = null,
|
||||
OptionalNullable<double>? max_price = null,
|
||||
OptionalNullable<bool>? is_price_limit_enabled = null) =>
|
||||
//
|
||||
new RiskLimitsModification(
|
||||
security.HasValue ? security.Value.value : this.security,
|
||||
security_criteria ?? this.security_criteria,
|
||||
account.HasValue ? account.Value.value : this.account,
|
||||
max_long_position.HasValue ? max_long_position.Value.value : this.max_long_position,
|
||||
max_short_position.HasValue ? max_short_position.Value.value : this.max_short_position,
|
||||
is_position_limit_enabled.HasValue ? is_position_limit_enabled.Value.value : this.is_position_limit_enabled,
|
||||
clip_limit.HasValue ? clip_limit.Value.value : this.clip_limit,
|
||||
is_clip_limit_enabled.HasValue ? is_clip_limit_enabled.Value.value : this.is_clip_limit_enabled,
|
||||
min_price.HasValue ? min_price.Value.value : this.min_price,
|
||||
max_price.HasValue ? max_price.Value.value : this.max_price,
|
||||
is_price_limit_enabled.HasValue ? is_price_limit_enabled.Value.value : this.is_price_limit_enabled);
|
||||
|
||||
static T? _common_value<T>(IEnumerable<AccountSecurityRiskLimits> source, Func<AccountSecurityRiskLimits, T> get) where T : struct =>
|
||||
_common_value(source, get, common => new T?(common));
|
||||
|
||||
static TResult _common_value<T, TResult>(IEnumerable<AccountSecurityRiskLimits> source, Func<AccountSecurityRiskLimits, T> get, Func<T, TResult> convert_common, TResult default_value = default) =>
|
||||
source.Select(get).Distinct().TryGetSingle(out var common) ? convert_common(common) : default_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using bts.admin.risk;
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class RiskLimitsVM : ViewModelBase, IEquatable<RiskLimitsVM> {
|
||||
RiskLimitsVM(AccountSecurityKey id) =>
|
||||
this.id = id;
|
||||
|
||||
public event Action request_commit;
|
||||
|
||||
/// The user can modify some props directly in the grid,
|
||||
/// which may or may not give the row a new sort-position.
|
||||
/// For this reason, the owner-viewmodel (<see cref="RiskManagementVM"/>)
|
||||
/// is notified both immediately before, and after the changes are reflected in <see cref="data"/>.
|
||||
/// By responding to those events, the owner-viewmodel can safely process any
|
||||
/// sorting-changes without breaking binary search functionality.
|
||||
/// <see cref="value_changing"/>, <see cref="value_changed"/>
|
||||
|
||||
/// <summary>
|
||||
/// This event is raised just before a new value is assigned to <see cref="data"/>.
|
||||
/// </summary>
|
||||
public event Action<(AccountSecurityRiskLimits old_value, AccountSecurityRiskLimits new_value)> value_changing;
|
||||
/// <summary>
|
||||
/// This event is raised immediately after a new value is assigned to <see cref="data"/>.
|
||||
/// </summary>
|
||||
public event Action<(AccountSecurityRiskLimits old_value, AccountSecurityRiskLimits new_value)> value_changed;
|
||||
public event Action is_dirty_changed;
|
||||
|
||||
public AccountSecurityKey id { get; }
|
||||
public SecurityImmutable? security => data.security;
|
||||
public string security_text => is_persisted ? security.ToString() : $"*{security}";
|
||||
public uint security_key => data.security_key;
|
||||
public string account_name => data.account_name;
|
||||
public uint account_key => data.key.account;
|
||||
// position limit
|
||||
public bool is_position_limit_enabled { get => _limits.is_position_limit_enabled; set => _modify(new RiskLimitsModification(is_position_limit_enabled: value)); }
|
||||
public uint max_long_position { get => _limits.max_long_position; set => _modify(new RiskLimitsModification(max_long_position: value)); }
|
||||
public uint max_short_position { get => _limits.max_short_position; set => _modify(new RiskLimitsModification(max_short_position: value)); }
|
||||
// clip limit
|
||||
public bool is_clip_limit_enabled { get => _limits.is_clip_limit_enabled; set => _modify(new RiskLimitsModification(is_clip_limit_enabled: value)); }
|
||||
public uint clip_limit { get => _limits.clip_limit; set => _modify(new RiskLimitsModification(clip_limit: value)); }
|
||||
// price limit
|
||||
public bool is_price_limit_enabled { get => _limits.is_price_limit_enabled; set => _modify(new RiskLimitsModification(is_price_limit_enabled: value)); }
|
||||
public double min_price { get => _limits.min_price; set => _modify(new RiskLimitsModification(min_price: value)); }
|
||||
public double max_price { get => _limits.max_price; set => _modify(new RiskLimitsModification(max_price: value)); }
|
||||
|
||||
public bool is_persisted => committed_data.HasValue;
|
||||
public bool is_dirty => !committed_data.HasValue || !committed_data.Value.Equals(data);
|
||||
|
||||
public AccountSecurityRiskLimits data { get; private set; }
|
||||
public AccountSecurityRiskLimits? committed_data { get; private set; }
|
||||
RiskLimits _limits => data.limits;
|
||||
|
||||
public static RiskLimitsVM create_placeholder(AccountSecurityRiskLimits state) {
|
||||
var vm = new RiskLimitsVM(state.key);
|
||||
vm.load(state, false);
|
||||
return vm;
|
||||
}
|
||||
|
||||
public static RiskLimitsVM create_persisted(AccountSecurityRiskLimits state) {
|
||||
var vm = new RiskLimitsVM(state.key);
|
||||
vm.load(state, true);
|
||||
return vm;
|
||||
}
|
||||
|
||||
public void load(AccountSecurityRiskLimits new_state, bool is_committed) {
|
||||
using (detect_property_changes()) {
|
||||
bool was_dirty = is_dirty;
|
||||
if (is_committed)
|
||||
committed_data = new_state;
|
||||
if (!data.Equals(new_state)) {
|
||||
value_changing?.Invoke((data, new_state));
|
||||
var orig_state = data;
|
||||
data = new_state;
|
||||
value_changed?.Invoke((orig_state, new_state));
|
||||
}
|
||||
if (is_dirty != was_dirty)
|
||||
is_dirty_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(RiskLimitsVM other) =>
|
||||
other != null && id == other.id;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as RiskLimitsVM);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
id.GetHashCode();
|
||||
|
||||
void _modify(RiskLimitsModification mod) {
|
||||
var new_value = data.with(limits: mod.apply(data.limits));
|
||||
if (committed_data.HasValue) {
|
||||
// if the user just toggled one of the limits on/off, request an immediate update to persist the change
|
||||
// (the on/off toggles are the only props that can be edited directly in the grid,
|
||||
// the user might find it annoying if they have to click 'Save' just to turn the limits on/off.
|
||||
var clims = committed_data.Value.limits;
|
||||
load(new_value, false);
|
||||
var revert_mod = new RiskLimitsModification(is_position_limit_enabled: clims.is_position_limit_enabled, is_clip_limit_enabled: clims.is_clip_limit_enabled, is_price_limit_enabled: clims.is_price_limit_enabled);
|
||||
var reverted_lims = new_value = new_value.with(limits: revert_mod.apply(new_value.limits));
|
||||
if (reverted_lims.limits.Equals(clims)) {
|
||||
// since only the limit toggle props were reverted,
|
||||
// the above equality indicates that only the limit toggle props were modified.
|
||||
request_commit?.Invoke();
|
||||
}
|
||||
} else {
|
||||
load(new_value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
using bts.admin.risk;
|
||||
using bts.api.core;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Controls;
|
||||
using Columns = bts.shell.risk_management.RiskManagementControl.Columns;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
class RiskManagementColumnModel : DIYGridColumnModel<Columns, AccountSecurityRiskLimits, RiskManagementFilterSettings> {
|
||||
public RiskManagementColumnModel(Columns id, IComparer<AccountSecurityRiskLimits> data_comparer, IDataGridColumnFilter<AccountSecurityRiskLimits> filter, string filter_template, Func<RiskManagementFilterSettings, object> get_column_filter_settings, Func<RiskManagementFilterSettings, object, RiskManagementFilterSettings> set_column_filter_settings)
|
||||
: base(id, data_comparer, filter, filter_template, get_column_filter_settings, set_column_filter_settings) {
|
||||
}
|
||||
}
|
||||
|
||||
class RiskManagementVM : DisposableViewModelBase, IHandleGridSorting, IHandleRowSelection, IValueFactory<RiskManagementSettings> {
|
||||
static readonly IComparer<AccountSecurityRiskLimits> _data_row_id_comparer =
|
||||
new ComparerBuilder<AccountSecurityKey>(new[] {
|
||||
Comparer<uint>.Default.wrap<uint, AccountSecurityKey>(ask => ask.account),
|
||||
Comparer<uint>.Default.wrap<uint, AccountSecurityKey>(ask => ask.security)
|
||||
}).to_comparer()
|
||||
.wrap<AccountSecurityKey, AccountSecurityRiskLimits>(r => r.key);
|
||||
|
||||
static IComparer<RiskLimitsVM> _row_id_comparer =>
|
||||
_data_row_id_comparer.wrap<AccountSecurityRiskLimits, RiskLimitsVM>(r => r.data);
|
||||
|
||||
readonly DeferrableActionWrapper _refresh_filter_changed, _selected_rows_changed, _dirty_rows_changed;
|
||||
readonly ColumnSortManager _sort_manager;
|
||||
readonly ObservableCollection<RiskManagementColumnModel> _columns = new ObservableCollection<RiskManagementColumnModel>();
|
||||
readonly Dictionary<Columns, RiskManagementColumnModel> _column_map = new Dictionary<Columns, RiskManagementColumnModel>();
|
||||
readonly SelectorVM<AccountTuple?, uint> _accounts_vm = new SelectorVM<AccountTuple?, uint>(acc => acc?.id ?? 0) { comparer = Comparer<AccountTuple>.Default.to_nullable() };
|
||||
IList<AccountSecurityRiskLimits> _data_rows;
|
||||
ImmutableSortedSet<RiskLimitsVM> _selected_rows = ImmutableSortedSet.Create(_row_id_comparer);
|
||||
IComparer<AccountSecurityRiskLimits> _data_comparer = _data_row_id_comparer;
|
||||
IComparer<RiskLimitsVM> _row_comparer;
|
||||
IDisposable _import_limits_op;
|
||||
ImmutableDictionary<AccountSecurityKey, RiskLimitsVM> _dirty_rows = ImmutableDictionary<AccountSecurityKey, RiskLimitsVM>.Empty;
|
||||
Dictionary<AccountSecurityKey, RiskLimitsVM> _imported_row_map = new Dictionary<AccountSecurityKey, RiskLimitsVM>();
|
||||
Dictionary<AccountSecurityKey, RiskLimitsVM> _row_cache = new Dictionary<AccountSecurityKey, RiskLimitsVM>();
|
||||
|
||||
public RiskManagementVM(IObservable<TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey>> data_source, IObservable<IReadOnlyList<AccountTuple>> accounts_source) {
|
||||
_selected_rows_changed = new DeferrableActionWrapper(() => selected_rows_changed?.Invoke());
|
||||
_refresh_filter_changed = new DeferrableActionWrapper(() => _filter_changed?.Invoke(_mk_filter()));
|
||||
_dirty_rows_changed = new DeferrableActionWrapper(() => dirty_rows_changed?.Invoke(dirty_rows));
|
||||
_sort_manager = new ColumnSortManager(_columns.OfType<ISortableDIYGridColumn>(), null);
|
||||
_sort_manager.active_sorting_changed += () =>
|
||||
data_comparer = _sort_manager.BuildComparer(id => _find_column(id)?.row_comparer, _data_comparer);
|
||||
_data_rows = rows.WrapSelector(r => r.data, null);
|
||||
_accounts_vm.selected_item_changed += () => {
|
||||
RaisePropertyChanged(nameof(selected_account));
|
||||
_refresh_filter_changed.execute();
|
||||
};
|
||||
column_map = FunctionalIndex.create<string, RiskManagementColumnModel>(_find_column);
|
||||
grid_settings_manager = new SimpleGridSettingsManagerVM<Columns>(
|
||||
_columns, null, ordered_cols => {
|
||||
CollectionHelper.sync_with_never_duplicate(_columns, ordered_cols, cs => {
|
||||
throw new InvalidOperationException();
|
||||
}, c => c.id.ToString(), cs => cs.id);
|
||||
}, null);
|
||||
/////////////////
|
||||
// Columns
|
||||
/////////////////
|
||||
// security
|
||||
mk_column_model(
|
||||
Columns.Security,
|
||||
wrap_comp(r => r.security),
|
||||
new RegexFilterVM<AccountSecurityRiskLimits>(r => r.security.ToString(), null),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
fs => fs.security_regex,
|
||||
(fs, v) => fs.with(security_regex: v));
|
||||
// security key
|
||||
mk_column_model(
|
||||
Columns.SecurityKey,
|
||||
wrap_comp(r => r.security_key),
|
||||
new RegexFilterVM<AccountSecurityRiskLimits>(r => r.security_key.ToString(), null),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
fs => fs.security_key_regex,
|
||||
(fs, v) => fs.with(security_key_regex: v));
|
||||
// max long
|
||||
mk_column_model(
|
||||
Columns.MaxNetLongPosition,
|
||||
wrap_comp(r => r.limits.max_long_position),
|
||||
new UIntMinMax<AccountSecurityRiskLimits>(
|
||||
r => r.limits.max_long_position,
|
||||
null),
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
fs => fs.max_long_position,
|
||||
(fs, v) => fs.with(max_long_position: v));
|
||||
// max short
|
||||
mk_column_model(
|
||||
Columns.MaxNetShortPosition,
|
||||
wrap_comp(r => r.limits.max_short_position),
|
||||
new UIntMinMax<AccountSecurityRiskLimits>(
|
||||
r => r.limits.max_short_position,
|
||||
null),
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
fs => fs.max_short_position,
|
||||
(fs, v) => fs.with(max_short_position: v));
|
||||
// is position limit enabled
|
||||
mk_is_enabled_column(
|
||||
Columns.IsPositionLimitEnabled,
|
||||
r => r.limits.is_position_limit_enabled,
|
||||
(fs, yes_no_items) => fs.with(is_position_limit_enabled: RiskManagementFilterSettings.yes_no_filter_state(yes_no_items)),
|
||||
nameof(RiskLimitsVM.is_position_limit_enabled));
|
||||
// clip size
|
||||
mk_column_model(
|
||||
Columns.ClipLimit,
|
||||
wrap_comp(r => r.limits.clip_limit),
|
||||
new UIntMinMax<AccountSecurityRiskLimits>(r => r.limits.clip_limit, null),
|
||||
DataGridFilterKnownTemplates.UIntMinMax,
|
||||
fs => fs.clip_limit,
|
||||
(fs, v) => fs.with(clip_limit: v));
|
||||
// is clip limit enabled
|
||||
mk_is_enabled_column(
|
||||
Columns.IsClipLimitEnabled,
|
||||
r => r.limits.is_clip_limit_enabled,
|
||||
(fs, yes_no_items) => fs.with(is_clip_limit_enabled: RiskManagementFilterSettings.yes_no_filter_state(yes_no_items)),
|
||||
nameof(RiskLimitsVM.is_clip_limit_enabled));
|
||||
// min price
|
||||
mk_column_model(
|
||||
Columns.MinPrice,
|
||||
wrap_comp(r => r.limits.min_price),
|
||||
new DoubleMinMax<AccountSecurityRiskLimits>(r => r.limits.min_price, nameof(RiskLimitsVM.min_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
fs => fs.min_price,
|
||||
(fs, v) => fs.with(min_price: v));
|
||||
// max price
|
||||
mk_column_model(
|
||||
Columns.MaxPrice,
|
||||
wrap_comp(r => r.limits.max_price),
|
||||
new DoubleMinMax<AccountSecurityRiskLimits>(r => r.limits.max_price, nameof(RiskLimitsVM.max_price)),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
fs => fs.max_price,
|
||||
(fs, v) => fs.with(max_price: v));
|
||||
// is price limit enabled
|
||||
mk_is_enabled_column(
|
||||
Columns.IsPriceLimitEnabled,
|
||||
r => r.limits.is_price_limit_enabled,
|
||||
(fs, yes_no_items) => fs.with(is_price_limit_enabled: RiskManagementFilterSettings.yes_no_filter_state(yes_no_items)),
|
||||
nameof(RiskLimitsVM.is_price_limit_enabled));
|
||||
|
||||
data_comparer = new ComparerBuilder<AccountSecurityRiskLimits>(new[] {
|
||||
wrap_comp(r => r.security),
|
||||
_data_row_id_comparer
|
||||
}).to_comparer();
|
||||
_column_map = _columns.ToDictionary(c => c.id);
|
||||
columns = ObservableAutoWrapper.SelectObservable(_columns, c => c);
|
||||
|
||||
var filter_source =
|
||||
Observable.FromEvent<Func<AccountSecurityRiskLimits, bool>>(cb => _filter_changed += cb, cb => _filter_changed -= cb)
|
||||
.StartWith(_mk_filter);
|
||||
var filtered_data_source =
|
||||
data_source.Filter(filter_source, AccountSecurityRiskLimitsDataHelper.key_fn);
|
||||
|
||||
var filter_hooks = _columns.Select(c => {
|
||||
var f = c.filter;
|
||||
f.filter_changed += _refresh_filter_changed.execute;
|
||||
return Disposable.Create(() => f.filter_changed -= _refresh_filter_changed.execute);
|
||||
}).ToList();
|
||||
_accounts_vm.selected_item_changed += on_selected_account_changed;
|
||||
_dispose = Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(rows, subscribe_row_changes),
|
||||
accounts_source.OnDispatcher().Subscribe(on_accounts),
|
||||
Disposable.wrap_collection(filter_hooks),
|
||||
filtered_data_source.OnDispatcher().Subscribe(on_data));
|
||||
//////////////////
|
||||
// local methods
|
||||
//////////////////
|
||||
IComparer<AccountSecurityRiskLimits> wrap_comp<T>(Func<AccountSecurityRiskLimits, T> get_value) =>
|
||||
Comparer<T>.Default.wrap<T, AccountSecurityRiskLimits>(get_value);
|
||||
|
||||
RiskManagementColumnModel mk_column_model<T>(
|
||||
Columns id,
|
||||
IComparer<AccountSecurityRiskLimits> row_comp,
|
||||
IDataGridColumnPersistentFilter<AccountSecurityRiskLimits, T> filter,
|
||||
string filter_template,
|
||||
Func<RiskManagementFilterSettings, T> get_filter_settings,
|
||||
Func<RiskManagementFilterSettings, T, RiskManagementFilterSettings> set_filter_settings) {
|
||||
//
|
||||
var col = new RiskManagementColumnModel(id, row_comp,
|
||||
filter,
|
||||
filter_template,
|
||||
fs => get_filter_settings(fs),
|
||||
(fs, val) => val is T cfs ? set_filter_settings(fs, cfs) : fs);
|
||||
_columns.Add(col);
|
||||
return col;
|
||||
}
|
||||
|
||||
// the 'is enabled' columns are special cases, bc their filters produces an array of bools
|
||||
// (cheap way to reuse the discrete values filter template for true/false items),
|
||||
// but the filter protobuf has a nullable<bool>, so an extra conversion must be done explicitly (hence the get_row_val param).
|
||||
RiskManagementColumnModel mk_is_enabled_column(Columns id, Func<AccountSecurityRiskLimits, bool> get_row_val, Func<RiskManagementFilterSettings, IEnumerable<bool>, RiskManagementFilterSettings> set_filter_prop, string prop) {
|
||||
var col = new RiskManagementColumnModel(
|
||||
id,
|
||||
wrap_comp(get_row_val),
|
||||
DiscreteValuesFilterVM.Create(new[] { true, false }, get_row_val, prop),
|
||||
DataGridFilterKnownTemplates.YesNo,
|
||||
fs => RiskManagementFilterSettings.yes_no_filter_items(fs.is_position_limit_enabled),
|
||||
(fs, fstate) => set_filter_prop(fs, fstate as IEnumerable<bool>));
|
||||
_columns.Add(col);
|
||||
return col;
|
||||
}
|
||||
|
||||
IDisposable subscribe_row_changes(RiskLimitsVM row) {
|
||||
row.value_changing += on_value_changing;
|
||||
row.value_changed += on_value_changed;
|
||||
row.request_commit += on_request_commit;
|
||||
check_dirty();
|
||||
return Disposable.Create(() => {
|
||||
row.value_changing -= on_value_changing;
|
||||
row.value_changed -= on_value_changed;
|
||||
row.request_commit -= on_request_commit;
|
||||
});
|
||||
|
||||
void on_value_changing((AccountSecurityRiskLimits old_value, AccountSecurityRiskLimits new_value) change) =>
|
||||
on_row_data_changed(new Change<AccountSecurityRiskLimits>(change.old_value, change.new_value), false);
|
||||
|
||||
void on_value_changed((AccountSecurityRiskLimits old_value, AccountSecurityRiskLimits new_value) change) =>
|
||||
check_dirty();
|
||||
|
||||
void on_request_commit() =>
|
||||
request_update_limit?.Invoke(row.data);
|
||||
|
||||
void check_dirty() {
|
||||
int dr_count = _dirty_rows.Count;
|
||||
_dirty_rows = row.is_dirty ? _dirty_rows.Add(row.id, row) : _dirty_rows.Remove(row.id);
|
||||
if (dr_count != _dirty_rows.Count)
|
||||
_dirty_rows_changed.execute();
|
||||
}
|
||||
}
|
||||
|
||||
void on_accounts(IReadOnlyList<AccountTuple> new_accounts) =>
|
||||
_accounts_vm.update_items(new_accounts.Cast<AccountTuple?>());
|
||||
|
||||
void on_data(TableUpdate<AccountSecurityRiskLimits, AccountSecurityKey> table) {
|
||||
using (_dirty_rows_changed.defer_execution())
|
||||
using (_selected_rows_changed.defer_execution()) {
|
||||
foreach (var lim in table.removed) {
|
||||
if (_row_cache.TryGetValue(lim.key, out var row)) {
|
||||
_data_rows.RemoveSorted(row.data, data_comparer);
|
||||
if (row.committed_data.HasValue)
|
||||
_row_cache.Remove(lim.key);
|
||||
}
|
||||
}
|
||||
foreach (var ch in table.changes) {
|
||||
on_row_data_changed(ch, true);
|
||||
}
|
||||
foreach (var lim in table.added) {
|
||||
if (_row_cache.TryGetValue(lim.key, out var row)) {
|
||||
// if row is already here, it must be an import
|
||||
// overwrite the import with the persisted row (this probably won't happen in practice)
|
||||
rows.RemoveSorted(row, row_comparer);
|
||||
_imported_row_map.Remove(lim.key);
|
||||
}
|
||||
var new_row = RiskLimitsVM.create_persisted(lim);
|
||||
_row_cache[lim.key] = new_row;
|
||||
rows.InsertSorted(new_row, row_comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_row_data_changed(Change<AccountSecurityRiskLimits> change, bool is_persisted) {
|
||||
Log.assert(_row_cache.TryGetValue(change.Old.key, out var row));
|
||||
int old_index = CollectionHelper.binary_search(rows, row, row_comparer);
|
||||
Log.assert(old_index >= 0);
|
||||
int new_index = CollectionHelper.binary_search(_data_rows, change.New, _data_comparer);
|
||||
// if the change came from the server (i.e., a persisted change that the row-vm doesn't already know about),
|
||||
// load the new data (current modifications will be overwritten).
|
||||
// Otherwise, the change is presumably the result of an uncommitted user-edit, which the row must already know about.
|
||||
if (is_persisted) {
|
||||
row.load(change.New, true);
|
||||
if (_dirty_rows.Remove(row.id, out _dirty_rows))
|
||||
_dirty_rows_changed.execute();
|
||||
}
|
||||
if (new_index < 0)
|
||||
new_index = ~new_index;
|
||||
if (Math.Abs(new_index - old_index) > 1) {
|
||||
if (new_index > old_index)
|
||||
new_index--;
|
||||
rows.Move(old_index, new_index);
|
||||
}
|
||||
}
|
||||
|
||||
void on_selected_account_changed() {
|
||||
foreach (var ir in _imported_row_map.Values) {
|
||||
if (ir.account_key == selected_account?.id) {
|
||||
rows.InsertOrReplaceSorted(ir, row_comparer);
|
||||
} else {
|
||||
rows.RemoveSorted(ir, row_comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action selected_rows_changed;
|
||||
public event Action<AccountSecurityRiskLimits> request_update_limit;
|
||||
public event Action<ImmutableDictionary<AccountSecurityKey, RiskLimitsVM>> dirty_rows_changed;
|
||||
event Action<Func<AccountSecurityRiskLimits, bool>> _filter_changed;
|
||||
|
||||
public IReadOnlyIndex<string, RiskManagementColumnModel> column_map { get; }
|
||||
public bool has_unpersisted_limits => false;
|
||||
// this collection is needed to control the column order
|
||||
public IReadOnlyObservableCollection<IDIYGridColumn> columns { get; }
|
||||
public ObservableCollection<RiskLimitsVM> rows { get; } = new ObservableCollection<RiskLimitsVM>();
|
||||
/// <summary>
|
||||
/// The selected rows are provided as an <see cref="ImmutableSortedSet{T}"/> so they can be observed/accessed on different threads.
|
||||
/// <see cref="RiskManagementVMExtensions.subscribe_selected_rows(RiskManagementVM)"/>.
|
||||
/// </summary>
|
||||
public ImmutableSortedSet<RiskLimitsVM> selected_rows => _selected_rows;
|
||||
/// <summary>
|
||||
/// The dirty rows are provided as an <see cref="ImmutableDictionary{TKey, TValue}"/> so they can be observed/accessed on different threads.
|
||||
/// <see cref="RiskManagementVMExtensions.subscribe_has_dirty_rows(RiskManagementVM)(RiskManagementVM)"/>.
|
||||
/// </summary>
|
||||
public ImmutableDictionary<AccountSecurityKey, RiskLimitsVM> dirty_rows => _dirty_rows;
|
||||
public IReadOnlyObservableCollection<AccountTuple?> accounts => _accounts_vm.items;
|
||||
public AccountTuple? selected_account {
|
||||
get => _accounts_vm.selected_item ?? default;
|
||||
set => _accounts_vm.selected_item = value;
|
||||
}
|
||||
public SimpleGridSettingsManagerVM<Columns> grid_settings_manager { get; }
|
||||
public IComparer<AccountSecurityRiskLimits> data_comparer {
|
||||
get => _data_comparer;
|
||||
private set {
|
||||
if (value != null) {
|
||||
_data_comparer = value;
|
||||
_row_comparer = null;
|
||||
rows.sync_with(rows.Ordered(row_comparer));
|
||||
_selected_rows = _selected_rows.WithComparer(row_comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IComparer<RiskLimitsVM> row_comparer =>
|
||||
_row_comparer ?? (_row_comparer = data_comparer?.wrap<AccountSecurityRiskLimits, RiskLimitsVM>(r => r.data));
|
||||
|
||||
public IReadOnlyDictionary<uint, SecurityMD> security_map { get; set; } = DictionaryHelper.empty<uint, SecurityMD>();
|
||||
|
||||
public IReadOnlyDictionary<uint, IEnumerable<AccountSecurityRiskLimits>> build_account_limits_map() =>
|
||||
_data_rows.GroupBy(dr => dr.account_key)
|
||||
.ToDictionary(g => g.Key, g => g.AsEnumerable());
|
||||
|
||||
public RiskManagementSettings create() {
|
||||
var grid = grid_settings_manager.create();
|
||||
var filters = _columns.Aggregate(new RiskManagementFilterSettings(), (fs, c) => c.assign_filter_settings(fs));
|
||||
return new RiskManagementSettings(grid, filters, _sort_manager.SortingCriteria, selected_account?.id ?? 0);
|
||||
}
|
||||
|
||||
public void load_from(RiskManagementSettings settings) {
|
||||
if (settings is null)
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
if (settings.grid != null)
|
||||
grid_settings_manager.load_from(settings.grid);
|
||||
_sort_manager.SortingCriteria = settings.sorting_criteria;
|
||||
using (_refresh_filter_changed.defer_execution()) {
|
||||
foreach (var col in _columns)
|
||||
col.load_from(null, settings.filters);
|
||||
_accounts_vm.try_select(settings.account_id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void load_from(GridSettings grid) {
|
||||
if (grid is null)
|
||||
throw new ArgumentNullException(nameof(grid));
|
||||
grid_settings_manager.load_from(grid);
|
||||
}
|
||||
|
||||
public void modify_rows(IEnumerable<AccountSecurityKey> targets, RiskLimitsModification mod) {
|
||||
foreach (var key in targets) {
|
||||
if (_row_cache.TryGetValue(key, out var row)) {
|
||||
var new_data = row.data.with(limits: mod.apply(row.data.limits));
|
||||
row.load(new_data, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void revert(IEnumerable<RiskLimitsVM> targets) {
|
||||
foreach (var row in targets) {
|
||||
if (row.committed_data.HasValue)
|
||||
row.load(row.committed_data.Value, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void import_limits(string file_name) {
|
||||
Disposable.dispose(ref _import_limits_op);
|
||||
_import_limits_op = RiskLimitsImporter.import_limits(file_name,
|
||||
security_map,
|
||||
accounts.Where(a => a.HasValue)
|
||||
.ToDictionary(a => a.Value.id, a => a.Value.name),
|
||||
add_imported_rows);
|
||||
|
||||
void add_imported_rows(IEnumerable<ParsedRiskLimits> imported_rows) {
|
||||
foreach (var ir in imported_rows) {
|
||||
var res = ir.result;
|
||||
if (!res.security.HasValue && security_map.TryGetValue(res.security_key, out var sec_md))
|
||||
res = res.with(security: sec_md.security);
|
||||
if (string.IsNullOrEmpty(res.account_name) && accounts.TryGetFirst(a => a?.id == res.account_key, out var acc))
|
||||
res = res.with(account: acc);
|
||||
if (_row_cache.TryGetValue(res.key, out var old_row))
|
||||
rows.RemoveSorted(old_row, row_comparer);
|
||||
var new_row = RiskLimitsVM.create_placeholder(res);
|
||||
_row_cache[res.key] = new_row;
|
||||
_imported_row_map[res.key] = new_row;
|
||||
if (new_row.account_key == selected_account?.id)
|
||||
rows.InsertSorted(new_row, row_comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IHandleRowSelection.row_selection_changed(SelectionChangedEventArgs e) {
|
||||
var bd = _selected_rows.ToBuilder();
|
||||
RowSelectionHelper.apply_changes(bd, e);
|
||||
_selected_rows = bd.ToImmutable();
|
||||
_selected_rows_changed.execute();
|
||||
}
|
||||
|
||||
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
_sort_manager.OnColumnClick(e.Column, KeyboardExtensions.IsShiftDown());
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
RiskManagementColumnModel _find_column(string key_str) =>
|
||||
Enum.TryParse(key_str, true, out Columns key) ? _find_column(key) : null;
|
||||
|
||||
RiskManagementColumnModel _find_column(Columns key) =>
|
||||
_column_map.GetValueOrDefault(key);
|
||||
|
||||
Func<AccountSecurityRiskLimits, bool> _mk_filter() {
|
||||
var col_filter_pred = create().filters.build_predicate();
|
||||
if (!selected_account.HasValue)
|
||||
return lim => false;
|
||||
var acc_id = selected_account.Value.id;
|
||||
return lim => lim.account_key == acc_id && (col_filter_pred is null || col_filter_pred(lim));
|
||||
}
|
||||
}
|
||||
|
||||
static class RiskManagementVMExtensions {
|
||||
public static IObservable<ImmutableSortedSet<RiskLimitsVM>> subscribe_selected_rows(this RiskManagementVM vm) =>
|
||||
vm is null ?
|
||||
throw new ArgumentNullException(nameof(vm)) :
|
||||
Observable.Create<ImmutableSortedSet<RiskLimitsVM>>(o => {
|
||||
o.OnNext(vm.selected_rows);
|
||||
vm.selected_rows_changed += on_selected_rows_changed;
|
||||
return Disposable.Create(() => vm.selected_rows_changed -= on_selected_rows_changed);
|
||||
void on_selected_rows_changed() =>
|
||||
o.OnNext(vm.selected_rows);
|
||||
})
|
||||
.SubscribeOnDispatcher();
|
||||
|
||||
public static IObservable<bool> subscribe_has_dirty_rows(this RiskManagementVM vm) =>
|
||||
vm is null ?
|
||||
throw new ArgumentNullException(nameof(vm)) :
|
||||
Observable.Create<bool>(o => {
|
||||
bool last_fired = vm.dirty_rows.Count > 0;
|
||||
o.OnNext(last_fired);
|
||||
vm.dirty_rows_changed += on_dirty_rows_changed;
|
||||
return Disposable.Create(() => vm.dirty_rows_changed -= on_dirty_rows_changed);
|
||||
void on_dirty_rows_changed(ImmutableDictionary<AccountSecurityKey, RiskLimitsVM> drs) {
|
||||
if (last_fired != drs.Count > 0)
|
||||
o.OnNext(last_fired = !last_fired);
|
||||
}
|
||||
})
|
||||
.SubscribeOnDispatcher();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<Window
|
||||
x:Class="bts.shell.risk_management.RiskLimitParametersDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:risk="clr-namespace:bts.shell.risk_management"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.AllowResize="False"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
Style="{StaticResource BTSDialog}"
|
||||
>
|
||||
<Window.Resources>
|
||||
<sys:Double x:Key="HorizontalPush">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">8</sys:Double>
|
||||
<Thickness x:Key="ParamMargin" Left="{StaticResource HorizontalPush}"/>
|
||||
<Style x:Key="HeaderText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{x:Static u:BTSColors.header_text_brush}"/>
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
</Style>
|
||||
<Style x:Key="LimitToggle" TargetType="CheckBox">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Window.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type risk:RiskLimitsModificationVM}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush}}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.ColumnSpan="17" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{x:Static u:BTSColors.header_brush}"/>
|
||||
<TextBlock Grid.Column="0" Text="Security" Visibility="{Binding can_edit_security, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="2" Text="Max Long" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="4" Text="Max Short" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="6" Text="Clip Limit" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="8" Text="Min Price" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="10" Text="Max Price" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="12" Text="Position Limit Enabled" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="14" Text="Clip Limit Enabled" Style="{StaticResource HeaderText}"/>
|
||||
<TextBlock Grid.Column="16" Text="Price Limit Enabled" Style="{StaticResource HeaderText}"/>
|
||||
|
||||
<c:ContractPickerPopup
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}"
|
||||
Visibility="{Binding can_edit_security, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
IsEditMode="True"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding security_map}"
|
||||
SelectedCriteria="{Binding security_criteria}"
|
||||
ShowDialogSkirt="True"
|
||||
ToolTip="{Binding security_criteria, Converter={x:Static c:ContractPickerPopup.SecurityCriteriaTextConverter}}"
|
||||
OKClick="{x:Static c:ContractPickerPopup.FlushSelectedSecurityHandler}"
|
||||
/>
|
||||
<c:TickUpDownNullableUInt Grid.Row="2" Grid.Column="2" Value="{Binding max_long_position}"/>
|
||||
<c:TickUpDownNullableUInt Grid.Row="2" Grid.Column="4" Value="{Binding max_short_position}"/>
|
||||
<c:TickUpDownNullableUInt Grid.Row="2" Grid.Column="6" Value="{Binding clip_limit}"/>
|
||||
<c:TickUpDownNullableDouble Grid.Row="2" Grid.Column="8" Value="{Binding min_price}"/>
|
||||
<c:TickUpDownNullableDouble Grid.Row="2" Grid.Column="10" Value="{Binding max_price}"/>
|
||||
<CheckBox Grid.Row="2" Grid.Column="12" IsChecked="{Binding is_position_limit_enabled}" Style="{StaticResource LimitToggle}"/>
|
||||
<CheckBox Grid.Row="2" Grid.Column="14" IsChecked="{Binding is_clip_limit_enabled}" Style="{StaticResource LimitToggle}"/>
|
||||
<CheckBox Grid.Row="2" Grid.Column="16" IsChecked="{Binding is_price_limit_enabled}" Style="{StaticResource LimitToggle}"/>
|
||||
</Grid>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="0,6,0,0">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Content="OK" Command="{Binding commit_cmd}" IsDefault="True"/>
|
||||
<Button Content="Cancel" Command="{Binding cancel_cmd}" IsCancel="True"/>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Window.ContentTemplate>
|
||||
</Window>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
/// <summary>
|
||||
/// Interaction logic for RiskLimitParametersDialog.xaml
|
||||
/// </summary>
|
||||
partial class RiskLimitParametersDialog : Window {
|
||||
public RiskLimitParametersDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<Window
|
||||
x:Class="bts.shell.risk_management.RiskLimitsImportConflictsDialog"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:risk="clr-namespace:bts.shell.risk_management"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
u:WindowStyle.AllowResize="True"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="Import Limits Conflicts"
|
||||
Style="{StaticResource BTSDialogTitleTemplate}"
|
||||
>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Height="12" Margin="5,2" Source="{x:Static u:Icons.Import}"/>
|
||||
<TextBlock Margin="4,0,0,0" Text="{Binding}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
<Window.Content>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<risk:RiskLimitsImportConflictsView Margin="12,10,12,8"/>
|
||||
<c:DialogSkirt Grid.Row="1">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:Array Type="{x:Type Button}">
|
||||
<Button Command="{Binding ok_cmd}">Continue</Button>
|
||||
<Button Command="{Binding cancel_cmd}" IsCancel="True">Cancel</Button>
|
||||
</x:Array>
|
||||
</c:DialogSkirt.RightItems>
|
||||
</c:DialogSkirt>
|
||||
</Grid>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
/// <summary>
|
||||
/// Interaction logic for RiskLimitsImportConflictsDialog.xaml
|
||||
/// </summary>
|
||||
partial class RiskLimitsImportConflictsDialog : Window {
|
||||
public RiskLimitsImportConflictsDialog() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.risk_management.RiskLimitsImportConflictsView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:risk="clr-namespace:bts.shell.risk_management"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
MinWidth="400"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="VerticallyCenteredText" TargetType="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<DataTemplate x:Key="UIntTemplate">
|
||||
<c:TickUpDownUInt Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="DoubleTemplate">
|
||||
<c:TickUpDownDouble Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="BooleanTemplate">
|
||||
<CheckBox IsChecked="{Binding value}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="PriceReadTemplate">
|
||||
<TextBlock Text="{Binding value, Converter={StaticResource DoubleTextConverter}}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
<!--Value Cell Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellTemplateSelector">
|
||||
<u:KeyedTemplateSelector.Default>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding value}" VerticalAlignment="Center"/>
|
||||
</DataTemplate>
|
||||
</u:KeyedTemplateSelector.Default>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static MinPrice, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource PriceReadTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static MaxPrice, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource PriceReadTemplate}"/>
|
||||
</u:KeyedTemplateSelector>
|
||||
<!--Value Cell Editing Template Selector-->
|
||||
<u:KeyedTemplateSelector x:Key="ValueCellEditingTemplateSelector">
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Security, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}">
|
||||
<DataTemplate>
|
||||
<c:ContractPickerPopup
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CloseOnCancel="True"
|
||||
CloseOnOK="True"
|
||||
Header="{Binding value}"
|
||||
IsEditMode="False"
|
||||
IsLazy="True"
|
||||
SecurityMap="{Binding DataContext.security_map, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}"
|
||||
SelectedSecurity="{Binding value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ShowDialogSkirt="True"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static Account, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}">
|
||||
<DataTemplate>
|
||||
<ComboBox ItemsSource="{Binding DataContext.accounts, RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedItem="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DataTemplate>
|
||||
</u:TemplateSelectorKeyValue>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static MaxNetLongPosition, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource UIntTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static MaxNetShortPosition, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource UIntTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static IsPositionLimitEnabled, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource BooleanTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static ClipLimit, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource UIntTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static IsClipLimitEnabled, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource BooleanTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static MinPrice, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource DoubleTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static MaxPrice, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource DoubleTemplate}"/>
|
||||
<u:TemplateSelectorKeyValue Key="{x:Static IsPriceLimitEnabled, MemberType={u:Nested risk:RiskLimitsImportConflictsView.RiskLimitsComponentNames}}" Value="{StaticResource BooleanTemplate}"/>
|
||||
</u:KeyedTemplateSelector>
|
||||
</UserControl.Resources>
|
||||
<!--Content-->
|
||||
<Grid x:Name="content_root">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image VerticalAlignment="Center" Margin="0,0,8,0">
|
||||
<Image.Source>
|
||||
<Binding Source="{x:Static MessageBoxImage.Exclamation}">
|
||||
<Binding.Converter>
|
||||
<cv:SystemIconConverter/>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Canvas x:Name="text_canvas" Grid.Column="1" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="1" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=text_canvas}">
|
||||
<Run Text="{Binding num_successfully_parsed_limits, Mode=OneWay}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding num_limits, Mode=OneWay}"/>
|
||||
<Run Text=" limit items have been successfully converted."/>
|
||||
<Run Text="Resolve conflicts below by selecting a value to assign each affected limit item,"/>
|
||||
<Run Text="or modify individual limit items in the main window."/>
|
||||
<LineBreak/>
|
||||
<LineBreak/>
|
||||
<Run Text="Click 'Continue' to proceed to the main window."/>
|
||||
</TextBlock>
|
||||
<DataGrid
|
||||
x:Name="data_grid"
|
||||
u:DataGridBehavior.SingleClickEdit="True"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserSortColumns="True"
|
||||
HeadersVisibility="All"
|
||||
ItemsSource="{Binding rows_view}"
|
||||
Margin="0,8,0,0"
|
||||
MinRowHeight="17"
|
||||
RowHeaderWidth="22"
|
||||
>
|
||||
<DataGrid.CellStyle>
|
||||
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellBaseStyle}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</DataGrid.CellStyle>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow">
|
||||
<Setter Property="IsSelected" Value="{Binding is_selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource SelectedRowBackground}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Component" Binding="{Binding component}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding text}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Affected Limit Items" Binding="{Binding affected_limits.Count}" ElementStyle="{StaticResource VerticallyCenteredText}" IsReadOnly="True"/>
|
||||
<DataGridTemplateColumn Header="Value" CellTemplateSelector="{StaticResource ValueCellTemplateSelector}" CellEditingTemplateSelector="{StaticResource ValueCellEditingTemplateSelector}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
/// <summary>
|
||||
/// Interaction logic for RiskLimitsImportConflictsView.xaml
|
||||
/// </summary>
|
||||
partial class RiskLimitsImportConflictsView : UserControl {
|
||||
public RiskLimitsImportConflictsView() =>
|
||||
InitializeComponent();
|
||||
|
||||
public enum RiskLimitsComponent {
|
||||
Security,
|
||||
Account,
|
||||
MaxNetLongPosition,
|
||||
MaxNetShortPosition,
|
||||
IsPositionLimitEnabled,
|
||||
ClipLimit,
|
||||
IsClipLimitEnabled,
|
||||
MinPrice,
|
||||
MaxPrice,
|
||||
IsPriceLimitEnabled
|
||||
}
|
||||
|
||||
public static class RiskLimitsComponentNames {
|
||||
public static readonly string
|
||||
Security = RiskLimitsComponent.Security.ToString(),
|
||||
Account = RiskLimitsComponent.Account.ToString(),
|
||||
MaxNetLongPosition = RiskLimitsComponent.MaxNetLongPosition.ToString(),
|
||||
MaxNetShortPosition = RiskLimitsComponent.MaxNetShortPosition.ToString(),
|
||||
IsPositionLimitEnabled = RiskLimitsComponent.IsPositionLimitEnabled.ToString(),
|
||||
ClipLimit = RiskLimitsComponent.ClipLimit.ToString(),
|
||||
IsClipLimitEnabled = RiskLimitsComponent.IsClipLimitEnabled.ToString(),
|
||||
MinPrice = RiskLimitsComponent.MinPrice.ToString(),
|
||||
MaxPrice = RiskLimitsComponent.MaxPrice.ToString(),
|
||||
IsPriceLimitEnabled = RiskLimitsComponent.IsPriceLimitEnabled.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<UserControl
|
||||
x:Class="bts.shell.risk_management.RiskManagementControl"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:risk="clr-namespace:bts.shell.risk_management"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance risk:RiskManagementVM, IsDesignTimeCreatable=True}"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<cv:BooleanToObjectConverter x:Key="IsDirtyOpacityConverter">
|
||||
<cv:BooleanToObjectConverter.ValueForFalse>
|
||||
<sys:Double>1.0</sys:Double>
|
||||
</cv:BooleanToObjectConverter.ValueForFalse>
|
||||
<cv:BooleanToObjectConverter.ValueForTrue>
|
||||
<sys:Double>0.6</sys:Double>
|
||||
</cv:BooleanToObjectConverter.ValueForTrue>
|
||||
</cv:BooleanToObjectConverter>
|
||||
<Style x:Key="TextCell" TargetType="c:TextCell">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Padding" Value="2,0"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding Path='(c:DIYGrid.Column).horizontal_alignment', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_color', RelativeSource={RelativeSource Self}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
|
||||
<Setter Property="Foreground" Value="{Binding Path='(c:DIYGrid.Column).font_color', RelativeSource={RelativeSource Self}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
|
||||
<Setter Property="FontFamily" Value="{Binding Path='(c:DIYGrid.Column).font_family', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="FontWeight" Value="{Binding Path='(c:DIYGrid.Column).font_weight', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="FontSize" Value="{Binding Path='(c:DIYGrid.Column).font_size', RelativeSource={RelativeSource Self}}"/>
|
||||
<Setter Property="Opacity" Value="{Binding is_dirty, Converter={StaticResource IsDirtyOpacityConverter}}"/>
|
||||
</Style>
|
||||
<Style x:Key="CellBorder" TargetType="Border">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="{Binding Path='(c:DIYGrid.Column).background_color', RelativeSource={RelativeSource Self}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
|
||||
<Setter Property="Padding" Value="2,0"/>
|
||||
</Style>
|
||||
<Style x:Key="LimitToggleSwitch" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
||||
<Setter Property="Foreground" Value="{Binding Path='(c:DIYGrid.Column).font_color', RelativeSource={RelativeSource AncestorType=Border}, Converter={x:Static cv:ColorToBrushConverter.Instance}}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="{Binding Path='(c:DIYGrid.Column).horizontal_alignment', RelativeSource={RelativeSource AncestorType=Border}}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="4,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">Account:</TextBlock>
|
||||
<ComboBox Grid.Column="1" Margin="8,0" ItemsSource="{Binding accounts}" SelectedItem="{Binding selected_account}"/>
|
||||
</Grid>
|
||||
<c:DIYGrid
|
||||
Grid.Row="1"
|
||||
c:DIYGrid.ColumnModels="{Binding columns}"
|
||||
c:DIYGrid.SyncWithColumnGeometry="True"
|
||||
Rows="{Binding rows}"
|
||||
RowHeight="{u:DPIDouble 21.6}"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.Columns>
|
||||
<c:DIYGridColumnCollection/>
|
||||
</c:DIYGrid.Columns>
|
||||
<c:DIYGrid.CachedColumnParams>
|
||||
<x:Array Type="{x:Type sys:String}">
|
||||
<sys:String>font_weight</sys:String>
|
||||
<sys:String>font_size</sys:String>
|
||||
<sys:String>font_brush</sys:String>
|
||||
<sys:String>font_family</sys:String>
|
||||
<sys:String>horizontal_alignment</sys:String>
|
||||
</x:Array>
|
||||
</c:DIYGrid.CachedColumnParams>
|
||||
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate DataType="{x:Type risk:RiskLimitsVM}">
|
||||
<Grid c:DIYGrid.SyncWithColumnGeometry="True" Height="{Binding RowHeight, RelativeSource={RelativeSource AncestorType=c:DIYGrid}}">
|
||||
<!--Security-->
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="Security"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[Security]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding security_text}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="SecurityKey"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[SecurityKey]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding security_key}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<!--Position-->
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="MaxNetLongPosition"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MaxNetLongPosition]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding max_long_position}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="MaxNetShortPosition"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MaxNetShortPosition]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding max_short_position}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<Border
|
||||
c:DIYGrid.ColumnKey="IsPositionLimitEnabled"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsPositionLimitEnabled]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Style="{StaticResource CellBorder}"
|
||||
>
|
||||
<CheckBox
|
||||
IsChecked="{Binding is_position_limit_enabled}"
|
||||
Style="{StaticResource LimitToggleSwitch}"
|
||||
/>
|
||||
</Border>
|
||||
<!--Clip-->
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="ClipLimit"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[ClipLimit]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding clip_limit}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<Border
|
||||
c:DIYGrid.ColumnKey="IsClipLimitEnabled"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsClipLimitEnabled]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Style="{StaticResource CellBorder}"
|
||||
>
|
||||
<CheckBox
|
||||
IsChecked="{Binding is_clip_limit_enabled}"
|
||||
Style="{StaticResource LimitToggleSwitch}"
|
||||
/>
|
||||
</Border>
|
||||
<!--Price-->
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="MinPrice"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MinPrice]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding min_price, StringFormat='#,0.0000'}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="MaxPrice"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[MaxPrice]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Text="{Binding max_price, StringFormat='#,0.0000'}"
|
||||
Style="{StaticResource TextCell}"
|
||||
/>
|
||||
<Border
|
||||
c:DIYGrid.ColumnKey="IsPriceLimitEnabled"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsPriceLimitEnabled]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Style="{StaticResource CellBorder}"
|
||||
>
|
||||
<CheckBox
|
||||
IsChecked="{Binding is_price_limit_enabled}"
|
||||
Style="{StaticResource LimitToggleSwitch}"
|
||||
/>
|
||||
</Border>
|
||||
<!--<Border
|
||||
c:DIYGrid.ColumnKey="IsDirty"
|
||||
c:DIYGrid.Column="{Binding Path='DataContext.column_map[IsDirty]', RelativeSource={RelativeSource AncestorType=c:DIYGrid}}"
|
||||
Style="{StaticResource CellBorder}"
|
||||
>
|
||||
<Image Source="{x:Static u:Icons.CheckMark}" Visibility="{Binding is_persisted, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
</Border>-->
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,33 @@
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace bts.shell.risk_management {
|
||||
/// <summary>
|
||||
/// Interaction logic for RiskManagementControl.xaml
|
||||
/// </summary>
|
||||
partial class RiskManagementControl : UserControl {
|
||||
public RiskManagementControl() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public enum Columns {
|
||||
None,
|
||||
Security,
|
||||
MaxNetLongPosition,
|
||||
MaxNetShortPosition,
|
||||
ClipLimit,
|
||||
MinPrice,
|
||||
MaxPrice,
|
||||
IsPositionLimitEnabled,
|
||||
IsClipLimitEnabled,
|
||||
IsPriceLimitEnabled,
|
||||
SecurityKey
|
||||
}
|
||||
}
|
||||
|
||||
class RiskManagementColumn : DIYGrid.ColumnBase<RiskManagementControl.Columns> {
|
||||
protected override void on_key_initialized(RiskManagementControl.Columns key) {
|
||||
id = key.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.position;
|
||||
using bts.shell.fills;
|
||||
using bts.shell.manual_fills;
|
||||
using bts.shell.risk_management;
|
||||
using bts.spark;
|
||||
using mbts.persist;
|
||||
using mbts.spark;
|
||||
@@ -27,6 +28,7 @@ namespace bts.shell {
|
||||
readonly PNLWindowFactory _plwf;
|
||||
readonly AlgoStudioWindowFactory _aswf;
|
||||
readonly ManualFillsWindowFactory _mfwf;
|
||||
readonly RiskManagementWindowFactory _rmwf;
|
||||
//
|
||||
List<SparkCommand> _actions;
|
||||
|
||||
@@ -54,6 +56,7 @@ namespace bts.shell {
|
||||
_old_plwf = new PAndLWindowFactory(this, bus, settings_hub, orders_model);
|
||||
_aswf = new AlgoStudioWindowFactory(this, bus, orders_model, error_reporter);
|
||||
_mfwf = new ManualFillsWindowFactory(this, bus, orders_model);
|
||||
_rmwf = new RiskManagementWindowFactory(this, bus, settings_hub, orders_model.admin_model);
|
||||
}
|
||||
|
||||
public ImageSource icon => Icons.Trades;
|
||||
@@ -77,7 +80,8 @@ namespace bts.shell {
|
||||
SparkCommands.PickLadder,
|
||||
SparkCommands.PickOrderTicket,
|
||||
SparkCommands.ShowAlgoStudio.complete(() => new AlgoStudioWindowFactory.StartupData()),
|
||||
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default)
|
||||
SparkCommands.ShowManualFills().complete(() => ManualFillsSettings.Default),
|
||||
SparkCommands.ShowRiskManagement.complete(() => RiskManagementSettings.Default)
|
||||
});
|
||||
}
|
||||
return _actions;
|
||||
@@ -95,6 +99,7 @@ namespace bts.shell {
|
||||
yield return _old_plwf;
|
||||
yield return _aswf;
|
||||
yield return _mfwf;
|
||||
yield return _rmwf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,9 +155,32 @@ namespace bts {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
|
||||
public IObservable<Update<(SecurityImmutable? sec, T val), TKey>> join<T,TKey>(
|
||||
IObservable<Update<T, TKey>> source, Func<T,security_key> skeyfn) where TKey : IEquatable<TKey> {
|
||||
return Observable.Create<Update<(SecurityImmutable?, T), TKey>>(observer => {
|
||||
public IObservable<Update<(SecurityImmutable? sec, T val), TKey>> join<T, TKey>(
|
||||
IObservable<Update<T, TKey>> source, Func<T, security_key> skeyfn) where TKey : IEquatable<TKey> {
|
||||
return join(source, skeyfn, get_incremental_props, mk_update);
|
||||
|
||||
(IEnumerable<T> all, IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, TKey> key_extractor) get_incremental_props(Update<T, TKey> upd_in) =>
|
||||
(upd_in.all, upd_in.added, upd_in.changes, upd_in.removed, upd_in.key_extractor);
|
||||
|
||||
Update<(SecurityImmutable? sec, T val), TKey> mk_update(
|
||||
IEnumerable<(SecurityImmutable? sec, T val)> all,
|
||||
IEnumerable<(SecurityImmutable? sec, T val)> added,
|
||||
IEnumerable<Change<(SecurityImmutable? sec, T val)>> changes,
|
||||
IEnumerable<(SecurityImmutable? sec, T val)> removed,
|
||||
Func<(SecurityImmutable?, T), TKey> key_extractor) =>
|
||||
//
|
||||
new Update<(SecurityImmutable? sec, T val), TKey>(
|
||||
all, added, changes, removed, key_extractor);
|
||||
}
|
||||
|
||||
public IObservable<TOut> join<TIn, T, TKey, TOut>(
|
||||
IObservable<TIn> source,
|
||||
Func<T,security_key> skeyfn,
|
||||
Func<TIn, (IEnumerable<T> all, IEnumerable<T> added, IEnumerable<Change<T>> changes, IEnumerable<T> removed, Func<T, TKey> key_extractor)> get_incremental_props,
|
||||
Func<IEnumerable<(SecurityImmutable? sec, T val)>, IEnumerable<(SecurityImmutable? sec, T val)>, IEnumerable<Change<(SecurityImmutable? sec, T val)>>, IEnumerable<(SecurityImmutable? sec, T val)>, Func<(SecurityImmutable?, T), TKey>, TOut> create_result_update)
|
||||
where TKey : IEquatable<TKey> {
|
||||
//
|
||||
return Observable.Create<TOut>(observer => {
|
||||
var state = ImmutableDictionary<TKey, (SecurityImmutable? s, T t)>.Empty;
|
||||
var gate = new object();
|
||||
// We get thie function from the first source update
|
||||
@@ -186,11 +209,11 @@ namespace bts {
|
||||
state = builder.ToImmutable();
|
||||
// fire update
|
||||
observer.OnNext(
|
||||
new Update<(SecurityImmutable?, T), TKey>(
|
||||
state.Values,
|
||||
Enumerable.Empty<(SecurityImmutable?,T)>(),
|
||||
changed,
|
||||
Enumerable.Empty<(SecurityImmutable?,T)>(),
|
||||
create_result_update(
|
||||
state.Values,
|
||||
Enumerable.Empty<(SecurityImmutable?, T)>(),
|
||||
changed,
|
||||
Enumerable.Empty<(SecurityImmutable?, T)>(),
|
||||
rowkeyfn));
|
||||
}
|
||||
}
|
||||
@@ -198,10 +221,11 @@ namespace bts {
|
||||
|
||||
return Disposable.Create(
|
||||
// Fire update when source changes
|
||||
source.Subscribe((update) => {
|
||||
source.Subscribe((update_in) => {
|
||||
var update = get_incremental_props(update_in);
|
||||
lock (gate) {
|
||||
rowkeyfn = rowkeyfn ?? (i => update.key_extractor(i.Item2));
|
||||
var relevant_securities = get_and_ensure_securities(update.added.Concat(update.changed).Select(skeyfn));
|
||||
var relevant_securities = get_and_ensure_securities(update.added.Concat(update.changes.New()).Select(skeyfn));
|
||||
|
||||
var added = new List<(SecurityImmutable?, T)>();
|
||||
var changed = new List<Change<(SecurityImmutable? s, T t)>>();
|
||||
@@ -238,8 +262,7 @@ namespace bts {
|
||||
}
|
||||
state = builder.ToImmutable();
|
||||
|
||||
observer.OnNext(new Update<(SecurityImmutable?, T t), TKey>(
|
||||
state.Values, added, changed, removed, rowkeyfn));
|
||||
observer.OnNext(create_result_update(state.Values, added, changed, removed, rowkeyfn));
|
||||
}
|
||||
}),
|
||||
// Also fire update when securities change
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifiers>win7-x64</RuntimeIdentifiers>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<LanguageTargets Condition="'$(VisualStudioVersion)'=='17.0'">
|
||||
C:/Program Files/Microsoft Visual Studio/2022/Community/Msbuild/Current/Bin/amd64/Microsoft.CSharp.targets
|
||||
</LanguageTargets>
|
||||
<LanguageTargets>$(MSBuildExtensionsPath)\$(VisualStudioVersion)\Bin\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<LanguageTargets Condition="'$(VisualStudioVersion)'=='16.0'">$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<StartupObject>mbts.ui.shell.App</StartupObject>
|
||||
@@ -35,7 +34,7 @@
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
|
||||
<PackageReference Include="IronPython" Version="2.7.7" />
|
||||
<PackageReference Include="NAudio" Version="1.8.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace bts.spark {
|
||||
public static PartialCommand<bts.shell.orderbook.OrderBookStartupData> ShowOrders =>
|
||||
SparkCommand.create("Order Book", Icons.Book, SparkTags.ShowOrderBookTag);
|
||||
|
||||
public static PartialCommand<shell.risk_management.RiskManagementSettings> ShowRiskManagement =>
|
||||
SparkCommand.create("Risk Management", Icons.Risk, SparkTags.ShowRiskManagement);
|
||||
|
||||
// Fills
|
||||
public static PartialCommand<FillsSettings> ShowFills(KeyGesture key_gesture = null) =>
|
||||
SparkCommand.create("Fills", Icons.Payment, SparkTags.ShowFillsTag, key_gesture);
|
||||
|
||||
@@ -63,6 +63,7 @@ namespace mbts.spark {
|
||||
public static ITag<IObservable<SecurityMD>> ShowLinkedLadder { get; } = new SparkTag<IObservable<SecurityMD>>("Linked Ladder");
|
||||
public static ITag<SecurityCriteria> ShowMarketViewTag { get; } = new SparkTag<SecurityCriteria>("Show MarketView");
|
||||
public static ITag<bts.shell.orderbook.OrderBookStartupData> ShowOrderBookTag { get; } = new SparkTag<bts.shell.orderbook.OrderBookStartupData>(nameof(ShowOrderBookTag));
|
||||
public static ITag<bts.shell.risk_management.RiskManagementSettings> ShowRiskManagement { get; } = new SparkTag<bts.shell.risk_management.RiskManagementSettings>(nameof(ShowRiskManagement));
|
||||
public static ITag<PAndLWindowFactory.PAndLStartupData> ShowPAndLTag { get; } = new SparkTag<PAndLWindowFactory.PAndLStartupData>(nameof(ShowPAndLTag));
|
||||
public static ITag<bts.shell.pnl.PNLSettings> ShowPNLTag { get; } = new SparkTag<bts.shell.pnl.PNLSettings>(nameof(ShowPNLTag));
|
||||
public static ITag<(SecurityImmutable security, ExecutionStrategyImmutable strategy)> ShowSyntheticCloneTag { get; } = new SparkTag<(SecurityImmutable security, ExecutionStrategyImmutable strategy)>(nameof(ShowSyntheticCloneTag));
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace bts.settings {
|
||||
interface IMarketViewGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPAndLGridSettingsMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface IPNLSettingsMigrationRule : ISettingsMigrationRule<PNLSettings> { }
|
||||
interface IRiskManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
interface ISyntheticManagementMigrationRule : ISettingsMigrationRule<GridSettings> { }
|
||||
|
||||
static class SettingsMigrationHelper {
|
||||
|
||||
@@ -61,6 +61,8 @@ namespace mbts.spark {
|
||||
PNLSettings _pnl;
|
||||
[ProtoMember(14)]
|
||||
GlobalAlertSettings _alerts;
|
||||
[ProtoMember(15)]
|
||||
GridSettings _risk_management_grid;
|
||||
|
||||
public UserSettings() { }
|
||||
|
||||
@@ -74,6 +76,7 @@ namespace mbts.spark {
|
||||
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.time_and_sales.TimeAndSalesSettings.DefaultColumns.all));
|
||||
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
|
||||
public GridSettings risk_management_grid => _risk_management_grid ?? (_risk_management_grid = bts.shell.risk_management.RiskManagementSettings.DefaultGrid);
|
||||
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
|
||||
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
|
||||
public PNLSettings pnl => _pnl ?? (_pnl = PNLSettings.Default);
|
||||
@@ -91,7 +94,8 @@ namespace mbts.spark {
|
||||
GridSettings provided_pnl_gridsettings = null,
|
||||
PNLSettings pnl = null,
|
||||
bts.shell.orderbook.OrderBookGridSettings order_book_grid = null,
|
||||
GlobalAlertSettings alerts = null) {
|
||||
GlobalAlertSettings alerts = null,
|
||||
GridSettings risk_management_grid = null) {
|
||||
var us = new UserSettings(ladder_profiles ?? _ladder_profiles) {
|
||||
_ladder = ladder ?? _ladder,
|
||||
_notification = notification ?? _notification,
|
||||
@@ -102,7 +106,8 @@ namespace mbts.spark {
|
||||
_pnl_grid = provided_pnl_gridsettings ?? _pnl_grid,
|
||||
_pnl = pnl ?? _pnl,
|
||||
_order_book_grid = order_book_grid ?? _order_book_grid,
|
||||
_alerts = alerts ?? _alerts
|
||||
_alerts = alerts ?? _alerts,
|
||||
_risk_management_grid = risk_management_grid ?? _risk_management_grid
|
||||
};
|
||||
us._last_ladder_profile_id = Math.Max(_last_ladder_profile_id, us.ladder_profiles.Select(p => p.id).MaxOrDefault());
|
||||
return us;
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace mbts.persist {
|
||||
_fills_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.fills_grid, (us, gs) => us.with(provided_fills_gridsettings: gs)),
|
||||
_mview_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.market_view_grid, (us, gs) => us.with(provided_market_view_gridsettings: gs)),
|
||||
_pandl_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.pnl_grid, (us, gs) => us.with(provided_pnl_gridsettings: gs)),
|
||||
_risk_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.risk_management_grid, (us, gs) => us.with(risk_management_grid: gs)),
|
||||
_synth_propi = ImmutableProperty.create<UserSettings, GridSettings>(us => us.synthetic_management_grid, (us, gs) => us.with(provided_synthetic_management_gridsettings: gs));
|
||||
|
||||
static readonly IImmutableProperty<UserSettings, PNLSettings>
|
||||
@@ -37,6 +38,7 @@ namespace mbts.persist {
|
||||
readonly IEnumerable<IMarketViewGridSettingsMigrationRule> _mview_migration_rules;
|
||||
readonly IEnumerable<IPAndLGridSettingsMigrationRule> _pandl_migration_rules;
|
||||
readonly IEnumerable<IPNLSettingsMigrationRule> _pnl_migration_rules;
|
||||
readonly IEnumerable<IRiskManagementMigrationRule> _risk_migration_rules;
|
||||
readonly IEnumerable<ISyntheticManagementMigrationRule> _synth_migration_rules;
|
||||
readonly IUserPersist _persist;
|
||||
|
||||
@@ -49,6 +51,7 @@ namespace mbts.persist {
|
||||
[ImportMany]IEnumerable<IMarketViewGridSettingsMigrationRule> mview_migration_rules,
|
||||
[ImportMany]IEnumerable<IPAndLGridSettingsMigrationRule> pandl_migration_rules,
|
||||
[ImportMany]IEnumerable<IPNLSettingsMigrationRule> pnl_migration_rules,
|
||||
[ImportMany]IEnumerable<IRiskManagementMigrationRule> risk_migration_rules,
|
||||
[ImportMany]IEnumerable<ISyntheticManagementMigrationRule> synth_migration_rules) {
|
||||
//
|
||||
_persist = persist;
|
||||
@@ -56,6 +59,7 @@ namespace mbts.persist {
|
||||
_mview_migration_rules = mview_migration_rules ?? Enumerable.Empty<IMarketViewGridSettingsMigrationRule>();
|
||||
_pandl_migration_rules = pandl_migration_rules ?? Enumerable.Empty<IPAndLGridSettingsMigrationRule>();
|
||||
_pnl_migration_rules = pnl_migration_rules ?? Enumerable.Empty<IPNLSettingsMigrationRule>();
|
||||
_risk_migration_rules = risk_migration_rules ?? Enumerable.Empty<IRiskManagementMigrationRule>();
|
||||
_synth_migration_rules = synth_migration_rules ?? Enumerable.Empty<ISyntheticManagementMigrationRule>();
|
||||
init();
|
||||
async void init() {
|
||||
@@ -68,6 +72,7 @@ namespace mbts.persist {
|
||||
_mview_migration_rules.merge().wrap(_mview_propi),
|
||||
_pandl_migration_rules.merge().wrap(_pandl_propi),
|
||||
_pnl_migration_rules.merge().wrap(_pnl_propi),
|
||||
_risk_migration_rules.merge().wrap(_risk_propi),
|
||||
_synth_migration_rules.merge().wrap(_synth_propi)
|
||||
};
|
||||
SettingsMigrationHelper.apply_quiet(ref s, wrapped_rules);
|
||||
@@ -142,6 +147,12 @@ namespace mbts.persist {
|
||||
_pnl_migration_rules,
|
||||
_pnl_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_risk_management_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
_risk_migration_rules,
|
||||
_risk_propi);
|
||||
|
||||
public IWindowSettingsManager<GridSettings> create_synth_settings_manager(GridSettings instance_settings) =>
|
||||
_create_settings_manager(
|
||||
instance_settings,
|
||||
@@ -170,4 +181,4 @@ namespace mbts.persist {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using mbts;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
@@ -10,6 +11,13 @@ using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.utils {
|
||||
class PropertyEqualityComparerAttribute : Attribute {
|
||||
public PropertyEqualityComparerAttribute(Type comparer_type) =>
|
||||
this.comparer_type = comparer_type ?? throw new ArgumentNullException(nameof(comparer_type));
|
||||
|
||||
public Type comparer_type { get; }
|
||||
}
|
||||
|
||||
interface IPropertyChangedDetector<T> where T : IRaiseNotifyPropertyChanged {
|
||||
IDisposable detect_changes(T target);
|
||||
}
|
||||
@@ -68,6 +76,42 @@ namespace bts.utils {
|
||||
public static IObservable<bool> dirty_monitor<T>(T instance, PropertyInfo[] props) where T : INotifyPropertyChanged =>
|
||||
_dirty_monitor(instance, props.ToDictionary(p => p.Name, p => new PropNode(p, p.GetValue(instance))));
|
||||
|
||||
public static Func<T, int> property_hash_code_delegate<T>(string prop) =>
|
||||
DelegateStore<T>.property_hash_code(prop);
|
||||
|
||||
public static Func<T, int> property_hash_code_delegate<T>(IEnumerable<string> props) {
|
||||
var prop_hash_code_dels = props.Select(DelegateStore<T>.property_hash_code).ExceptNull().ToArray();
|
||||
return impl;
|
||||
int impl(T a) =>
|
||||
EqualityComparer.auto_phase(prop_hash_code_dels.Select(peq => peq(a)));
|
||||
}
|
||||
|
||||
public static Func<T, T, bool> property_equals_delegate<T>(string prop) =>
|
||||
DelegateStore<T>.property_equals(prop);
|
||||
|
||||
public static Func<T, T, bool> property_equals_delegate<T>(IEnumerable<string> props) {
|
||||
var prop_equals_dels = props.Select(DelegateStore<T>.property_equals).ExceptNull().ToArray();
|
||||
return impl;
|
||||
bool impl(T a, T b) =>
|
||||
prop_equals_dels.All(peq => peq(a, b));
|
||||
}
|
||||
|
||||
public static IEqualityComparer<T> property_equality_comparer<T>() {
|
||||
var prop_infos = PropertyInfoStore<T>.all_props;
|
||||
return property_equality_comparer<T>(
|
||||
prop_infos.Select(p => p.Name),
|
||||
// hash code should NEVER include mutable props
|
||||
prop_infos.Where(pi => !pi.CanWrite).Select(p => p.Name));
|
||||
}
|
||||
|
||||
public static IEqualityComparer<T> property_equality_comparer<T>(IEnumerable<string> props) =>
|
||||
property_equality_comparer<T>(props, props);
|
||||
|
||||
public static IEqualityComparer<T> property_equality_comparer<T>(IEnumerable<string> equals_props, IEnumerable<string> hash_code_props) =>
|
||||
EqualityComparer.Create(
|
||||
property_equals_delegate<T>(equals_props),
|
||||
property_hash_code_delegate<T>(hash_code_props));
|
||||
|
||||
static IObservable<bool> _dirty_monitor<T>(T instance, Dictionary<string, PropNode> prop_map) where T : INotifyPropertyChanged =>
|
||||
Observable.Create<bool>(o => {
|
||||
var dirty_props = new HashSet<string>();
|
||||
@@ -106,6 +150,8 @@ namespace bts.utils {
|
||||
PropertyInfo prop_info { get; }
|
||||
Func<T, object> getter { get; }
|
||||
Action<T, object> setter { get; }
|
||||
Func<T, T, bool> property_equals { get; }
|
||||
Func<T, int> property_hash_code { get; }
|
||||
(Action<T> initialize, Func<T, bool> refresh) create_snapshot();
|
||||
(Action initialize, Func<bool> refresh) create_snapshot(T source);
|
||||
}
|
||||
@@ -139,6 +185,15 @@ namespace bts.utils {
|
||||
public static Action<T, object> setter(string prop_name) =>
|
||||
accessor_data.TryGetValue(prop_name, out var dels) ? dels.setter : null;
|
||||
|
||||
public static Func<T, T, bool> property_equals(string prop_name) =>
|
||||
accessor_data.TryGetValue(prop_name, out var dels) ? dels.property_equals : null;
|
||||
|
||||
public static Func<T, int> property_hash_code(string prop_name) =>
|
||||
accessor_data.TryGetValue(prop_name, out var dels) ? dels.property_hash_code : null;
|
||||
|
||||
public static IPropertyAccessorData<T> get_accessor(string prop_name) =>
|
||||
accessor_data.GetValueOrDefault(prop_name);
|
||||
|
||||
static DelegateStore() {
|
||||
var prop_dels = new Dictionary<string, IPropertyAccessorData<T>>();
|
||||
var accessor_type_cache = new Dictionary<Type, Type>();
|
||||
@@ -185,6 +240,7 @@ namespace bts.utils {
|
||||
class PropertyAccessorData<T, TValue> : IPropertyAccessorData<T, TValue> {
|
||||
readonly Func<T, object> _ng_get;
|
||||
readonly Action<T, object> _ng_set;
|
||||
IEqualityComparer<TValue> _property_equals_comp;
|
||||
|
||||
public PropertyAccessorData(PropertyInfo prop_info) {
|
||||
this.prop_info = prop_info;
|
||||
@@ -200,11 +256,27 @@ namespace bts.utils {
|
||||
setter = set_method.CreateDelegate(typeof(Action<T, TValue>)) as Action<T, TValue>;
|
||||
_ng_set = (src, val) => setter(src, (TValue)val);
|
||||
}
|
||||
if (prop_info.GetCustomAttribute<PropertyEqualityComparerAttribute>()?.comparer_type is Type comp_type) {
|
||||
_property_equals_comp = Activator.CreateInstance(comp_type) as IEqualityComparer<TValue>;
|
||||
}
|
||||
if (_property_equals_comp is null) {
|
||||
_property_equals_comp = EqualityComparer<TValue>.Default;
|
||||
}
|
||||
}
|
||||
|
||||
public Func<T, TValue> getter { get; }
|
||||
public Action<T, TValue> setter { get; }
|
||||
public PropertyInfo prop_info { get; }
|
||||
public Func<T, T, bool> property_equals {
|
||||
get {
|
||||
return impl;
|
||||
bool impl(T a, T b) =>
|
||||
_property_equals_comp.Equals(getter(a), getter(b));
|
||||
}
|
||||
}
|
||||
public Func<T, int> property_hash_code =>
|
||||
a => _property_equals_comp.GetHashCode(getter(a));
|
||||
|
||||
Func<T, object> IPropertyAccessorData<T>.getter => _ng_get;
|
||||
Action<T, object> IPropertyAccessorData<T>.setter => _ng_set;
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
using System;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.ui.utility {
|
||||
@@ -153,6 +158,95 @@ namespace mbts.ui.utility {
|
||||
_try_match_item(_item_to_value(item), out match);
|
||||
}
|
||||
|
||||
class SelectorVM<T, TKey> {
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
readonly IEqualityComparer<TKey> _key_comparer;
|
||||
readonly Func<T, TKey> _key_fn;
|
||||
readonly ObservableCollection<T> _items = new ObservableCollection<T>();
|
||||
readonly Dictionary<TKey, T> _map;
|
||||
/// not as noisy as <see cref="ObservableCollection{T}.CollectionChanged"/>,
|
||||
/// this event is only raised after a batch collection change has been completed.
|
||||
/// <see cref="update_items(IEnumerable{T})"/>.
|
||||
IComparer<T> _comparer;
|
||||
T _selected_item;
|
||||
TKey _pending_selected_item_key;
|
||||
bool _is_pending_selection;
|
||||
|
||||
public SelectorVM(Func<T, TKey> key_fn)
|
||||
: this(key_fn, EqualityComparer<TKey>.Default) { }
|
||||
|
||||
public SelectorVM(Func<T, TKey> key_fn, IEqualityComparer<TKey> key_comparer) {
|
||||
_key_fn = key_fn ?? throw new ArgumentNullException(nameof(key_fn));
|
||||
_key_comparer = key_comparer ?? throw new ArgumentNullException(nameof(key_comparer));
|
||||
_eq_comparer = EqualityComparer.Create(_key_fn, _key_comparer);
|
||||
_map = new Dictionary<TKey, T>(key_comparer);
|
||||
items = ObservableAutoWrapper.create_read_only_wrapper(_items);
|
||||
}
|
||||
|
||||
public event Action selected_item_changed;
|
||||
|
||||
public IReadOnlyObservableCollection<T> items { get; }
|
||||
public T selected_item {
|
||||
get => _selected_item;
|
||||
set {
|
||||
if (!_eq_comparer.Equals(_selected_item, value)) {
|
||||
_selected_item = value;
|
||||
selected_item_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
public IComparer<T> comparer {
|
||||
get => _comparer;
|
||||
set {
|
||||
_comparer = value;
|
||||
if (value != null)
|
||||
_update_items(items, false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool try_select(T item, bool wait_for_item_to_arrive = false) =>
|
||||
try_select(_key_fn(item), wait_for_item_to_arrive);
|
||||
|
||||
public bool try_select(TKey item_key, bool wait_for_item_to_arrive = false) {
|
||||
if (_map.TryGetValue(item_key, out var item)) {
|
||||
selected_item = item;
|
||||
return true;
|
||||
} else if (wait_for_item_to_arrive) {
|
||||
_pending_selected_item_key = item_key;
|
||||
_is_pending_selection = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void update_items(IEnumerable<T> items) =>
|
||||
_update_items(items, true);
|
||||
|
||||
void _update_items(IEnumerable<T> items, bool map_changed) {
|
||||
// The currently selected item may be removed during this collection change.
|
||||
// After the items have been changed, attempt to re-select the last selected item;
|
||||
// if there is already a particular 'pending selection,' attempt to select that instead.
|
||||
var target_key = _is_pending_selection ? _pending_selected_item_key : _key_fn(_selected_item);
|
||||
if (map_changed)
|
||||
_map.Clear();
|
||||
if (items is null) {
|
||||
_items.Clear();
|
||||
} else {
|
||||
if (_comparer != null) {
|
||||
items = items.Ordered(_comparer);
|
||||
}
|
||||
_items.sync_with(items);
|
||||
}
|
||||
if (map_changed) {
|
||||
foreach (var i in this.items)
|
||||
_map[_key_fn(i)] = i;
|
||||
}
|
||||
if (_map.Count != this.items.Count)
|
||||
throw new InvalidOperationException("Map and items are out of sync (different item counts).");
|
||||
if (try_select(target_key, _is_pending_selection || _items.Count == 0) && _is_pending_selection)
|
||||
_is_pending_selection = false;
|
||||
}
|
||||
}
|
||||
|
||||
static class SelectionModelExtensions {
|
||||
public static IObservable<TValue> observe_selected_item<TItem, TValue>(this SelectionModel<TItem, TValue> selection_model, bool notify_on_subscribe = false) =>
|
||||
Observable.Create<TValue>(o => {
|
||||
@@ -166,4 +260,4 @@ namespace mbts.ui.utility {
|
||||
return Disposable.Create(() => selection_model.Changed -= callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,10 @@ namespace mbts.ui.controls {
|
||||
public string Text { get => GetValue(TextProperty) as string; set => SetValue(TextProperty, value); }
|
||||
#endregion
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize) => finalSize;
|
||||
protected override Size ArrangeOverride(Size finalSize) {
|
||||
_gcr.available_size = finalSize;
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize) {
|
||||
_gcr.available_size = availableSize;
|
||||
|
||||
@@ -237,6 +237,15 @@ namespace mbts.ui.controls {
|
||||
Color? _background_color_override;
|
||||
object _header_copy;
|
||||
bool _is_visible;
|
||||
IDisposable _model_hooks;
|
||||
|
||||
static ColumnBase() {
|
||||
WidthProperty.OverrideMetadata(typeof(ColumnBase),
|
||||
new PropertyMetadata((d, e) => {
|
||||
if (d is ColumnBase cb)
|
||||
cb.width_changed?.Invoke((double)e.NewValue);
|
||||
}));
|
||||
}
|
||||
|
||||
protected ColumnBase() {
|
||||
var gs = GridSettings.Default;
|
||||
@@ -256,10 +265,25 @@ namespace mbts.ui.controls {
|
||||
get => GetValue(SortDirectionProperty) as ListSortDirection?;
|
||||
set => SetValue(SortDirectionProperty, value);
|
||||
}
|
||||
ListSortDirection? ISortableDIYGridColumn.SortDirection {
|
||||
get => SortDirection;
|
||||
set => SetCurrentValue(SortDirectionProperty, value);
|
||||
}
|
||||
|
||||
#region IsVisible
|
||||
public static readonly DependencyProperty IsVisibleProperty =
|
||||
WPFHelper.create_dp<bool, ColumnBase>(nameof(IsVisible), (o, v) => o.is_visible = v);
|
||||
|
||||
public bool IsVisible {
|
||||
get => (bool)GetValue(IsVisibleProperty);
|
||||
set => SetValue(IsVisibleProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
|
||||
public event Action font_params_changed;
|
||||
public event Action<bool> is_visible_changed;
|
||||
public event Action<double> width_changed;
|
||||
|
||||
public string id { get; set; }
|
||||
public string sort_member_path { get; set; }
|
||||
@@ -273,6 +297,7 @@ namespace mbts.ui.controls {
|
||||
_is_visible = value;
|
||||
is_visible_changed?.Invoke(value);
|
||||
raise_prop_changed();
|
||||
SetCurrentValue(IsVisibleProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,6 +322,29 @@ namespace mbts.ui.controls {
|
||||
set => base.Header = (_header_copy = value);
|
||||
}
|
||||
|
||||
public void set_model(IDIYGridColumnModel model) {
|
||||
Disposable.dispose(ref _model_hooks);
|
||||
model.width = Width;
|
||||
width_changed += on_width_changed;
|
||||
model.width_changed += on_model_width_changed;
|
||||
model.result_changed += on_result_changed;
|
||||
_model_hooks = Disposable.Create(() => {
|
||||
width_changed -= on_width_changed;
|
||||
model.width_changed -= on_model_width_changed;
|
||||
});
|
||||
void on_model_width_changed(double value) {
|
||||
if (!DoubleUtil.AreClose(value, Width))
|
||||
SetCurrentValue(WidthProperty, value);
|
||||
}
|
||||
void on_width_changed(double value) {
|
||||
model.width_changed -= on_model_width_changed;
|
||||
model.width = value;
|
||||
model.width_changed += on_model_width_changed;
|
||||
}
|
||||
void on_result_changed() =>
|
||||
load_from(model.create());
|
||||
}
|
||||
|
||||
/// important: don't access any dependency props in here.
|
||||
/// this may be called from a thread other than that of this dispatcher (i.e., when <see cref="IShellWindow.GetState(System.IO.Stream)"/> is called).
|
||||
public GridColumnSettings create() =>
|
||||
@@ -327,9 +375,8 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
if (original_width != settings.width) {
|
||||
original_width = settings.width;
|
||||
Width = original_width;
|
||||
SetCurrentValue(WidthProperty, original_width);
|
||||
}
|
||||
//
|
||||
}
|
||||
|
||||
// 'fallback' font-params (aka 'grid-level' font-params)
|
||||
|
||||
@@ -9,10 +9,10 @@ using System.Reactive.Linq;
|
||||
namespace mbts.ui {
|
||||
class ColumnSortManager {
|
||||
readonly List<ISortableDIYGridColumn> _visible_sorting = new List<ISortableDIYGridColumn>();
|
||||
readonly IList<ISortableDIYGridColumn> _columns;
|
||||
readonly IEnumerable<ISortableDIYGridColumn> _columns;
|
||||
readonly string _primary_key;
|
||||
|
||||
public ColumnSortManager(IList<ISortableDIYGridColumn> columns, string primary_key) {
|
||||
public ColumnSortManager(IEnumerable<ISortableDIYGridColumn> columns, string primary_key) {
|
||||
_columns = columns;
|
||||
_primary_key = primary_key;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
namespace mbts.ui.controls {
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
class DIYGridColumn : DIYGrid.ColumnBase<string> {
|
||||
public DIYGridColumn()
|
||||
: base() {
|
||||
@@ -6,4 +20,194 @@
|
||||
Width = 60;
|
||||
}
|
||||
}
|
||||
|
||||
interface IDIYGridColumnModel : DIYGrid.IFilterableColumn, IValueFactoryVM<GridColumnSettings> {
|
||||
string id { get; }
|
||||
string column_template { get; }
|
||||
IComparer row_comparer { get; }
|
||||
double width { get; set; }
|
||||
event Action<double> width_changed;
|
||||
event Action result_changed;
|
||||
}
|
||||
|
||||
interface IDIYGridColumnFactory {
|
||||
DIYGrid.ColumnBase create_view();
|
||||
}
|
||||
|
||||
class DIYGridColumnModel<TKey> : ViewModelBase, IDIYGridColumnModel, ISortableDIYGridColumn<TKey>, ISortableDIYGridColumn, IValueFactoryVM<GridColumnSettings> where TKey : struct {
|
||||
readonly DeferrableActionWrapper _result_changed;
|
||||
|
||||
FontWeight _fallback_font_weight;
|
||||
Color _fallback_font_color;
|
||||
Color _fallback_background_color;
|
||||
double _fallback_font_size;
|
||||
FontFamily _fallback_font_family;
|
||||
|
||||
double _width = 60;
|
||||
bool _is_visible = true;
|
||||
ListSortDirection? _sort_direction;
|
||||
object _custom_header;
|
||||
string _display_name;
|
||||
HorizontalAlignment _horizontal_alignment = HorizontalAlignment.Left;
|
||||
Color? _background_color_override, _font_color_override;
|
||||
FontWeight? _font_weight_override;
|
||||
|
||||
public DIYGridColumnModel(TKey id, IComparer row_comparer, IDataGridColumnFilter filter, string filter_template = null, string column_template = null) {
|
||||
this.id = id;
|
||||
this.row_comparer = row_comparer;
|
||||
this.filter = filter;
|
||||
this.filter_template = filter_template;
|
||||
this.column_template = column_template;
|
||||
_result_changed = new DeferrableActionWrapper(() => result_changed?.Invoke());
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
public event Action<double> width_changed;
|
||||
|
||||
public TKey id { get; }
|
||||
string IDIYGridColumnModel.id => id.ToString();
|
||||
public IComparer row_comparer { get; }
|
||||
public string display_name {
|
||||
get => _display_name;
|
||||
set {
|
||||
if (value != _display_name) {
|
||||
_display_name = value;
|
||||
_result_changed.execute();
|
||||
RaisePropertyChanged();
|
||||
if (_custom_header is null) {
|
||||
RaisePropertyChanged(nameof(header));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public object header => _custom_header ?? display_name;
|
||||
public object custom_header { get => _custom_header;
|
||||
set {
|
||||
if (value != _custom_header) {
|
||||
_custom_header = value;
|
||||
RaisePropertyChanged(nameof(custom_header), nameof(header));
|
||||
}
|
||||
}
|
||||
}
|
||||
public double width {
|
||||
get => _width;
|
||||
set {
|
||||
if (_set(ref _width, value, DoubleUtil.AreClose))
|
||||
width_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
public bool is_visible { get => _is_visible; set => _set(ref _is_visible, value); }
|
||||
public ListSortDirection? sort_direction { get => _sort_direction; set => Set(ref _sort_direction, value); }
|
||||
public Color? background_color_override { get => _background_color_override; set => _set(ref _background_color_override, value); }
|
||||
public Color? font_color_override { get => _font_color_override; set => _set(ref _font_color_override, value); }
|
||||
public FontWeight? font_weight_override { get => _font_weight_override; set => _set(ref _font_weight_override, value); }
|
||||
public FontFamily font_family => _fallback_font_family;
|
||||
public FontWeight font_weight => _font_weight_override ?? _fallback_font_weight;
|
||||
public Color font_color => font_color_override ?? _fallback_font_color;
|
||||
public Color background_color => background_color_override ?? _fallback_background_color;
|
||||
public double font_size => _fallback_font_size;
|
||||
public HorizontalAlignment horizontal_alignment { get => _horizontal_alignment; set => _set(ref _horizontal_alignment, value); }
|
||||
public IDataGridColumnFilter filter { get; }
|
||||
public string column_template { get; }
|
||||
public string filter_template { get; }
|
||||
|
||||
string IDIYGridColumn.id => id.ToString();
|
||||
ListSortDirection? ISortableDIYGridColumn.SortDirection { get => sort_direction; set => sort_direction = value; }
|
||||
|
||||
public GridColumnSettings create() =>
|
||||
new GridColumnSettings(
|
||||
id: id.ToString(),
|
||||
display_name: display_name,
|
||||
is_visible: is_visible,
|
||||
font_color_override: font_color_override,
|
||||
font_weight_override: font_weight_override,
|
||||
width: width,
|
||||
background_color_override: background_color_override,
|
||||
horizontal_alignment: horizontal_alignment);
|
||||
|
||||
public void set_fallback_font_params(FontFamily font_family, double font_size, Color font_color, FontWeight font_weight, Color background_color) {
|
||||
using (ReflectionHelper.detect_property_changes(this, RaisePropertyChanged, nameof(font_family), nameof(font_size), nameof(font_color), nameof(font_weight), nameof(background_color))) {
|
||||
_fallback_font_family = font_family;
|
||||
_fallback_font_size = font_size;
|
||||
_fallback_font_color = font_color;
|
||||
_fallback_background_color = background_color;
|
||||
_fallback_font_weight = font_weight;
|
||||
}
|
||||
}
|
||||
|
||||
public void load_from(GridColumnSettings value) {
|
||||
if (!value.strong_id<TKey>().Equals(id))
|
||||
throw new ArgumentException("Column id in settings object does not match the column model's id.", nameof(value));
|
||||
using (_result_changed.defer_execution())
|
||||
using (_detect_result_changed())
|
||||
using (detect_property_changes(nameof(font_color), nameof(font_weight), nameof(background_color))) {
|
||||
display_name = value.display_name;
|
||||
is_visible = value.is_visible;
|
||||
font_color_override = value.font_color_override;
|
||||
font_weight_override = value.font_weight_override;
|
||||
background_color_override = value.background_color_override;
|
||||
width = value.width;
|
||||
horizontal_alignment = value.horizontal_alignment;
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable _detect_result_changed() {
|
||||
var orig_state = create();
|
||||
return Disposable.Create(() => {
|
||||
var curr_state = create();
|
||||
if (!orig_state.Equals(curr_state))
|
||||
result_changed?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
bool _set<T>(ref T backing, T new_value, [CallerMemberName]string prop = null) =>
|
||||
_set(ref backing, new_value, EqualityComparer<T>.Default, prop);
|
||||
|
||||
bool _set<T>(ref T backing, T new_value, Func<T, T, bool> equals, [CallerMemberName] string prop = null) =>
|
||||
// Technically, the GetHashCode impl could disagree with the equals delegate.
|
||||
// However, this doesn't matter because the _set method only uses IEqualityComparer.Equals (not GetHashCode).
|
||||
// It didn't seem worth the trouble of changing the method signature, as there is no conceivable reason why that method
|
||||
// would ever be changed to use GetHashCode.
|
||||
_set(ref backing, new_value, EqualityComparer.Create(equals, a => a.GetHashCode()), prop);
|
||||
|
||||
bool _set<T>(ref T backing, T new_value, IEqualityComparer<T> comparer, [CallerMemberName]string prop = null) {
|
||||
if (Set(ref backing, new_value, comparer, prop)) {
|
||||
_result_changed.execute();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class DIYGridColumnModel<TKey, TRow, TGridFilter> : DIYGridColumnModel<TKey> where TKey : struct {
|
||||
readonly Func<TGridFilter, object> _get_column_filter_settings;
|
||||
readonly Func<TGridFilter, object, TGridFilter> _set_column_filter_settings;
|
||||
|
||||
public DIYGridColumnModel(TKey id, IComparer<TRow> row_comparer, IDataGridColumnFilter<TRow> filter, string filter_template, Func<TGridFilter, object> get_column_filter_settings, Func<TGridFilter, object, TGridFilter> set_column_filter_settings)
|
||||
: base(id, row_comparer.to_non_generic(), filter, filter_template) {
|
||||
_get_column_filter_settings = get_column_filter_settings;
|
||||
_set_column_filter_settings = set_column_filter_settings;
|
||||
this.row_comparer = row_comparer;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public new IComparer<TRow> row_comparer { get; }
|
||||
|
||||
public new IDataGridColumnFilter<TRow> filter { get; }
|
||||
|
||||
public void load_from(GridColumnSettings column_settings, TGridFilter grid_filter_settings) {
|
||||
if (column_settings != null)
|
||||
load_from(column_settings);
|
||||
if (_get_column_filter_settings != null && grid_filter_settings != null)
|
||||
filter?.load_state(_get_column_filter_settings(grid_filter_settings));
|
||||
}
|
||||
|
||||
public TGridFilter assign_filter_settings(TGridFilter target) {
|
||||
if (_set_column_filter_settings != null) {
|
||||
var filter_settings = filter.get_state();
|
||||
target = _set_column_filter_settings(target, filter_settings);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts;
|
||||
using bts.collections;
|
||||
using bts.utils;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui.controls.adorners;
|
||||
@@ -867,6 +868,83 @@ namespace mbts.ui.controls {
|
||||
public static void SetColumnKey(DependencyObject d, object value) => d.SetValue(ColumnKeyProperty, value);
|
||||
#endregion
|
||||
|
||||
#region Column
|
||||
public static readonly DependencyProperty ColumnProperty =
|
||||
DependencyProperty.RegisterAttached("Column", typeof(object), typeof(DIYGrid), new PropertyMetadata());
|
||||
|
||||
public static object GetColumn(DependencyObject d) => d.GetValue(ColumnProperty);
|
||||
public static void SetColumn(DependencyObject d, object value) => d.SetValue(ColumnProperty, value);
|
||||
#endregion
|
||||
|
||||
#region ColumnModels
|
||||
public static readonly DependencyProperty ColumnModelsProperty =
|
||||
DependencyProperty.RegisterAttached("ColumnModels", typeof(IReadOnlyObservableCollection<IDIYGridColumn>),
|
||||
typeof(DIYGrid),
|
||||
new PropertyMetadata(null, OnColumnModelsChanged));
|
||||
|
||||
static void OnColumnModelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
IDisposable hook = null;
|
||||
if (e.NewValue is IEnumerable<IDIYGridColumnModel> col_models) {
|
||||
resort();
|
||||
if (e.NewValue is INotifyCollectionChanged col_models_notifier) {
|
||||
hook = CollectionHelper.buffer_collection_changed_callback(col_models_notifier, resort, TimeSpan.FromSeconds(0.3), TimeSpan.Zero);
|
||||
}
|
||||
void resort() {
|
||||
if (d is DIYGrid grid) {
|
||||
var cols = grid.Columns;
|
||||
if (cols != null) {
|
||||
var col_map = cols.ToDictionary(c => c.id);
|
||||
var sorted_cols = col_models.Select(cm => {
|
||||
if (col_map.TryGetValue(cm.id, out var cb))
|
||||
return cb;
|
||||
if (cm is IDIYGridColumnFactory col_factory) {
|
||||
cb = col_factory.create_view();
|
||||
} else if (!string.IsNullOrEmpty(cm.column_template)) {
|
||||
cb = grid.TryFindResource(cm.column_template) as ColumnBase;
|
||||
}
|
||||
if (cb is null) {
|
||||
cb = new DIYGridColumn { id = cm.id };
|
||||
BindingOperations.SetBinding(cb, ColumnBase.HeaderProperty, new Binding(nameof(DIYGridColumnModel<int>.header)) { Source = cm });
|
||||
BindingOperations.SetBinding(cb, ColumnBase.SortDirectionProperty, new Binding(nameof(DIYGridColumnModel<int>.sort_direction)) { Source = cm, Mode = BindingMode.TwoWay });
|
||||
cb.set_model(cm);
|
||||
BindingOperations.SetBinding(cb, ColumnBase.IsVisibleProperty, new Binding(nameof(DIYGridColumnModel<int>.is_visible)) { Source = cm });
|
||||
if (cm is IFilterableColumn fc) {
|
||||
DataGridFilterProperties.SetFilter(cb, fc.filter);
|
||||
DataGridFilterProperties.SetFilterTemplate(cb, grid.FindResource(fc.filter_template) as DataTemplate);
|
||||
BindingOperations.SetBinding(cb, DataGridFilterProperties.FilterActiveProperty, new Binding(nameof(IDataGridColumnFilter.active)) { Source = fc.filter });
|
||||
BindingOperations.SetBinding(cb, DataGridFilterProperties.FilterOpenProperty, new Binding(nameof(IDataGridColumnFilter.open)) { Source = fc.filter });
|
||||
}
|
||||
}
|
||||
return cb;
|
||||
}).ToList();
|
||||
CollectionHelper.sync_with_never_duplicate(cols, sorted_cols);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetColumnModelsHooks(d, hook);
|
||||
}
|
||||
|
||||
public static IReadOnlyObservableCollection<IDIYGridColumn> GetColumnModels(DependencyObject d) =>
|
||||
d.GetValue(ColumnModelsProperty) as IReadOnlyObservableCollection<IDIYGridColumn>;
|
||||
|
||||
public static void SetColumnModels(DependencyObject d, IReadOnlyObservableCollection<IDIYGridColumn> value) =>
|
||||
d.SetValue(ColumnModelsProperty, value);
|
||||
|
||||
static readonly DependencyPropertyKey ColumnModelsHooksPropertyKey =
|
||||
DependencyProperty.RegisterAttachedReadOnly("ColumnModelsHooks", typeof(IDisposable),
|
||||
typeof(DIYGrid),
|
||||
new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
|
||||
|
||||
internal static DependencyProperty ColumnModelsHooksProperty => ColumnModelsHooksPropertyKey.DependencyProperty;
|
||||
|
||||
internal static IDisposable GetColumnModelsHooks(DependencyObject d) =>
|
||||
d.GetValue(ColumnModelsHooksProperty) as IDisposable;
|
||||
|
||||
static void SetColumnModelsHooks(DependencyObject d, IDisposable value) =>
|
||||
d.SetValue(ColumnModelsHooksPropertyKey, value);
|
||||
#endregion
|
||||
|
||||
#region SyncWithColumnGeometrySource
|
||||
public static readonly DependencyProperty SyncWithColumnGeometrySourceProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.utils;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
@@ -121,4 +122,111 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleGridSettingsManagerVM<TColumnKey> : ViewModelBase, IObserver<GridSettings> where TColumnKey : struct {
|
||||
readonly IReadOnlyList<DIYGridColumnModel<TColumnKey>> _columns;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
readonly DelegateProperty<double> _font_size;
|
||||
readonly DelegateProperty<SolidColorBrush> _font_color, _selection_highlight, _background_color;
|
||||
readonly IReadOnlyDictionary<string, ImmutableColumnSettingStore> _column_flags;
|
||||
readonly Action<IEnumerable<GridColumnSettings>> _sync_column_order;
|
||||
readonly Action _invalidate_rows;
|
||||
|
||||
public SimpleGridSettingsManagerVM(
|
||||
IReadOnlyList<DIYGridColumnModel<TColumnKey>> columns,
|
||||
IReadOnlyDictionary<string, GridColumnSettingsFeatureFlags> column_flags,
|
||||
Action<IEnumerable<GridColumnSettings>> sync_column_order,
|
||||
Action invalidate_rows) {
|
||||
//
|
||||
_columns = columns;
|
||||
_sync_column_order = sync_column_order;
|
||||
_invalidate_rows = invalidate_rows;
|
||||
_column_flags = (column_flags ?? DictionaryHelper.empty<string, GridColumnSettingsFeatureFlags>())
|
||||
.ToDictionary(kv => kv.Key, kv => ImmutableColumnSettingStore.create(kv.Value));
|
||||
_grid_settings = backing(nameof(grid_settings), GridSettings.Default);
|
||||
_font_family = backing(nameof(font_family), () => new FontFamily(grid_settings.font_family), nameof(grid_settings));
|
||||
_font_weight = backing(nameof(font_weight), () => grid_settings.font_weight, nameof(grid_settings));
|
||||
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
|
||||
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
|
||||
_background_color = backing(nameof(background_color), () => grid_settings.background_color.argb_color().to_solid_brush().frozen(), nameof(grid_settings));
|
||||
}
|
||||
|
||||
public GridSettings grid_settings => _grid_settings.Value;
|
||||
public FontFamily font_family => _font_family.Value;
|
||||
public FontWeight font_weight => _font_weight.Value;
|
||||
public double font_size => _font_size.Value;
|
||||
public SolidColorBrush font_color => _font_color.Value;
|
||||
public SolidColorBrush selection_highlight => _selection_highlight.Value;
|
||||
public SolidColorBrush background_color => _background_color.Value;
|
||||
|
||||
public SimpleGridSettingsVM create_vm(GridSettings settings) {
|
||||
var vm = new SimpleGridSettingsVM(_column_flags.ToDictionary(kv => kv.Key, kv => kv.Value.flags));
|
||||
vm.load_from(settings);
|
||||
return vm;
|
||||
}
|
||||
|
||||
public GridSettings create() {
|
||||
// IMPORTANT: don't overwrite current sorting
|
||||
_grid_settings.set(grid_settings.with(_columns.Select(c => {
|
||||
var col_settings = c.create();
|
||||
if (_column_flags.TryGetValue(c.id.ToString(), out ImmutableColumnSettingStore store))
|
||||
col_settings = store.apply(col_settings);
|
||||
return col_settings;
|
||||
})));
|
||||
return grid_settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will overwrite the columns' immutable-value store (unlike the IObserver<<see cref="GridSettings"/>> 'OnNext' implementation)
|
||||
/// </summary>
|
||||
public void load_from(GridSettings grid) => _load_from_core(grid, true);
|
||||
|
||||
void IObserver<GridSettings>.OnCompleted() { }
|
||||
void IObserver<GridSettings>.OnError(Exception error) { }
|
||||
void IObserver<GridSettings>.OnNext(GridSettings grid) {
|
||||
if (grid.columns != null) {
|
||||
var cols_with_applied_flags = grid.columns.ToList();
|
||||
for (int i = 0; i < cols_with_applied_flags.Count; i++) {
|
||||
if (_column_flags.TryGetValue(cols_with_applied_flags[i].id, out ImmutableColumnSettingStore store)) {
|
||||
cols_with_applied_flags[i] = store.apply(cols_with_applied_flags[i]);
|
||||
}
|
||||
}
|
||||
grid = grid.with(cols_with_applied_flags);
|
||||
}
|
||||
_load_from_core(grid, false);
|
||||
}
|
||||
|
||||
void _load_from_core(GridSettings grid, bool reset_immutable_flag_stores) {
|
||||
grid = grid ?? GridSettings.Default;
|
||||
_grid_settings.set(grid);
|
||||
foreach (var col in _columns) {
|
||||
col.set_fallback_font_params(new FontFamily(grid.font_family), grid.font_size, grid.font_color.rgb_color(), grid.font_weight, grid.background_color.argb_color());
|
||||
}
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null) {
|
||||
if (reset_immutable_flag_stores) {
|
||||
foreach (var kv in _column_flags) {
|
||||
if (grid.columns.TryGetFirst(c => c.id == kv.Key, out GridColumnSettings cs))
|
||||
kv.Value.store(cs);
|
||||
}
|
||||
}
|
||||
if (_columns != null) {
|
||||
var col_settings_id_map = grid_settings.columns.ToDictionary(cs => cs.id);
|
||||
var columns_id_map = _columns.ToDictionary(c => c.id.ToString());
|
||||
if (col_settings_id_map.Count == columns_id_map.Count && col_settings_id_map.Count == grid_settings.columns.Count)
|
||||
_sync_column_order?.Invoke(grid_settings.columns);
|
||||
// ** don't assume that columns:column_settings have a perfect 1:1 relationship (they should, but it is possible we accidentally break the settings as grids/uis change) **
|
||||
foreach (var col in _columns) {
|
||||
if (col_settings_id_map.TryGetValue(col.id.ToString(), out GridColumnSettings cs))
|
||||
col.load_from(cs);
|
||||
}
|
||||
}
|
||||
_invalidate_rows?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ namespace mbts.spark.filters {
|
||||
TimeFilter = "TimeFilterTemplate",
|
||||
DiscreteValues = "DiscreteValuesTemplate",
|
||||
DiscreteValuesCollectionView = "DiscreteValuesCollectionViewTemplate",
|
||||
SecurityCriteriaCriteria = "SecurityCriteriaCriteria",
|
||||
EnumValues = "EnumValuesTemplate",
|
||||
Regex = "RegexTemplate",
|
||||
YesNo = "YesNoTemplate";
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace mbts.spark.filters {
|
||||
ICommand cancel { get; }
|
||||
bool filter(object row);
|
||||
event Action filter_changed;
|
||||
void load_state(object state);
|
||||
object get_state();
|
||||
}
|
||||
|
||||
interface IDataGridColumnFilter<in TRow> : IDataGridColumnFilter {
|
||||
@@ -20,6 +22,6 @@ namespace mbts.spark.filters {
|
||||
|
||||
interface IDataGridColumnPersistentFilter<in TRow, TState> : IDataGridColumnFilter<TRow> {
|
||||
void load_state(TState state);
|
||||
TState get_state();
|
||||
new TState get_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -53,6 +54,15 @@ namespace mbts.spark.filters {
|
||||
_raise_filter_changed();
|
||||
}
|
||||
|
||||
object IDataGridColumnFilter.get_state() =>
|
||||
get_state();
|
||||
|
||||
void IDataGridColumnFilter.load_state(object state) {
|
||||
if (state is TState fstate)
|
||||
load_state(fstate);
|
||||
else if (state != null) throw ExceptionBuilder.Argument.not_of_type<TState>(nameof(state), state?.GetType());
|
||||
}
|
||||
|
||||
protected abstract bool _get_active();
|
||||
protected abstract bool _filter(TValue value);
|
||||
protected abstract void _load_state_internal(TState state);
|
||||
@@ -111,4 +121,4 @@ namespace mbts.spark.filters {
|
||||
bool IDataGridColumnFilter.filter(object row) =>
|
||||
filter(ConvertUtils.try_convert<TRow>(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using bts.api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark.filters {
|
||||
class SecurityCriteriaFilter<TRow> : FilterVMBase<TRow, SecurityImmutable, SecurityCriteria>, IDisposable {
|
||||
@@ -43,4 +45,50 @@ namespace mbts.spark.filters {
|
||||
void _set_default_values() =>
|
||||
_security_criteria = model.result_criteria;
|
||||
}
|
||||
}
|
||||
|
||||
class SecurityCriteriaCriteriaFilter<TRow> : FilterVMBase<TRow, SecurityCriteria, SecurityCriteria>, IDisposable {
|
||||
SecurityCriteria _security_criteria;
|
||||
|
||||
public SecurityCriteriaCriteriaFilter(Func<TRow, SecurityCriteria> get_row_val, string prop_name)
|
||||
: base(get_row_val, prop_name) { }
|
||||
|
||||
public ContractPickerModel model { get; }
|
||||
|
||||
public void Dispose() =>
|
||||
model.security_criteria_clause_changed -= _on_security_criteria_clause_changed;
|
||||
|
||||
public override SecurityCriteria get_state() =>
|
||||
_security_criteria;
|
||||
|
||||
protected override bool _filter(SecurityCriteria value) {
|
||||
if ((_security_criteria?.clauses).is_null_or_empty())
|
||||
return true;
|
||||
if (!_security_criteria.is_defined())
|
||||
return true;
|
||||
if ((value?.clauses).is_null_or_empty())
|
||||
return false;
|
||||
var result = value.clauses.Any(c => c.is_defined && _security_criteria.clauses.Any(fclause => c.is_subset_of(fclause)));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void _load_state_internal(SecurityCriteria state) =>
|
||||
_security_criteria = state;
|
||||
|
||||
protected override void _clear() {
|
||||
_close();
|
||||
using (_clear_properties())
|
||||
_set_default_values();
|
||||
}
|
||||
|
||||
protected override bool _get_active() =>
|
||||
_security_criteria != null && _security_criteria.clauses.Length > 0;
|
||||
|
||||
void _on_security_criteria_clause_changed(object sender, SecurityCriteriaClauseChangedEventsArgs e) {
|
||||
_security_criteria = model.result_criteria;
|
||||
_raise_filter_changed();
|
||||
}
|
||||
|
||||
void _set_default_values() =>
|
||||
_security_criteria = model.result_criteria;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
@@ -60,16 +61,24 @@ namespace mbts.spark.filters {
|
||||
public TCompoundState get_state() =>
|
||||
_get_state(_filter1.get_state(), _filter2.get_state());
|
||||
|
||||
object IDataGridColumnFilter.get_state() =>
|
||||
get_state();
|
||||
|
||||
public void load_state(TCompoundState state) {
|
||||
_filter1.load_state(_get_filter1_state(state));
|
||||
_filter2.load_state(_get_filter2_state(state));
|
||||
}
|
||||
|
||||
void IDataGridColumnFilter.load_state(object state) {
|
||||
if (state is TCompoundState cstate)
|
||||
load_state(cstate);
|
||||
else throw ExceptionBuilder.Argument.not_of_type<TCompoundState>(nameof(state), state?.GetType());
|
||||
}
|
||||
|
||||
protected void _raise_request_update() =>
|
||||
RequestUpdate?.Invoke();
|
||||
|
||||
|
||||
bool IDataGridColumnFilter.filter(object row) =>
|
||||
filter(ConvertUtils.try_convert<TRow>(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,18 @@
|
||||
/>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="{x:Static filters:DataGridFilterKnownTemplates.SecurityCriteriaCriteria}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<msp:ContractPickerView DataContext="{Binding model}">
|
||||
|
||||
</msp:ContractPickerView>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="AlgoTypeTemplate">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
class ColorToBrushConverter : IValueConverter {
|
||||
public static ColorToBrushConverter Instance => new ColorToBrushConverter();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is Color color) return new SolidColorBrush(color);
|
||||
if (value is BTSColor bts_color) return new SolidColorBrush(bts_color);
|
||||
|
||||
@@ -64,25 +64,6 @@ namespace mbts.ui {
|
||||
}
|
||||
}, can_execute == null ? null : new Func<object, bool>(_ => can_execute()));
|
||||
|
||||
public static IDialogCommand export_csv(
|
||||
Func<string> get_file_content,
|
||||
string source_window_name) =>
|
||||
new AnonymousFileDialogCommand<object>(_ =>
|
||||
_create_save_file_dialog(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
$"BTS_Spark_{source_window_name}_{DateTime.Now.ToString("yyyyMMdd_HHmm")}.csv",
|
||||
new[] { ("CSV (comma delimited)", new string[] { "csv" }) }
|
||||
),
|
||||
(_, file) => {
|
||||
try {
|
||||
File.WriteAllText(file, get_file_content.Invoke());
|
||||
} catch (Exception ex) {
|
||||
BTSMessageBox.Show($"Error exporting data to csv: {ex}.", "CSV Export Error", BTSMessageBoxButtons.OK, System.Windows.MessageBoxImage.Error, true, false, true);
|
||||
}
|
||||
},
|
||||
// we are always able to perform an export, even when no rows (it will just be a header)
|
||||
(_) => true);
|
||||
|
||||
static TDialog _create_file_dialog<TDialog>(string initial_dir = null, IReadOnlyList<(string name, string[] extensions)> filter_items = null) where TDialog : FileDialog, new() {
|
||||
var rv = new TDialog();
|
||||
if (!string.IsNullOrEmpty(initial_dir))
|
||||
|
||||
@@ -9,19 +9,13 @@ using System.Reactive.Linq;
|
||||
namespace mbts.spark {
|
||||
[ProtoContract]
|
||||
class SecurityCriteria : IEquatable<SecurityCriteria> {
|
||||
public SecurityCriteria()
|
||||
{
|
||||
public SecurityCriteria() {
|
||||
clauses = new SecurityCriteriaClause[] { };
|
||||
}
|
||||
|
||||
public SecurityCriteria(IEnumerable<SecurityCriteriaClause> c)
|
||||
{
|
||||
public SecurityCriteria(IEnumerable<SecurityCriteriaClause> c) {
|
||||
clauses = c.ToArray();
|
||||
}
|
||||
internal SecurityCriteria(IEnumerable<SecurityImmutable> securities) {
|
||||
clauses = new[] { new SecurityCriteriaClause(securities) };
|
||||
}
|
||||
internal SecurityCriteria(params SecurityImmutable[] securities) : this(securities.AsEnumerable()) { }
|
||||
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteriaClause[] clauses;
|
||||
@@ -31,8 +25,11 @@ namespace mbts.spark {
|
||||
new SecurityCriteriaClause (security_keys: keys.ToArray())
|
||||
});
|
||||
|
||||
internal static SecurityCriteria from_securities(ISet<SecurityImmutable> securities) =>
|
||||
new SecurityCriteria(securities ?? Enumerable.Empty<SecurityImmutable>());
|
||||
internal static SecurityCriteria from_securities(params SecurityImmutable[] securities) =>
|
||||
from_securities(securities.AsEnumerable());
|
||||
|
||||
internal static SecurityCriteria from_securities(IEnumerable<SecurityImmutable> securities) =>
|
||||
new SecurityCriteria(new[] { SecurityCriteriaClause.from_securities(securities) });
|
||||
|
||||
internal static Func<SecurityCriteria, IObservable<IEnumerable<SecurityImmutable>>> get_to_securities(IBus bus) =>
|
||||
new Func<SecurityCriteria, IObservable<IEnumerable<SecurityImmutable>>>(sc => to_securities(sc, bus));
|
||||
@@ -61,8 +58,27 @@ namespace mbts.spark {
|
||||
this.pretty_name = pretty_name;
|
||||
}
|
||||
|
||||
public SecurityCriteriaClause(IEnumerable<SecurityImmutable> securities)
|
||||
: this(security_keys: securities.Select(s => s.Id).ToArray()) { }
|
||||
public static SecurityCriteriaClause from_securities(IEnumerable<SecurityImmutable> securities) {
|
||||
var num_secs = securities.SoftCount();
|
||||
uint[] keys;
|
||||
string pretty_name = null;
|
||||
if (num_secs.HasValue) {
|
||||
keys = new uint[num_secs.Value];
|
||||
int i = 0;
|
||||
if (keys.Length == 1) {
|
||||
foreach (var s in securities) {
|
||||
keys[i++] = s.Id;
|
||||
pretty_name = s.ToString();
|
||||
}
|
||||
} else {
|
||||
foreach (var s in securities)
|
||||
keys[i++] = s.Id;
|
||||
}
|
||||
} else {
|
||||
keys = securities.Select(s => s.Id).ToArray();
|
||||
}
|
||||
return new SecurityCriteriaClause(security_keys: keys, pretty_name: pretty_name);
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
public readonly string pretty_name;
|
||||
|
||||
@@ -24,6 +24,12 @@ namespace mbts.spark {
|
||||
return self == null ? all_secs : all_secs.Values.Where(s => ContractPickerModel.security_in_criteria(self, s)).ToDictionary(key => key.Id);
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyDictionary<uint,SecurityImmutable>> securities(this SecurityCriteria self, IBus bus, TimeSpan timeout) {
|
||||
var md_map = await bus.subscribe(SparkTags.SecuritiesTag).FirstOrDefaultAsync(timeout, DictionaryHelper.empty<uint, SecurityMD>());
|
||||
var all_secs = md_map.Values.Select(md => md.security).ToDictionary(key => key.Id);
|
||||
return self == null ? all_secs : all_secs.Values.Where(s => ContractPickerModel.security_in_criteria(self, s)).ToDictionary(key => key.Id);
|
||||
}
|
||||
|
||||
public static IObservable<IEnumerable<SecurityImmutable>> subscribe(this SecurityCriteria self, IBus bus) {
|
||||
var md_source = bus.subscribe(SparkTags.SecuritiesTag);
|
||||
return self == null ? md_source.Select(kv => kv.Values.Select(smd => smd.security)) :
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace mbts.ui {
|
||||
interface IValueChangedEventArgs<out T> {
|
||||
T value { get; }
|
||||
}
|
||||
|
||||
static class ValueChangedEventArgs {
|
||||
public static IValueChangedEventArgs<T> create<T>(Func<T> get_value) =>
|
||||
new LazyImpl<T>(new Lazy<T>(get_value));
|
||||
|
||||
class LazyImpl<T> : IValueChangedEventArgs<T> {
|
||||
readonly Lazy<T> _value;
|
||||
public LazyImpl(Lazy<T> value) =>
|
||||
_value = value;
|
||||
|
||||
public T value => _value.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,13 +260,13 @@ namespace mbts.co2 {
|
||||
}
|
||||
|
||||
// If Size is wrong remember to update from
|
||||
[StructLayout(LayoutKind.Sequential, Size = 704)]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 712)]
|
||||
internal struct ColdEntry {
|
||||
public int exchange_status;
|
||||
// Last trade
|
||||
public AtomicPriceSize last_trade;
|
||||
public ulong total_volume;
|
||||
public utils.LLDateTime last_trade_time;
|
||||
public utils.LLDateTime last_trade_time;
|
||||
|
||||
//// Settlement
|
||||
public UInt64 misc_timestamp;
|
||||
|
||||
@@ -24,7 +24,7 @@ static class ExchangeSpecificUitlities{
|
||||
}
|
||||
|
||||
public static bool is_ice_lots(SecurityImmutable sec){
|
||||
return sec.Ice.MarketDescription.Contains("ICE Lots");
|
||||
return sec.Ice.IsIceLots;
|
||||
}
|
||||
|
||||
public static bool is_tas(SecurityImmutable sec){
|
||||
@@ -33,25 +33,36 @@ static class ExchangeSpecificUitlities{
|
||||
|
||||
#endregion
|
||||
|
||||
#region CPP
|
||||
#region CPP
|
||||
public static double get_future_cpp (SecurityImmutable sec){
|
||||
if(sec.Symbol == "HHD")
|
||||
// only futures in HHD are the dailies, everything
|
||||
// else is a strip or spread
|
||||
return 1;
|
||||
// TODO, this should be part of the security
|
||||
if(sec.Symbol == "HHD")
|
||||
return 2500;
|
||||
if(is_ice_lots(sec))
|
||||
return 2500;
|
||||
else{
|
||||
var days_in_month = DateTime.DaysInMonth(Convert.ToInt32(sec.Future.ExpirationYear),
|
||||
Convert.ToInt32(sec.Future.ExpirationMonth));
|
||||
return days_in_month; // because the size is multiplied by 2500 already
|
||||
// other futures - HNG, HHD, HEN - are like daily
|
||||
// strips
|
||||
UInt32 days_in_month = 31;
|
||||
var mm = sec.Future.ExpirationMonth;
|
||||
if(mm <=11)
|
||||
days_in_month = Convert.ToUInt32(
|
||||
DateTime.DaysInMonth(
|
||||
Convert.ToInt32(sec.Future.ExpirationYear),
|
||||
// expects 1-12
|
||||
Convert.ToInt32(sec.Future.ExpirationMonth+1)));
|
||||
else
|
||||
// TODO : sometimes Jan is 12 !
|
||||
days_in_month = 31;
|
||||
return days_in_month * 2500;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static double get_spread_cpp(SecurityImmutable sec){
|
||||
// for purpose of pnl we are 0'ing out the spread pnl
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public static double get_strip_cpp(PNLUnitImmutable pnl,
|
||||
SecurityImmutable sec){
|
||||
// add up all the legs
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.24.3" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
|
||||
<PackageReference Include="System.Reactive" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -77,16 +77,17 @@ namespace bts.utils {
|
||||
|
||||
public static int auto_phase(params int[] hash_codes) =>
|
||||
auto_phase(Array.AsReadOnly(hash_codes));
|
||||
|
||||
|
||||
public static int auto_phase<T>(IEnumerable<T> items) =>
|
||||
auto_phase(items.Take(_max_auto_phase_buckets).ToList());
|
||||
|
||||
public static int auto_phase<T>(IReadOnlyList<T> hash_codes) {
|
||||
public static int auto_phase<T>(IReadOnlyList<T> hash_codes, int bit_span = 32) {
|
||||
Log.assert(bit_span > 0 && bit_span <= 32);
|
||||
// don't divide by zero
|
||||
if (hash_codes.Count == 0)
|
||||
return 0;
|
||||
int size = Math.Min(hash_codes.Count, _max_auto_phase_buckets);
|
||||
int bits_per_hash = 32 / size;
|
||||
int bits_per_hash = bit_span / size;
|
||||
int phase = 1 << (bits_per_hash - 1);
|
||||
int rv = hash_codes[0].GetHashCode() % phase;
|
||||
/// The idea is to split the 32 to bits into evenly sized buckets for each hash code item.
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace bts {
|
||||
Plugin,
|
||||
Sdk,
|
||||
Http,
|
||||
iLink3
|
||||
// Please keep this list in sync with logging.h/logging.cpp
|
||||
}
|
||||
|
||||
|
||||
@@ -252,6 +252,17 @@ namespace System.Collections.Generic {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetFirstIndex<T>(this IEnumerable<T> source, Func<T, bool> predicate, out int index) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
index = 0;
|
||||
foreach (T element in source) {
|
||||
if (predicate(element))
|
||||
return true;
|
||||
index++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T>(this IEnumerable<T> source, out T result) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
using (var en = source.GetEnumerator()) {
|
||||
@@ -716,4 +727,4 @@ namespace System.Collections.Generic {
|
||||
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace bts.collections {
|
||||
static class ImmutableDictionaryExtensions {
|
||||
/// <summary>
|
||||
/// Adds the specified key/value pair if the dictionary does not already contain the key, otherwise replaces the value for that key.
|
||||
/// </summary>
|
||||
/// <returns><see cref="true"/> if a new key was added; otherwise <see cref="false"/>.</returns>
|
||||
public static bool AddOrSet<TKey, TValue>(this ImmutableDictionary<TKey, TValue> dictionary, TKey key, TValue value, out ImmutableDictionary<TKey, TValue> result) {
|
||||
int orig_count = dictionary.Count;
|
||||
result = dictionary.SetItem(key, value);
|
||||
return result.Count != orig_count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the entry with specified key.
|
||||
/// </summary>
|
||||
/// <returns><see cref="true"/> if the entry was removed; otherwise <see cref="false"/>.</returns>
|
||||
public static bool Remove<TKey, TValue>(this ImmutableDictionary<TKey, TValue> dictionary, TKey key, out ImmutableDictionary<TKey, TValue> result) {
|
||||
result = dictionary.Remove(key);
|
||||
return result.Count != dictionary.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,5 +25,21 @@ namespace bts.collections {
|
||||
bd.Remove(v);
|
||||
return bd.ToImmutable();
|
||||
}
|
||||
|
||||
public static bool Add<T>(this ImmutableHashSet<T> source, T value, out ImmutableHashSet<T> result) {
|
||||
int orig_count = source.Count;
|
||||
result = source.Add(value);
|
||||
return result.Count != orig_count;
|
||||
}
|
||||
|
||||
public static ImmutableHashSet<T> AddRange<T>(this ImmutableHashSet<T> source, IEnumerable<T> value) {
|
||||
if (source is null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (value is null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
var bd = source.ToBuilder();
|
||||
bd.AddRange(value);
|
||||
return bd.ToImmutable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,15 @@ namespace bts.collections {
|
||||
list.Insert(i < 0 ? ~i : i, value);
|
||||
}
|
||||
|
||||
public static void InsertOrReplaceSorted<T>(this IList<T> list, T item, IComparer<T> comparer) {
|
||||
int i = CollectionHelper.binary_search(list, item, comparer);
|
||||
if (i < 0) {
|
||||
list.Insert(~i, item);
|
||||
} else {
|
||||
list[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool RemoveSorted<T>(this IList<T> list, T item, IComparer<T> comparer) {
|
||||
int i = CollectionHelper.binary_search(list, item, comparer);
|
||||
if (i < 0)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace bts.collections {
|
||||
interface IReadOnlyIndex<TKey, TValue> {
|
||||
TValue this[TKey key] { get; }
|
||||
}
|
||||
|
||||
static class FunctionalIndex {
|
||||
public static FunctionalIndex<TKey, TValue> create<TKey, TValue>(Func<TKey, TValue> get_value) =>
|
||||
new FunctionalIndex<TKey, TValue>(get_value);
|
||||
}
|
||||
|
||||
class FunctionalIndex<TKey, TValue> : IReadOnlyIndex<TKey, TValue> {
|
||||
readonly Func<TKey, TValue> _get_value;
|
||||
|
||||
public FunctionalIndex(Func<TKey, TValue> get_value) =>
|
||||
_get_value = get_value ?? throw new ArgumentNullException(nameof(get_value));
|
||||
|
||||
public TValue this[TKey key] => _get_value(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.utils {
|
||||
public static class CallbackBuffer {
|
||||
public static IDisposable buffer_callback(Func<Action, IDisposable> attach_callback, Action callback, TimeSpan delay_before_callback, TimeSpan delay_after_callback) {
|
||||
bool is_disposed = false, is_busy = false, dirty = false;
|
||||
object locker = new object();
|
||||
var detach = attach_callback(callback_buffer_impl);
|
||||
return Disposable.Create(() => {
|
||||
lock (locker) {
|
||||
if (is_disposed)
|
||||
return;
|
||||
is_disposed = true;
|
||||
dirty = false;
|
||||
}
|
||||
Disposable.dispose(ref detach);
|
||||
});
|
||||
async void callback_buffer_impl() {
|
||||
lock (locker) {
|
||||
if (is_busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
is_busy = true;
|
||||
}
|
||||
while (true) {
|
||||
if (delay_before_callback > TimeSpan.Zero) {
|
||||
await Task.Delay(delay_before_callback);
|
||||
}
|
||||
lock (locker) {
|
||||
if (is_disposed) {
|
||||
is_busy = false;
|
||||
dirty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
callback();
|
||||
if (delay_after_callback > TimeSpan.Zero) {
|
||||
await Task.Delay(delay_after_callback);
|
||||
}
|
||||
lock (locker) {
|
||||
if (!dirty) {
|
||||
is_busy = false;
|
||||
break;
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable buffer_async_callback(Func<Action, IDisposable> attach_callback, Func<Task> async_callback) {
|
||||
bool is_disposed = false, is_busy = false, dirty = false;
|
||||
object locker = new object();
|
||||
var detach = attach_callback(callback_buffer_impl);
|
||||
return Disposable.Create(() => {
|
||||
lock (locker) {
|
||||
if (is_disposed)
|
||||
return;
|
||||
is_disposed = true;
|
||||
dirty = false;
|
||||
}
|
||||
Disposable.dispose(ref detach);
|
||||
});
|
||||
async void callback_buffer_impl() {
|
||||
lock (locker) {
|
||||
if (is_busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
is_busy = true;
|
||||
}
|
||||
while (true) {
|
||||
await async_callback();
|
||||
lock (locker) {
|
||||
if (!dirty || is_disposed) {
|
||||
is_busy = false;
|
||||
break;
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.utils {
|
||||
static class CollectionHelper {
|
||||
@@ -77,6 +79,30 @@ namespace bts.utils {
|
||||
return ~hi;
|
||||
}
|
||||
|
||||
public static IDisposable buffer_collection_changed_callback(INotifyCollectionChanged source, Action callback, TimeSpan delay_before_callback, TimeSpan delay_after_callback) {
|
||||
// return CallbackBuffer.buffer_callback(attach_callback, callback, delay_before_callback, delay_after_callback);
|
||||
// IDisposable attach_callback(Action cb) {
|
||||
// source.CollectionChanged += on_collection_changed;
|
||||
// return Disposable.Create(() => source.CollectionChanged -= on_collection_changed);
|
||||
// void on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||
// cb();
|
||||
// }
|
||||
return CallbackBuffer.buffer_async_callback(attach_callback, impl);
|
||||
IDisposable attach_callback(Action cb) {
|
||||
source.CollectionChanged += on_collection_changed;
|
||||
return Disposable.Create(() => source.CollectionChanged -= on_collection_changed);
|
||||
void on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||
cb();
|
||||
}
|
||||
async Task impl() {
|
||||
if (delay_before_callback > TimeSpan.Zero)
|
||||
await Task.Delay(delay_before_callback);
|
||||
callback();
|
||||
if (delay_after_callback > TimeSpan.Zero)
|
||||
await Task.Delay(delay_after_callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void reflect_change<T, TData>(IList<T> target, IEnumerable<TData> source, Func<TData, T> create_item, Action<T> on_removed, NotifyCollectionChangedEventArgs e) {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.utils.csv {
|
||||
public static class CsvHelper {
|
||||
|
||||
public static string ToCsv<T>(this IEnumerable<T> items, OrderedDictionary properties = null) where T : class {
|
||||
var output = "";
|
||||
var delimiter = ",";
|
||||
if (properties == null) {
|
||||
properties = new OrderedDictionary();
|
||||
foreach (var pi in typeof(T).GetProperties()) {
|
||||
properties.Add(pi.Name, pi);
|
||||
}
|
||||
}
|
||||
|
||||
using (var sw = new StringWriter()) {
|
||||
var dict_values = properties.Values.OfType<PropertyInfo>();
|
||||
var header = dict_values.Select(n => n.Name).Aggregate((first, second) => first + delimiter + second);
|
||||
sw.WriteLine(header);
|
||||
foreach (var i in items) {
|
||||
var row = dict_values.Select(x => x.GetValue(i, null))
|
||||
.Select(x => x == null ? "null" : x.ToString())
|
||||
.Aggregate((first, second) => first + delimiter + second);
|
||||
sw.WriteLine(row);
|
||||
}
|
||||
output = sw.ToString();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static OrderedDictionary create_ordering_maintainer(Type t, string[] property_names_in_order) {
|
||||
OrderedDictionary od = new OrderedDictionary();
|
||||
var props = t.GetProperties();
|
||||
foreach (var n in property_names_in_order) {
|
||||
var query = props.Where(pi => pi.Name == n);
|
||||
if (query.Any()) {
|
||||
var property_info = query.First();
|
||||
if (!od.Contains(property_info)) {
|
||||
od.Add(n, property_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return od;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,16 @@ namespace mbts.utils {
|
||||
public static ArgumentException unexpected_case<T>(T value, string name) =>
|
||||
new ArgumentException($"Unexpected {typeof(T)} '{value}.'", name);
|
||||
|
||||
public static ArgumentException not_of_type<T>(object value, string name) =>
|
||||
not_of_type(value, name, typeof(T));
|
||||
public static ArgumentException not_of_type<T>(string name, Type actual = null) =>
|
||||
actual is null ?
|
||||
not_of_type(name, typeof(T)) :
|
||||
not_of_type(name, typeof(T), actual);
|
||||
|
||||
public static ArgumentException not_of_type(object value, string name, Type t) =>
|
||||
new ArgumentException($"Value is not of the expected type '{t}.'");
|
||||
public static ArgumentException not_of_type(string name, Type t) =>
|
||||
new ArgumentException(Format.NotOfType(t), name);
|
||||
|
||||
public static ArgumentException not_of_type(string name, Type expected, Type actual) =>
|
||||
new ArgumentException(Format.NotOfType(expected, actual), name);
|
||||
|
||||
public static ArgumentException null_or_empty(string name) =>
|
||||
new ArgumentException("Value is null or empty.", name);
|
||||
@@ -49,6 +54,8 @@ namespace mbts.utils {
|
||||
public static string LessThanOrEqualTo(int value) => $"{Messages.LessThanOrEqualTo} {value}.";
|
||||
public static string GreaterThan(int value) => $"{Messages.GreaterThan} {value}.";
|
||||
public static string GreaterThanOrEqualTo(int value) => $"{Messages.GreaterThanOrEqualTo} {value}.";
|
||||
public static string NotOfType(Type type) => $"Argument is not of expected type '{type}.'";
|
||||
public static string NotOfType(Type expected, Type actual) => $"Argument is not of expected type '{expected}' (actually '{actual}').";
|
||||
}
|
||||
|
||||
public static class Messages {
|
||||
@@ -59,4 +66,4 @@ namespace mbts.utils {
|
||||
GreaterThanOrEqualTo = "Greater than or equal to";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +1,241 @@
|
||||
using mbts;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions {
|
||||
/// <summary>
|
||||
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
|
||||
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
|
||||
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
|
||||
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
|
||||
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
|
||||
/// <returns>An "Observable Heartbeat"</returns>
|
||||
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
|
||||
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
|
||||
IObservable<bool> alive = source.Select(_ => true);
|
||||
return dead.Merge(alive).DistinctUntilChanged();
|
||||
}
|
||||
|
||||
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
|
||||
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
|
||||
|
||||
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
|
||||
source.CombineLatest(second, (v1, v2) => (v1, v2));
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
|
||||
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
/// <summary>
|
||||
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
|
||||
/// </summary>
|
||||
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
|
||||
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
|
||||
|
||||
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
|
||||
Observable.Create<T>(o => {
|
||||
callback();
|
||||
add_handler(callback);
|
||||
return Disposable.Create(() => remove_handler(callback));
|
||||
void callback() => o.OnNext(selector());
|
||||
});
|
||||
|
||||
public static IObservable<T> FromEventThrottle<T>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, TimeSpan throttle) =>
|
||||
FromEventThrottle<T>(cb => {
|
||||
add_handler(cb);
|
||||
return Disposable.Create(() => remove_handler(cb));
|
||||
}, throttle);
|
||||
|
||||
public static IObservable<T> FromEventThrottle<T>(Func<Action<T>, IDisposable> add_handler, TimeSpan throttle) =>
|
||||
Observable.Create<T>(o => {
|
||||
T last_value = default;
|
||||
bool busy = false, dirty = false;
|
||||
return add_handler(callback);
|
||||
async void callback(T value) {
|
||||
last_value = value;
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
} else {
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
o.OnNext(last_value);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
public static IObservable<TResult> FromEventThrottle<T, TResult>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, Func<T, TResult> selector, TimeSpan throttle) =>
|
||||
FromEventThrottle(cb => {
|
||||
add_handler(cb);
|
||||
return Disposable.Create(() => remove_handler(cb));
|
||||
}, selector, throttle);
|
||||
|
||||
public static IObservable<TResult> FromEventThrottle<T, TResult>(Func<Action<T>, IDisposable> add_handler, Func<T, TResult> selector, TimeSpan throttle) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
T last_value = default;
|
||||
bool busy = false, dirty = false;
|
||||
return add_handler(callback);
|
||||
async void callback(T value) {
|
||||
last_value = value;
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
} else {
|
||||
busy = true;
|
||||
do {
|
||||
var result = selector(last_value);
|
||||
dirty = false;
|
||||
o.OnNext(result);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
|
||||
PairWithPrevious(source, default);
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
|
||||
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
|
||||
|
||||
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
|
||||
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
|
||||
if (buffer_size == 0) return source.Publish().RefCount();
|
||||
int num_subscribers = 0;
|
||||
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
|
||||
IDisposable source_sub = null;
|
||||
return Observable.Create<T>(o => {
|
||||
if (Interlocked.Increment(ref num_subscribers) == 1)
|
||||
source_sub = source.Subscribe(replay.OnNext);
|
||||
IDisposable sub = replay.Subscribe(o);
|
||||
return Disposable.Create(() => {
|
||||
sub.Dispose();
|
||||
if (Interlocked.Decrement(ref num_subscribers) == 0)
|
||||
source_sub.Dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
|
||||
/// each inner sequence is terminated when the next one begins.
|
||||
/// <para/>In other words, observers are notified by the inner observerables in "series," rather than "parallel."
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of elements in the source (outer) sequence.</typeparam>
|
||||
/// <typeparam name="TResult">The type of elements in the inner sequences.</typeparam>
|
||||
/// <param name="source">A sequence of elements to create a series of inner sequences.</param>
|
||||
/// <param name="select_next">A function to create an inner sequence.</param>
|
||||
/// <returns>An IDisposable object used to unsubscribe from the source (outer) sequence, and the latest inner sequence.</returns>
|
||||
public static IObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
object locker = new object();
|
||||
bool is_disposed = false;
|
||||
IDisposable inner_sub = null;
|
||||
IDisposable outer_sub = source.Subscribe(on_outer);
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void on_outer(TSource value) {
|
||||
lock (locker) {
|
||||
if (!is_disposed) {
|
||||
inner_sub?.Dispose();
|
||||
inner_sub = select_next(value)?.Subscribe(o.OnNext);
|
||||
}
|
||||
}
|
||||
}
|
||||
void dispose() {
|
||||
IDisposable temp_outer_sub = null, temp_inner_sub = null;
|
||||
lock (locker) {
|
||||
is_disposed = true;
|
||||
temp_outer_sub = outer_sub;
|
||||
outer_sub = null;
|
||||
temp_inner_sub = inner_sub;
|
||||
inner_sub = null;
|
||||
}
|
||||
temp_outer_sub?.Dispose();
|
||||
temp_inner_sub?.Dispose();
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
if (can_get_value())
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) {
|
||||
return source.Subscribe(on_next, on_error, on_completed);
|
||||
// local methods
|
||||
void on_next(T value) {
|
||||
for (int i = 0; i < observers.Length; i++)
|
||||
observers[i].OnNext(value);
|
||||
}
|
||||
void on_error(Exception error) {
|
||||
for (int i = 0; i < observers.Length; i++)
|
||||
observers[i].OnError(error);
|
||||
}
|
||||
void on_completed() {
|
||||
for (int i = 0; i < observers.Length; i++)
|
||||
observers[i].OnCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
|
||||
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the source sequence.</typeparam>
|
||||
/// <param name="source">A sequence of elements to invoke a subscribe function on.</param>
|
||||
/// <param name="subscribe_value">A function used to subscribe each source element.</param>
|
||||
/// <returns>An IDisposable object used to unsubscribe from the source sequence, and dispose the latest value subscription.</returns>
|
||||
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
|
||||
IDisposable value_sub = null;
|
||||
IDisposable source_sub = source.Subscribe(on_source);
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void on_source(T value) {
|
||||
Disposable.dispose(ref value_sub);
|
||||
value_sub = subscribe_value(value);
|
||||
}
|
||||
void dispose() {
|
||||
Disposable.dispose(ref source_sub);
|
||||
Disposable.dispose(ref value_sub);
|
||||
}
|
||||
}
|
||||
|
||||
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
|
||||
// with structs it was even more annoying because they can't be null
|
||||
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
|
||||
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
|
||||
}
|
||||
}
|
||||
using mbts;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions {
|
||||
public static IObservable<bool> And(this IObservable<bool> source1, IObservable<bool> source2) =>
|
||||
source1.CombineLatest(source2).Select(tup => tup.Item1 && tup.Item2);
|
||||
|
||||
/// <summary>
|
||||
/// <para>Creates an "Observable Heartbeat" that will publish true/false to indicate whether or not the heartbeat is detected, it will only send updates when the 'heartbeat status' has changed.</para>
|
||||
/// <para>Whenever the source produces an update, the heartbeat evaluates to true and the heartbeat duration clock is reset.</para>
|
||||
/// <para>Whenever heartbeat duration clock expires, the heartbeat evaluates to false.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of data produced by the source (bears no impact on the resultant observable heartbeat</typeparam>
|
||||
/// <param name="source">The source that will indicate alive/dead by publishing updates within the duration period</param>
|
||||
/// <param name="duration">The amount of time that must pass without receiving an update from the source to determine the heartbeat is not detected</param>
|
||||
/// <returns>An "Observable Heartbeat"</returns>
|
||||
public static IObservable<bool> AsHeartbeat<T>(this IObservable<T> source, TimeSpan duration) {
|
||||
IObservable<bool> dead = source.Throttle(duration).Select(_ => false);
|
||||
IObservable<bool> alive = source.Select(_ => true);
|
||||
return dead.Merge(alive).DistinctUntilChanged();
|
||||
}
|
||||
|
||||
public static IObservable<T> BufferLatest<T>(this IObservable<T> source, TimeSpan throttle) =>
|
||||
source.Buffer(throttle).Where(vals => vals.Count > 0).Select(vals => vals[vals.Count - 1]);
|
||||
|
||||
public static IObservable<(T1, T2)> CombineLatest<T1, T2>(this IObservable<T1> source, IObservable<T2> second) =>
|
||||
source.CombineLatest(second, (v1, v2) => (v1, v2));
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, TimeSpan timeout) where T : class =>
|
||||
await source.Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(T result, bool success)> FirstAsyncTask<T>(this IObservable<T> source, Func<T, bool> predicate, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (val, true)).Merge(Observable.Return((default(T), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
public static Task<T> FirstOrDefaultAsync<T>(this IObservable<T> source, TimeSpan timeout, T default_value = default) {
|
||||
var cts = new CancellationTokenSource(timeout);
|
||||
var main_task = source.FirstOrDefaultAsync().ToTask(cts.Token);
|
||||
var result_task = main_task.ContinueWith(t => t.Status == TaskStatus.RanToCompletion ? t.Result : default_value);
|
||||
return result_task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A less verbose way to call <see cref="Observable.FromEvent{Action{TEventArgs}, TEventArgs}(Action{Action{TEventArgs}}, Action{Action{TEventArgs}})"/>.
|
||||
/// </summary>
|
||||
public static IObservable<T> FromAction<T>(Action<T> event_source) =>
|
||||
Observable.FromEvent<Action<T>, T>(cb => event_source += cb, cb => event_source -= cb);
|
||||
|
||||
public static IObservable<T> FromEvent<T>(Action<Action> add_handler, Action<Action> remove_handler, Func<T> selector) =>
|
||||
Observable.Create<T>(o => {
|
||||
callback();
|
||||
add_handler(callback);
|
||||
return Disposable.Create(() => remove_handler(callback));
|
||||
void callback() => o.OnNext(selector());
|
||||
});
|
||||
|
||||
public static IObservable<T> FromEventThrottle<T>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, TimeSpan throttle) =>
|
||||
FromEventThrottle<T>(cb => {
|
||||
add_handler(cb);
|
||||
return Disposable.Create(() => remove_handler(cb));
|
||||
}, throttle);
|
||||
|
||||
public static IObservable<T> FromEventThrottle<T>(Func<Action<T>, IDisposable> add_handler, TimeSpan throttle) =>
|
||||
Observable.Create<T>(o => {
|
||||
T last_value = default;
|
||||
bool busy = false, dirty = false;
|
||||
return add_handler(callback);
|
||||
async void callback(T value) {
|
||||
last_value = value;
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
} else {
|
||||
busy = true;
|
||||
do {
|
||||
dirty = false;
|
||||
o.OnNext(last_value);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
public static IObservable<TResult> FromEventThrottle<T, TResult>(Action<Action<T>> add_handler, Action<Action<T>> remove_handler, Func<T, TResult> selector, TimeSpan throttle) =>
|
||||
FromEventThrottle(cb => {
|
||||
add_handler(cb);
|
||||
return Disposable.Create(() => remove_handler(cb));
|
||||
}, selector, throttle);
|
||||
|
||||
public static IObservable<TResult> FromEventThrottle<T, TResult>(Func<Action<T>, IDisposable> add_handler, Func<T, TResult> selector, TimeSpan throttle) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
T last_value = default;
|
||||
bool busy = false, dirty = false;
|
||||
return add_handler(callback);
|
||||
async void callback(T value) {
|
||||
last_value = value;
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
} else {
|
||||
busy = true;
|
||||
do {
|
||||
var result = selector(last_value);
|
||||
dirty = false;
|
||||
o.OnNext(result);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source) =>
|
||||
PairWithPrevious(source, default);
|
||||
|
||||
public static IObservable<(TSource prev, TSource curr)> PairWithPrevious<TSource>(this IObservable<TSource> source, TSource default_value) =>
|
||||
source.Scan<TSource, (TSource prev, TSource curr)>((default_value, default_value), (last, next) => (last.curr, next));
|
||||
|
||||
public static IObservable<T> RefCountReplay<T>(this IObservable<T> source, int buffer_size) {
|
||||
if (buffer_size < 0) throw new ArgumentException("Buffer size is less than zero.", nameof(buffer_size));
|
||||
if (buffer_size == 0) return source.Publish().RefCount();
|
||||
int num_subscribers = 0;
|
||||
ReplaySubject<T> replay = new ReplaySubject<T>(buffer_size);
|
||||
IDisposable source_sub = null;
|
||||
return Observable.Create<T>(o => {
|
||||
if (Interlocked.Increment(ref num_subscribers) == 1)
|
||||
source_sub = source.Subscribe(replay.OnNext);
|
||||
IDisposable sub = replay.Subscribe(o);
|
||||
return Disposable.Create(() => {
|
||||
sub.Dispose();
|
||||
if (Interlocked.Decrement(ref num_subscribers) == 0)
|
||||
source_sub.Dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
|
||||
/// each inner sequence is terminated when the next one begins.
|
||||
/// <para/>In other words, observers are notified by the inner observerables in "series," rather than "parallel."
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of elements in the source (outer) sequence.</typeparam>
|
||||
/// <typeparam name="TResult">The type of elements in the inner sequences.</typeparam>
|
||||
/// <param name="source">A sequence of elements to create a series of inner sequences.</param>
|
||||
/// <param name="select_next">A function to create an inner sequence.</param>
|
||||
/// <returns>An IDisposable object used to unsubscribe from the source (outer) sequence, and the latest inner sequence.</returns>
|
||||
public static IObservable<TResult> SelectManyInSeries<TSource, TResult>(this IObservable<TSource> source, Func<TSource, IObservable<TResult>> select_next) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
object locker = new object();
|
||||
bool is_disposed = false;
|
||||
IDisposable inner_sub = null;
|
||||
IDisposable outer_sub = source.Subscribe(on_outer);
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void on_outer(TSource value) {
|
||||
lock (locker) {
|
||||
if (!is_disposed) {
|
||||
inner_sub?.Dispose();
|
||||
inner_sub = select_next(value)?.Subscribe(o.OnNext);
|
||||
}
|
||||
}
|
||||
}
|
||||
void dispose() {
|
||||
IDisposable temp_outer_sub = null, temp_inner_sub = null;
|
||||
lock (locker) {
|
||||
is_disposed = true;
|
||||
temp_outer_sub = outer_sub;
|
||||
outer_sub = null;
|
||||
temp_inner_sub = inner_sub;
|
||||
inner_sub = null;
|
||||
}
|
||||
temp_outer_sub?.Dispose();
|
||||
temp_inner_sub?.Dispose();
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IObservable<T> StartWith<T>(this IObservable<T> source, Func<T> get_value, Func<bool> can_get_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
if (can_get_value())
|
||||
o.OnNext(get_value());
|
||||
return source.Subscribe(o);
|
||||
});
|
||||
|
||||
public static IDisposable Subscribe<T>(this IObservable<T> source, params IObserver<T>[] observers) {
|
||||
return source.Subscribe(on_next, on_error, on_completed);
|
||||
// local methods
|
||||
void on_next(T value) {
|
||||
for (int i = 0; i < observers.Length; i++)
|
||||
observers[i].OnNext(value);
|
||||
}
|
||||
void on_error(Exception error) {
|
||||
for (int i = 0; i < observers.Length; i++)
|
||||
observers[i].OnError(error);
|
||||
}
|
||||
void on_completed() {
|
||||
for (int i = 0; i < observers.Length; i++)
|
||||
observers[i].OnCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An inner subscription is made for each value in the source sequence using the specified delegate (<paramref name="subscribe_value"/>).
|
||||
/// When a new value arrives, the subscription for the previous value is disposed before <paramref name="subscribe_value"/> is invoked with the new value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of elements in the source sequence.</typeparam>
|
||||
/// <param name="source">A sequence of elements to invoke a subscribe function on.</param>
|
||||
/// <param name="subscribe_value">A function used to subscribe each source element.</param>
|
||||
/// <returns>An IDisposable object used to unsubscribe from the source sequence, and dispose the latest value subscription.</returns>
|
||||
public static IDisposable SubscribeEach<T>(this IObservable<T> source, Func<T, IDisposable> subscribe_value) {
|
||||
IDisposable value_sub = null;
|
||||
IDisposable source_sub = source.Subscribe(on_source);
|
||||
return Disposable.Create(dispose);
|
||||
// local methods
|
||||
void on_source(T value) {
|
||||
Disposable.dispose(ref value_sub);
|
||||
value_sub = subscribe_value(value);
|
||||
}
|
||||
void dispose() {
|
||||
Disposable.dispose(ref source_sub);
|
||||
Disposable.dispose(ref value_sub);
|
||||
}
|
||||
}
|
||||
|
||||
// was sick of this scenario: observable_source.Select(enumerable => enumerable.FirstOrDefault(where)).Where(val => val != null)
|
||||
// with structs it was even more annoying because they can't be null
|
||||
public static IObservable<TResult> TrySelect<T, TResult>(this IObservable<T> source, Func<T, (TResult result, bool success)> try_select) =>
|
||||
source.Select(try_select).Where(ts => ts.success).Select(ts => ts.result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace bts.utils {
|
||||
// Mainly used as an optional override of nullable data arg.
|
||||
// Explanation: the 'with' method pattern typically interprets null args to mean "keep the existing value."
|
||||
// However, this makes it impossible for the caller to overwrite an existing value with null.
|
||||
// This struct is designed (or at least was originally) to address this use-case.
|
||||
// It is analagous to escape chars.
|
||||
readonly struct OptionalNullable<T> where T : struct {
|
||||
public OptionalNullable(T? value) => this.value = value;
|
||||
|
||||
public T? value { get; }
|
||||
|
||||
public static implicit operator OptionalNullable<T>(T? value) =>
|
||||
new OptionalNullable<T>(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
using System.IO;
|
||||
using System.Security.Policy;
|
||||
using System.Diagnostics;
|
||||
|
||||
using ExcelDna.Integration;
|
||||
using ExcelDna.IntelliSense;
|
||||
using ExcelDna.Logging;
|
||||
|
||||
namespace bts.excel
|
||||
{
|
||||
public class TestMethod
|
||||
{
|
||||
[ExcelFunction]
|
||||
static public string bts_excel_test_string(string x) { return "Hello " + x; }
|
||||
[ExcelFunction]
|
||||
static public object bts_excel_test_int(int x) { return x; }
|
||||
[ExcelFunction]
|
||||
static public object bts_excel_test_long(long x) { return x; }
|
||||
}
|
||||
|
||||
[System.Runtime.InteropServices.ComVisible(true)]
|
||||
public class ContextTeardown : ExcelComAddIn
|
||||
{
|
||||
private api.core.ServiceContext _context;
|
||||
public ContextTeardown(api.core.ServiceContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
//This is called on application shutdown
|
||||
public override void OnBeginShutdown(ref Array custom)
|
||||
{
|
||||
_context.Dispose();
|
||||
_context = null;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Runtime.InteropServices.ComVisible(true)]
|
||||
public class ExcelAddin : IExcelAddIn
|
||||
{
|
||||
public void AutoClose()
|
||||
{
|
||||
//don't do anything here, it's only called when the addin is removed, not on shutdown
|
||||
IntelliSenseServer.Uninstall();
|
||||
}
|
||||
public void AutoOpen() //This is called on EXCEL startup, when the addin is loaded
|
||||
{
|
||||
Trace.Listeners.Add(new LogDisplayTraceListener("bts"));
|
||||
IntelliSenseServer.Install();
|
||||
ExcelIntegration.RegisterUnhandledExceptionHandler(ex => ex.ToString());
|
||||
|
||||
load_assemblies();
|
||||
ExcelComAddInHelper.LoadComAddIn(new ContextTeardown(APIDispatch.service_context.Value));
|
||||
}
|
||||
|
||||
private void load_assemblies()
|
||||
{
|
||||
var config = System.Configuration.ConfigurationManager.AppSettings["load_assemblies"];
|
||||
if (config != null)
|
||||
{
|
||||
var asses = config.Split(new char[] { ';', ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var ass in asses)
|
||||
Utilities.bts_load_assembly(ass);
|
||||
}
|
||||
}
|
||||
|
||||
public static void calculate_full_rebuild()
|
||||
{
|
||||
object app = ExcelDnaUtil.Application;
|
||||
var mi = app?.GetType().GetMethod("CalculateFullRebuild", BindingFlags.Instance | BindingFlags.Public);
|
||||
if(mi != null)
|
||||
mi.Invoke(app, null);
|
||||
}
|
||||
|
||||
public static void calculate_full()
|
||||
{
|
||||
object app = ExcelDnaUtil.Application;
|
||||
var mi = app?.GetType().GetMethod("CalculateFull", BindingFlags.Instance | BindingFlags.Public);
|
||||
if (mi != null)
|
||||
mi.Invoke(app, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("spark")]
|
||||
[assembly: InternalsVisibleTo("spark_core")]
|
||||
[assembly: InternalsVisibleTo("sparkcmd")]
|
||||
[assembly: InternalsVisibleTo("spark_test")]
|
||||
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("b319f835-7390-47e5-8093-461ed0dfce63")]
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="ExcelDnaProps"> -->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!--
|
||||
If you change properties in this file, they may not come into effect until you:
|
||||
* Rebuild the solution/project
|
||||
|
||||
or
|
||||
|
||||
* Close Visual Studio
|
||||
* Delete .vs folder, if exists
|
||||
* Delete ProjectName.csproj.user (or equivalent for VB, F#, etc.), if exists
|
||||
* Delete SolutionName.suo, if exists
|
||||
* Open your solution/project again in Visual Studio
|
||||
-->
|
||||
|
||||
<!--
|
||||
Configuration properties for building .dna files
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
Enable/Disable automatic generation of platform-specific versions of .dna files
|
||||
-->
|
||||
<ExcelDnaCreate32BitAddIn Condition="'$(ExcelDnaCreate32BitAddIn)' == ''">false</ExcelDnaCreate32BitAddIn>
|
||||
<ExcelDnaCreate64BitAddIn Condition="'$(ExcelDnaCreate64BitAddIn)' == ''">true</ExcelDnaCreate64BitAddIn>
|
||||
|
||||
<!--
|
||||
Define the suffix used for each platform-specific file e.g. MyAddIn64.dna
|
||||
-->
|
||||
<ExcelDna32BitAddInSuffix Condition="'$(ExcelDna32BitAddInSuffix)' == ''"></ExcelDna32BitAddInSuffix>
|
||||
<ExcelDna64BitAddInSuffix Condition="'$(ExcelDna64BitAddInSuffix)' == ''">64</ExcelDna64BitAddInSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Configuration properties for packing .dna files
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
Enable/Disable packing of .dna files
|
||||
-->
|
||||
<RunExcelDnaPack Condition="'$(RunExcelDnaPack)' == ''">true</RunExcelDnaPack>
|
||||
|
||||
<!--
|
||||
Suffix used for packed .xll files e.g. MyAddIn-packed.xll
|
||||
-->
|
||||
<ExcelDnaPackXllSuffix Condition="'$(ExcelDnaPackXllSuffix)' == ''">-packed</ExcelDnaPackXllSuffix>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.excel.extensions
|
||||
{
|
||||
public static class EnumParser
|
||||
{
|
||||
public static T ToEnum<T>(this string val, T default_val = default(T)) where T : struct, IConvertible, IComparable
|
||||
{
|
||||
val = val ?? string.Empty;
|
||||
T eval;
|
||||
if (!Enum.TryParse(val, true, out eval))
|
||||
{
|
||||
string[] names = Enum.GetNames(typeof(T));
|
||||
val = names.FirstOrDefault(x => x.ToLower().Contains(val));
|
||||
if(val != null)
|
||||
Enum.TryParse(val, true, out eval);
|
||||
|
||||
if (eval.CompareTo(default(T)) == 0 && default_val.CompareTo(default(T)) != 0)
|
||||
eval = default_val;
|
||||
}
|
||||
return eval;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ExcelDna.Integration;
|
||||
using ExcelDna.Integration.Rtd;
|
||||
using ExcelDna.Registration.Utils;
|
||||
|
||||
using bts.api.admin;
|
||||
using bts.excel;
|
||||
using bts.excel.extensions;
|
||||
using bts.excel.rtd;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace bts.api.orderman
|
||||
{
|
||||
public partial class OrderService_Excel
|
||||
{
|
||||
readonly static Lazy<user.AuthService> _users = new Lazy<user.AuthService>(() => Locator.locate<user.AuthService>(), true);
|
||||
readonly static Lazy<admin.AdminService> _admin = new Lazy<admin.AdminService>(() => Locator.locate<admin.AdminService>(), true);
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Get All Accessible Accounts")]
|
||||
async public static void spark_get_accounts(string login_token, ExcelAsyncHandle handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> accts = new List<string>();
|
||||
var admin = await _admin.Value.get_filtered_admin_data_async(new FilteredAdminDataRequest());
|
||||
if (admin.success)
|
||||
{
|
||||
foreach (var acc in admin.value.Accounts)
|
||||
if(acc.IsPresent)
|
||||
accts.Add(acc.Name);
|
||||
}
|
||||
var res = Utilities.make_column_vector(accts.ToArray());
|
||||
handle.SetResult(res);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
handle.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Cancel an Order by Order ID", IsMacroType = true)]
|
||||
public static object spark_orderservice_cancel_order(long order_id)
|
||||
{
|
||||
var resp = _service.Value.cancel_order(new Cancel() { OrderKey = Convert.ToUInt32(order_id) });
|
||||
return parse_response(resp);
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Update an Order by Order ID", IsMacroType = true)]
|
||||
public static object spark_orderservice_update_order(long order_id, double price, long quantity)
|
||||
{
|
||||
var resp = _service.Value.update_order(new Update() { OrderKey = Convert.ToUInt32(order_id), Price = price, Quantity = Convert.ToUInt32(quantity) });
|
||||
return parse_response(resp);
|
||||
}
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Send an Order to Spark via security_key", IsMacroType = true)]
|
||||
public static object spark_orderservice_send_order_key(
|
||||
[ExcelArgument("Required. Only send the order if > last")] long local_order_id,
|
||||
[ExcelArgument("Required. Security key")] long security_key,
|
||||
[ExcelArgument("Required. Side BUY or SELL")] string side,
|
||||
[ExcelArgument("Required. User")] string user,
|
||||
[ExcelArgument("Required. Account")] string account,
|
||||
[ExcelArgument("Required. Price")] double price,
|
||||
[ExcelArgument("Required. Quantity")] long quantity,
|
||||
[ExcelArgument("Required. OrderType (Limit, Stop, Stop_Limit, Iceberg) - Default Limit")] string order_type,
|
||||
[ExcelArgument("OPTIONAL. Order Params JSON (\"stop_price\" : 51.88)")] string order_params = null,
|
||||
|
||||
[ExcelArgument("OPTIONAL. (DAY,GTC,IOC) - Default DAY")] string time_in_force = null,
|
||||
[ExcelArgument("OPTIONAL. (YES,NO) - Default YES")] string manual_order_indicator = null,
|
||||
[ExcelArgument("OPTIONAL. (1,2,3,4) - Default 1")] string cti_code = null,
|
||||
[ExcelArgument("OPTIONAL. (CUSTOMER,FIRM) - Default FIRM")] string customer_or_firm = null)
|
||||
{
|
||||
if (local_order_id <= _local_order_id)
|
||||
return "local_order_id must be greater than " + _local_order_id;
|
||||
Create cr = new Create();
|
||||
cr.NewOrder = new OrderDefinition()
|
||||
{
|
||||
SecurityKey = Convert.ToUInt32(security_key),
|
||||
Side = EnumParser.ToEnum<api.Side>(side),
|
||||
TimeInForce = time_in_force.ToEnum<TimeInForce>(TimeInForce.Day),
|
||||
ManualOrderIndicator = manual_order_indicator.ToEnum<ManualOrderIndicator>(ManualOrderIndicator.Yes),
|
||||
CtiCode = cti_code.ToEnum<CTICode>(CTICode.CtiCode1),
|
||||
CustomerOrFirm = customer_or_firm.ToEnum<CustomerOrFirm>(CustomerOrFirm.Firm),
|
||||
};
|
||||
cr.Price = price;
|
||||
cr.Quantity = Convert.ToUInt32(quantity);
|
||||
|
||||
var btsuser = resolve_user(user);
|
||||
if(btsuser == null)
|
||||
return "Could not resolve user: " + user;
|
||||
cr.NewOrder.User = btsuser.Name;
|
||||
cr.NewOrder.UserId = btsuser.Id;
|
||||
var btaccount = resolve_account(account);
|
||||
if(btaccount == null)
|
||||
return "Could not resolve account: " + account;
|
||||
cr.NewOrder.Account = btaccount.Name;
|
||||
cr.NewOrder.AccountId = btaccount.Id;
|
||||
cr.NewOrder.CustomDef = custom_def(order_type, order_params);
|
||||
if(cr.NewOrder.CustomDef == null || cr.NewOrder.CustomDef.CustomCase == CustomOrderDefinition.CustomOneofCase.None)
|
||||
return "Could not parse order_type and/or order_params info";
|
||||
|
||||
var resp = _service.Value.create_order(cr);
|
||||
_local_order_id = local_order_id;
|
||||
return parse_response(resp, true);
|
||||
}
|
||||
|
||||
private static object parse_response(Response resp, bool show_vertically = false)
|
||||
{
|
||||
object[,] response_cells = new JsonFormatter(resp).format(false);
|
||||
if(show_vertically)
|
||||
response_cells = bts.excel.Utilities.transpose_keep_dimension(response_cells);
|
||||
return response_cells;
|
||||
}
|
||||
|
||||
static Lazy<AdminData> _admin_data = new Lazy<AdminData>(() => _admin.Value.get_admin_data(new VoidMessage()), true);
|
||||
static BTSUser resolve_user(string username)
|
||||
{
|
||||
var user = _admin_data.Value.Users.FirstOrDefault(x => username != null ? x.Name.ToLowerInvariant() == username.ToLowerInvariant() : true);
|
||||
return user;
|
||||
}
|
||||
|
||||
static BTSAccount resolve_account(string acctname)
|
||||
{
|
||||
var acc = _admin_data.Value.Accounts.FirstOrDefault(x => x.Name.ToLowerInvariant() == acctname.ToLowerInvariant());
|
||||
return acc;
|
||||
}
|
||||
|
||||
static CustomOrderDefinition custom_def(string order_type, string order_params)
|
||||
{
|
||||
var member = ReflectionExtensions.get_fields_or_properties(typeof(CustomOrderDefinition))
|
||||
.Where(x => x.Name.ToLower() == order_type.ToLower()).FirstOrDefault();
|
||||
if (member != null)
|
||||
{
|
||||
var mi = member.member_type();
|
||||
var instance = APIDispatch.generate_imessage(mi, order_params, false);
|
||||
var cust = new CustomOrderDefinition();
|
||||
ReflectionExtensions.set_value(member, cust, instance);
|
||||
return cust;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
static long _local_order_id = 0;
|
||||
|
||||
[ExcelFunction(Category = "BTS", Description = "Send an Order to Spark via security_key", IsMacroType = true)]
|
||||
public static object spark_orderservice_send_order(
|
||||
[ExcelArgument("Required. Only send the order if > last")] long local_order_id,
|
||||
[ExcelArgument("Required. Exchange (GLBX,SIM)")] string exchange,
|
||||
[ExcelArgument("Required. Security Desc (ZNZ9 or ZNZ9-ZNH0)")] string security_desc,
|
||||
[ExcelArgument("Required. Side BUY or SELL")] string side,
|
||||
[ExcelArgument("Required. User")] string user,
|
||||
[ExcelArgument("Required. Account")] string account,
|
||||
[ExcelArgument("Required. Price")] double price,
|
||||
[ExcelArgument("Required. Quantity")] long quantity,
|
||||
[ExcelArgument("Required. OrderType (Limit, Stop, Stop_Limit, Iceberg) - Default Limit")] string order_type,
|
||||
[ExcelArgument("OPTIONAL. Order Params JSON (\"stop_price\" : 51.88)")] string order_params = null,
|
||||
|
||||
[ExcelArgument("OPTIONAL. (DAY,GTC,IOC) - Default DAY")] string time_in_force = null,
|
||||
[ExcelArgument("OPTIONAL. (YES,NO) - Default YES")] string manual_order_indicator = null,
|
||||
[ExcelArgument("OPTIONAL. (1,2,3,4) - Default 1")] string cti_code = null,
|
||||
[ExcelArgument("OPTIONAL. (CUSTOMER,FIRM) - Default FIRM")] string customer_or_firm = null)
|
||||
{
|
||||
if (local_order_id <= _local_order_id)
|
||||
return "local_order_id must be greater than " + _local_order_id;
|
||||
Venue ven;
|
||||
if (!Utilities.parse_venue(exchange, out ven))
|
||||
return "Can't Parse Exchange: " + (exchange ?? string.Empty);
|
||||
|
||||
uint key;
|
||||
if (MDSubscribe.market_data(ven).lookup_security_key_from_desc(security_desc, out key))
|
||||
{
|
||||
return spark_orderservice_send_order_key(local_order_id, (long)key, side, user, account, price, quantity, order_type, order_params,
|
||||
time_in_force, manual_order_indicator, cti_code, customer_or_firm);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Can't find security desc " + security_desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.excel.extensions
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
public static void set_value(this MemberInfo info, object target, object value)
|
||||
{
|
||||
if (info is FieldInfo)
|
||||
((FieldInfo)info).SetValue(target, value);
|
||||
else if (info is PropertyInfo)
|
||||
((PropertyInfo)info).SetValue(target, value);
|
||||
}
|
||||
|
||||
public static object get_value(this MemberInfo info, object target)
|
||||
{
|
||||
if (info is FieldInfo)
|
||||
return ((FieldInfo)info).GetValue(target);
|
||||
else if (info is PropertyInfo)
|
||||
return ((PropertyInfo)info).GetValue(target);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MemberInfo[] get_fields_or_properties(this Type t)
|
||||
{
|
||||
MemberInfo[] mi = t.GetMembers(BindingFlags.Instance | BindingFlags.Public
|
||||
| BindingFlags.NonPublic).Where(m => (m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property)).ToArray();
|
||||
return mi;
|
||||
}
|
||||
|
||||
public static MemberInfo[] get_fields(this Type t)
|
||||
{
|
||||
MemberInfo[] mi = t.GetMembers(BindingFlags.Instance | BindingFlags.Public
|
||||
| BindingFlags.NonPublic).Where(m => (m.MemberType == MemberTypes.Field)).ToArray();
|
||||
return mi;
|
||||
}
|
||||
|
||||
public static MemberInfo[] get_properties(this Type t)
|
||||
{
|
||||
MemberInfo[] mi = t.GetMembers(BindingFlags.Instance | BindingFlags.Public
|
||||
| BindingFlags.NonPublic).Where(m => (m.MemberType == MemberTypes.Property)).ToArray();
|
||||
return mi;
|
||||
}
|
||||
|
||||
public static MemberInfo get_field_or_property(this Type t, string name)
|
||||
{
|
||||
MemberInfo[] mi = t.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(m => (m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property))
|
||||
.Where(m => (m.Name == name))
|
||||
.ToArray();
|
||||
if (mi != null && mi.Length > 0)
|
||||
return mi[0];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MemberInfo get_static_field_or_property(this Type t, string name)
|
||||
{
|
||||
MemberInfo[] mi = t.GetMembers(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(m => (m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property))
|
||||
.Where(m => (m.Name == name))
|
||||
.ToArray();
|
||||
if (mi != null && mi.Length > 0)
|
||||
return mi[0];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MemberInfo[] search_field_or_property(this Type t, Func<MemberInfo, bool> criteria)
|
||||
{
|
||||
MemberInfo[] mi = t.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(m => (m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property))
|
||||
.Where(criteria)
|
||||
.ToArray();
|
||||
if (mi != null && mi.Length > 0)
|
||||
return mi;
|
||||
else
|
||||
return new MemberInfo[0];
|
||||
}
|
||||
|
||||
|
||||
public static Type member_type(this MemberInfo info)
|
||||
{
|
||||
if (info is FieldInfo)
|
||||
return ((FieldInfo)info).FieldType;
|
||||
else if (info is PropertyInfo)
|
||||
return ((PropertyInfo)info).PropertyType;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodInfo[] find_method(this Type t, Func<MethodInfo, bool> criteria)
|
||||
{
|
||||
var mi = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(criteria).ToArray();
|
||||
if (mi != null && mi.Length > 0)
|
||||
return mi;
|
||||
else
|
||||
return new MethodInfo[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="ExcelDna.AddIn" version="0.34.6" targetFramework="net471" />
|
||||
<package id="ExcelDna.Integration" version="0.34.6" targetFramework="net471" />
|
||||
</packages>
|
||||
Loaded 100 of 536 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user