Compare commits
405
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66ad51280f | ||
|
|
029edd8a86 | ||
|
|
dfdcbab2a0 | ||
|
|
6d7c0702f0 | ||
|
|
86eaddea93 | ||
|
|
57f8f3ff58 | ||
|
|
775df2850d | ||
|
|
cb2b15f10b | ||
|
|
60362a4c95 | ||
|
|
c6d7a8f38c | ||
|
|
7931c62b46 | ||
|
|
58518451c8 | ||
|
|
e3bad78aef | ||
|
|
8e9ab8f2eb | ||
|
|
8512f414d6 | ||
|
|
513fc353cf | ||
|
|
4a04beafd2 | ||
|
|
79a3251f26 | ||
|
|
8785e0fecd | ||
|
|
04251c96b6 | ||
|
|
c2d400af1a | ||
|
|
133cc16628 | ||
|
|
16f7aede93 | ||
|
|
2b9488faff | ||
|
|
51b6d592d3 | ||
|
|
1eb4ceba0a | ||
|
|
dbf5cadb44 | ||
|
|
90f1bb0851 | ||
|
|
547d83e1b8 | ||
|
|
e888149c41 | ||
|
|
b944c3980f | ||
|
|
c2a8266106 | ||
|
|
4277ba61b9 | ||
|
|
35cfe9533b | ||
|
|
d49efef93d | ||
|
|
502ed13456 | ||
|
|
716c4dd674 | ||
|
|
4a2b189a07 | ||
|
|
a42edaf9fa | ||
|
|
cbc142ab15 | ||
|
|
3980f2d030 | ||
|
|
00f6d269c9 | ||
|
|
10b2d38de8 | ||
|
|
6d4d8f6ac1 | ||
|
|
5db89e1e8d | ||
|
|
379a46afa2 | ||
|
|
d9eff37184 | ||
|
|
3d055de139 | ||
|
|
648be7dd97 | ||
|
|
2cac9affe4 | ||
|
|
30ff3ba707 | ||
|
|
9d3afd68aa | ||
|
|
8c3de1789b | ||
|
|
b251e1d47e | ||
|
|
6ccd48a8ba | ||
|
|
41d84f7f73 | ||
|
|
795062864d | ||
|
|
1ff72e9444 | ||
|
|
e13b5066a3 | ||
|
|
5ba7901258 | ||
|
|
bc6eb59110 | ||
|
|
5ae5a5dcbf | ||
|
|
7966bcce77 | ||
|
|
ff86b10895 | ||
|
|
ea3c0717d8 | ||
|
|
d0abca3fd9 | ||
|
|
7895fe7357 | ||
|
|
6f5f4f5cc2 | ||
|
|
066a0ba693 | ||
|
|
c2b67b4813 | ||
|
|
ecad456a2d | ||
|
|
2cb3cfdcdd | ||
|
|
0ebc8e5b4b | ||
|
|
2072105c0f | ||
|
|
d663c86aa0 | ||
|
|
4a2dfa46a1 | ||
|
|
6e35db6a24 | ||
|
|
b45dc3bc2d | ||
|
|
a1912907b8 | ||
|
|
b1b3dcdeb8 | ||
|
|
03dc8ef767 | ||
|
|
975bf56c2a | ||
|
|
3f58c2ce84 | ||
|
|
96f28acf91 | ||
|
|
c2c2658e21 | ||
|
|
d6b8e359cd | ||
|
|
84a0850d95 | ||
|
|
4effdc4160 | ||
|
|
b9fe39f6e3 | ||
|
|
b555850015 | ||
|
|
23794fa30d | ||
|
|
90b5a309a3 | ||
|
|
07beda3fc6 | ||
|
|
e3c1ea70f2 | ||
|
|
93b93969f5 | ||
|
|
0a10111846 | ||
|
|
eee00cc407 | ||
|
|
0eb58eaba8 | ||
|
|
12f6733d1e | ||
|
|
93a0984ed1 | ||
|
|
b7e9c393a0 | ||
|
|
aa602b3fd6 | ||
|
|
b4b8cabdae | ||
|
|
cfe06869b3 | ||
|
|
b177ed1cc0 | ||
|
|
ff17a64933 | ||
|
|
81a1fe11f6 | ||
|
|
ad5bf88fcf | ||
|
|
e15f155ac9 | ||
|
|
6d6822cd65 | ||
|
|
ace0c70516 | ||
|
|
e9e5059bc2 | ||
|
|
cde72f51a0 | ||
|
|
e1fd49ae32 | ||
|
|
274f3f98e4 | ||
|
|
a4b7d84318 | ||
|
|
be17205094 | ||
|
|
6ef9bee8e4 | ||
|
|
2c8b3563f1 | ||
|
|
71f7e64ccb | ||
|
|
c31b44ecfb | ||
|
|
19efb77e07 | ||
|
|
76de640421 | ||
|
|
fb030742d3 | ||
|
|
73608f1b45 | ||
|
|
58aebe7002 | ||
|
|
299cb0b29e | ||
|
|
06663f711e | ||
|
|
51479f4885 | ||
|
|
5b520ba241 | ||
|
|
f0651a5341 | ||
|
|
897e0990a2 | ||
|
|
30e1ae5c3c | ||
|
|
efe83f81ec | ||
|
|
5fb21ada97 | ||
|
|
58edd07f6b | ||
|
|
9d81354c3a | ||
|
|
0f37be89a4 | ||
|
|
f6586fe636 | ||
|
|
7827ccc0e7 | ||
|
|
af6a2acc0a | ||
|
|
84abf59a55 | ||
|
|
2fc1d30bf1 | ||
|
|
4dcd5d63a1 | ||
|
|
63e5697b20 | ||
|
|
8e5cb100a7 | ||
|
|
938bee1817 | ||
|
|
d48af15480 | ||
|
|
e3b2e98cd9 | ||
|
|
ee53fb854c | ||
|
|
d36e2d61f6 | ||
|
|
bfff3ea5fe | ||
|
|
059c03d289 | ||
|
|
8c561f7886 | ||
|
|
288567d1eb | ||
|
|
ecfc5405fe | ||
|
|
8db34a2a11 | ||
|
|
91094c4d84 | ||
|
|
dae8c85d73 | ||
|
|
abdd98082f | ||
|
|
2f6aa785ab | ||
|
|
7acf1f7043 | ||
|
|
1c3c7a078a | ||
|
|
93ace52bfa | ||
|
|
b5d97c0b1b | ||
|
|
284aa53ad4 | ||
|
|
8a9952f52d | ||
|
|
748aafaab3 | ||
|
|
3ba30c70de | ||
|
|
e72f465999 | ||
|
|
f3d93f7749 | ||
|
|
8d142b8a74 | ||
|
|
8210b9f35f | ||
|
|
12b20b05ce | ||
|
|
72efbccdbd | ||
|
|
33643e9eab | ||
|
|
0f6dc6f40b | ||
|
|
8c778f5e41 | ||
|
|
f308654200 | ||
|
|
bdf5b980fc | ||
|
|
914ac597cd | ||
|
|
4aa6a77680 | ||
|
|
6b59dd84ee | ||
|
|
4e2c5f2d17 | ||
|
|
9a1b94a1b0 | ||
|
|
13b3c55771 | ||
|
|
d0a6fcdb9c | ||
|
|
1eb13028c1 | ||
|
|
424e5ff16b | ||
|
|
bed389885b | ||
|
|
85cf25a57a | ||
|
|
61471d4675 | ||
|
|
d83435d49d | ||
|
|
582fc10dce | ||
|
|
5969985d24 | ||
|
|
fabe7d665c | ||
|
|
7ce75bb8e7 | ||
|
|
b8e1c3cd60 | ||
|
|
4909944694 | ||
|
|
7258af09b4 | ||
|
|
7f2ce6c1a9 | ||
|
|
4835725e1c | ||
|
|
6a9e8de475 | ||
|
|
4639951acc | ||
|
|
d8de211aac | ||
|
|
87394bc60b | ||
|
|
beba30f65c | ||
|
|
2d58424d8f | ||
|
|
d1bbfa3791 | ||
|
|
aab17f460f | ||
|
|
461ef53322 | ||
|
|
2486bc90b2 | ||
|
|
1c5cbaf60b | ||
|
|
b8de447cfb | ||
|
|
8bfc2d40f9 | ||
|
|
c09fd71c94 | ||
|
|
f3b037553e | ||
|
|
d386d93166 | ||
|
|
5217742c71 | ||
|
|
c59ec95460 | ||
|
|
70f01583ec | ||
|
|
66d2f9eb63 | ||
|
|
4625bed8b8 | ||
|
|
f18fb332b5 | ||
|
|
8746a6f5ca | ||
|
|
702d47abb5 | ||
|
|
bf2a54717c | ||
|
|
9db779fb25 | ||
|
|
f55ddec708 | ||
|
|
6e942dd2d4 | ||
|
|
142310a17a | ||
|
|
7f99a89a95 | ||
|
|
bfce663b4b | ||
|
|
33c743643d | ||
|
|
858e0a389b | ||
|
|
9c3bb85674 | ||
|
|
f4930e8cd4 | ||
|
|
f21e7f3602 | ||
|
|
b56c35aca4 | ||
|
|
e6ac83196e | ||
|
|
7b6f74fc4a | ||
|
|
f754953fac | ||
|
|
2dd1143d9f | ||
|
|
e1ee3275d8 | ||
|
|
2549e406f3 | ||
|
|
d5024f1858 | ||
|
|
3623ac96db | ||
|
|
19834625f9 | ||
|
|
c8fc649b6b | ||
|
|
6b1fc1c413 | ||
|
|
6f086532ac | ||
|
|
dc66282225 | ||
|
|
e2c74c0bb3 | ||
|
|
e89d50eec1 | ||
|
|
8bebbc278d | ||
|
|
2e6d12c9c1 | ||
|
|
102a5e23ea | ||
|
|
582dc791db | ||
|
|
afb35d8c25 | ||
|
|
3a32ed5fe6 | ||
|
|
4e01697327 | ||
|
|
22032c1742 | ||
|
|
0a9bce317c | ||
|
|
40e44db597 | ||
|
|
4a447e44e1 | ||
|
|
b1f4654b3c | ||
|
|
9d350334c0 | ||
|
|
ac597f505f | ||
|
|
147d9ceb1f | ||
|
|
377804dfb9 | ||
|
|
0d26401961 | ||
|
|
889e226151 | ||
|
|
986d53f4e0 | ||
|
|
d13ba2968a | ||
|
|
1a192c8117 | ||
|
|
5e36d4801c | ||
|
|
2cf6411ca7 | ||
|
|
e99df129d5 | ||
|
|
d07c01bd17 | ||
|
|
d69673c524 | ||
|
|
4ee595cdaf | ||
|
|
6fcaf07df0 | ||
|
|
492d48d886 | ||
|
|
b6a56e768b | ||
|
|
3e2b5976a8 | ||
|
|
edd3f6cbd6 | ||
|
|
2e9347341b | ||
|
|
2061e38349 | ||
|
|
650dd57b6d | ||
|
|
82747f7dc0 | ||
|
|
04b26904a3 | ||
|
|
f123e4c72c | ||
|
|
b887cbebab | ||
|
|
14e975bc7e | ||
|
|
064f557057 | ||
|
|
4d106b5e6c | ||
|
|
9fbeaf0d68 | ||
|
|
4018ac8b78 | ||
|
|
81c9503300 | ||
|
|
477210926d | ||
|
|
0aa48a79a9 | ||
|
|
9e71c7af02 | ||
|
|
4647751409 | ||
|
|
a3087b874b | ||
|
|
5b2f5c5c8f | ||
|
|
479227090e | ||
|
|
2dfcf320f5 | ||
|
|
b4c7213cb2 | ||
|
|
c7dfd69b90 | ||
|
|
4fdc866485 | ||
|
|
f5d5ca9a1c | ||
|
|
cfafcf77b9 | ||
|
|
8ebe783139 | ||
|
|
1851a96a58 | ||
|
|
0b334ef870 | ||
|
|
ee811a729a | ||
|
|
cb15be276d | ||
|
|
7d5af6729b | ||
|
|
e5679cf082 | ||
|
|
98f89b9ad4 | ||
|
|
ba4f8f67e3 | ||
|
|
9c78380490 | ||
|
|
f6166f0674 | ||
|
|
6334a902e7 | ||
|
|
082ca62fff | ||
|
|
9dbd8dd7c6 | ||
|
|
8f7a4d0df6 | ||
|
|
f372dd56b3 | ||
|
|
86eba20d61 | ||
|
|
35a985280d | ||
|
|
f5e1b3319c | ||
|
|
65c3948e4f | ||
|
|
002bdff9a3 | ||
|
|
bb6e5dd548 | ||
|
|
7c1aed3339 | ||
|
|
22ae51fb99 | ||
|
|
a53d347f81 | ||
|
|
838dbcd886 | ||
|
|
5ed2309d22 | ||
|
|
4d41704dd2 | ||
|
|
bd1000e518 | ||
|
|
dd4aa3c2ed | ||
|
|
fee9d3f597 | ||
|
|
416f9aef31 | ||
|
|
48f5e4c201 | ||
|
|
4438b4ca8d | ||
|
|
73356e0d61 | ||
|
|
4a285fa581 | ||
|
|
cfa82c2177 | ||
|
|
0410a0431a | ||
|
|
02843f14ff | ||
|
|
f1f1d4259c | ||
|
|
71fc43af68 | ||
|
|
350144ffd5 | ||
|
|
da227a5550 | ||
|
|
0c92cf3a87 | ||
|
|
1188af391e | ||
|
|
0bb85cf915 | ||
|
|
d77f5996fe | ||
|
|
940c41a274 | ||
|
|
e671960dc3 | ||
|
|
8fa34b6b39 | ||
|
|
f9d5429b5b | ||
|
|
2c342482eb | ||
|
|
3aefac26c2 | ||
|
|
1554594cdd | ||
|
|
727a0f38b5 | ||
|
|
7a0e8cdb26 | ||
|
|
a4e97b04d7 | ||
|
|
463980a6ef | ||
|
|
9b25857042 | ||
|
|
ef19e7d76b | ||
|
|
471faec2cb | ||
|
|
ce96d42db8 | ||
|
|
187894ca84 | ||
|
|
63492c4575 | ||
|
|
e150c611f6 | ||
|
|
bb837d6c26 | ||
|
|
965e541ac8 | ||
|
|
1b232aa610 | ||
|
|
25439b094a | ||
|
|
f927ef2c5e | ||
|
|
d42389b75a | ||
|
|
671cbcf03a | ||
|
|
e589e23f41 | ||
|
|
11fadc0a3e | ||
|
|
b8c2d93866 | ||
|
|
67a23dd196 | ||
|
|
ff22c4eb5f | ||
|
|
5addff4c50 | ||
|
|
142e1bd2da | ||
|
|
d05d91bbe7 | ||
|
|
3a90ca9cb5 | ||
|
|
85a46727be | ||
|
|
624fd2c976 | ||
|
|
16d599d2d6 | ||
|
|
c00e4fcb6b | ||
|
|
03e19afe1d | ||
|
|
058e11eadc | ||
|
|
6ff52e7107 | ||
|
|
ee4c28ed41 | ||
|
|
50d26f0060 | ||
|
|
36378e6074 | ||
|
|
420b46741e | ||
|
|
e8f65f95bd |
No files matched your search
@@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"fable": {
|
||||
"version": "4.0.6",
|
||||
"version": "4.24.0",
|
||||
"commands": [
|
||||
"fable"
|
||||
]
|
||||
@@ -27,4 +27,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,12 @@ RUN apt-get update && apt-get install -y libpcap0.8-dev
|
||||
# Databento (deps)
|
||||
RUN apt-get update && apt-get -y install libzstd-dev
|
||||
|
||||
# Docker in Docker
|
||||
RUN apt-get update && apt-get install -y \
|
||||
docker.io \
|
||||
docker-compose \
|
||||
docker-compose-v2
|
||||
|
||||
# Set up default executables (update-alternatives)
|
||||
RUN \
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-$clang_major 100; \
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
{
|
||||
"dockerFile": "Dockerfile",
|
||||
// Creates local dir to mount to HOME in docker
|
||||
"initializeCommand": "mkdir -p ${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId}",
|
||||
"hostRequirements": {
|
||||
"memory": "4gb",
|
||||
"storage": "6gb"
|
||||
"memory": "8gb",
|
||||
"storage": "10gb"
|
||||
},
|
||||
"postCreateCommand": "/home/bts/spark/docker/sparkd/dev/devcontainer_post_create.sh",
|
||||
"containerUser": "bts",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bts/spark,type=bind",
|
||||
"workspaceFolder": "/home/bts/spark",
|
||||
"updateRemoteUserUID": true,
|
||||
"shutdownAction": "none",
|
||||
// Note: If running multiple containers on the same host, each must use a
|
||||
@@ -17,34 +12,20 @@
|
||||
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
|
||||
"runArgs": [
|
||||
"--shm-size",
|
||||
"2gb",
|
||||
"-p",
|
||||
"8000:8000",
|
||||
"-p",
|
||||
"8001:8001",
|
||||
"-p",
|
||||
"8002:8002",
|
||||
"-p",
|
||||
"8003:8003",
|
||||
"-p",
|
||||
"12345:12345"
|
||||
"8gb"
|
||||
],
|
||||
"mounts": [
|
||||
"source=${localEnv:HOME}/.bts/devcontainer-mounts/${devcontainerId},target=/home/bts,type=bind"
|
||||
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"clangd.arguments": [
|
||||
"--compile-commands-dir=${workspaceFolder}/build/clang/debug"
|
||||
]
|
||||
},
|
||||
"extensions": [
|
||||
"eamodio.gitlens",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"ms-vscode.cmake-tools",
|
||||
"ms-vscode.cpptools"
|
||||
"ms-vscode.cpptools",
|
||||
"xaver.clang-format"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# This file defines the global default configuration for docker deployments.
|
||||
# It is tracked in source control. For temporary modifications, please create
|
||||
# a separate file called ".env.local" (it's in .gitignore, so won't be tracked).
|
||||
# If a variable is defined in both files, the value in .env.local will be used.
|
||||
#
|
||||
# About the deployment procedure:
|
||||
# We use Docker Compose to build images and launch containers for various services
|
||||
# that make up our software suite:
|
||||
# - sparkd
|
||||
# - TODO: pubsub
|
||||
# - TODO: md-publisher
|
||||
# - TODO: webapp (DSQA on the web)
|
||||
# - TODO: api-server (websocket based)
|
||||
# From inside a devcontainer, you can launch all services in separate containers
|
||||
# on your machine by running:
|
||||
#
|
||||
# tools/deploy.sh
|
||||
#
|
||||
# This script builds release versions for each service, packaged (generally) in
|
||||
# self-contained directories. Next, it runs a few "docker compose" commands to
|
||||
# build docker images loaded with the self-contained binaries from the previous
|
||||
# step, and start containers with those images to run each service. To view the
|
||||
# commands that deploy.sh runs internally (without actually running them), run:
|
||||
#
|
||||
# tools/deploy.sh --dry-run
|
||||
#
|
||||
# For additional information/help, run:
|
||||
#
|
||||
# tools/deploy.sh --help
|
||||
#
|
||||
|
||||
### Sparkd ###
|
||||
# The internal image for sparkd to use. For more info, view:
|
||||
# src/server/sparkd/Dockerfile
|
||||
SPARKD__IMAGE=sparkd
|
||||
# The config dir for this service to use
|
||||
SPARKD__CONFIG_DIR=src/server/sparkd/configurations/dev
|
||||
# Mount a specific volume or dir to "~/sparkdata/data" inside the docker.
|
||||
# Defaults to a volume named "sparkd_<config_port>" (e.g., sparkd_8000).
|
||||
# SPARKD__DATA_DIR=/home/bts/sparkdata/data
|
||||
SPARKD__SOURCE_DIR=install/sparkd-release-gcc
|
||||
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf/
|
||||
SPARKD__CONFIG_PORT=8000
|
||||
SPARKD__QUICKBUS_PORT=8001
|
||||
SPARKD__SIM_FIX_PORT=8002
|
||||
SPARKD__SHM_SIZE=4gb
|
||||
|
||||
# TODO: add config for other services (pubsub, md-publisher, webapp, api-server, etc.)
|
||||
@@ -59,3 +59,4 @@ trace_data/
|
||||
.devcontainer/
|
||||
*.Generated.cs
|
||||
*.lst
|
||||
.env.local
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[submodule "externals"]
|
||||
path = externals
|
||||
url = git@bitbucket.org:bluetradesys/externals.git
|
||||
url = https://bitbucket.org/bluetradesys/externals.git
|
||||
[submodule "vcpkg"]
|
||||
path = vcpkg
|
||||
url = https://github.com/microsoft/vcpkg.git
|
||||
Vendored
+181
-10
@@ -31,7 +31,39 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": ["run"],
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
},
|
||||
{
|
||||
"name": "spreader tests2",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader2]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
},
|
||||
{
|
||||
"name": "spreader tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
@@ -91,6 +123,18 @@
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark (Skip Build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"program": "Spark.exe",
|
||||
"cwd": "build/Debug/bin/net7.0-windows",
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark (Parameterized)",
|
||||
"type": "coreclr",
|
||||
@@ -109,21 +153,138 @@
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Sparkd",
|
||||
"name": "Spark Test",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Sparkd (Debug | Clang)",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "sparkd",
|
||||
"windows": {
|
||||
"preLaunchTask": "Build Spark (Debug)",
|
||||
"program": "Spark.Console.exe",
|
||||
"cwd": "build/Debug/bin/net7.0",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
]
|
||||
},
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"cwd": "build/sparkd-debug-clang/server",
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Spark Test (Skip Build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"program": "Spark.Console.exe",
|
||||
"cwd": "build/Debug/bin/net7.0",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
]
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Sparkd",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"preLaunchTask": "Build Sparkd (Debug)",
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sparkd (Skip Build)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sparkd Test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"preLaunchTask": "Build Sparkd (Debug)",
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sparkd Test (Skip Build)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
@@ -154,5 +315,15 @@
|
||||
"description": "Config Server URL",
|
||||
"default": "http://localhost:8000/config",
|
||||
},
|
||||
{
|
||||
"id": "sparkTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
Vendored
+103
-1
@@ -641,6 +641,21 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Spark (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
"color": "terminal.ansiGreen",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Spark Config Query",
|
||||
"dependsOn": "Build Spark",
|
||||
@@ -684,6 +699,24 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Run Sparkd (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Run Sparkd Config Query",
|
||||
"dependsOn": "Build Sparkd",
|
||||
@@ -705,6 +738,65 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Spark (Debug)",
|
||||
"dependsOn": "Build Spark (Debug)",
|
||||
"type": "process",
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
"command": "",
|
||||
"windows": {
|
||||
"command": "Spark.Console.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Sparkd",
|
||||
"dependsOn": "Build Sparkd",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Sparkd (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"linux": {
|
||||
"command": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:sparkdCmakePreset}/server"
|
||||
},
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
|
||||
"type": "process",
|
||||
@@ -823,6 +915,16 @@
|
||||
"id": "sparkdConfigQuery",
|
||||
"type": "promptString",
|
||||
"description": "Config Type(s)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sparkTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
+18
-5
@@ -1,7 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
project(spark)
|
||||
include(GNUInstallDirs)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/GitVersion.cmake)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
get_version_from_git()
|
||||
if(WIN32)
|
||||
set(platform "windows")
|
||||
else()
|
||||
@@ -87,9 +90,15 @@ else()
|
||||
add_compile_definitions(BINANCE_US_PRICE_STATS_PATH="/api/v3/ticker/24hr")
|
||||
|
||||
# ** Binance USDS Futures **
|
||||
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://dapi.binance.com")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
|
||||
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://fapi.binance.com")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/fapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/fapi/v1/ticker/24hr")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_USER_DATA_PATH="/api/v3/userDataStream")
|
||||
|
||||
# ** Binance Coin Futures **
|
||||
add_compile_definitions(CCAPI_BINANCE_COIN_FUTURES_URL_REST_BASE="https://dapi.binance.com")
|
||||
add_compile_definitions(BINANCE_COIN_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_COIN_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
|
||||
|
||||
# ** Deribit **
|
||||
add_compile_definitions(DERIBIT_INSTRUMENT_PATH="/api/v2/public/get_instrument")
|
||||
@@ -109,6 +118,7 @@ else()
|
||||
|
||||
# subdirs
|
||||
if (BUILD_SPARKD)
|
||||
add_subdirectory(src/lib_binance_execution)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/data_files)
|
||||
endif()
|
||||
@@ -145,11 +155,14 @@ else()
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_BINARY_DIR}/server/plugins"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(
|
||||
FILES
|
||||
"${SPARK_LIB}/libACE-5.7.5.so"
|
||||
"${SPARK_LIB}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
"${SPARK_LIB}/libonixs-fix-engine.so.4.10.1"
|
||||
"${SPARK_LIB}/libonixs-fix-engine.so.4"
|
||||
"${SPARK_LIB}/libonixs-fix-engine.so"
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
endif()
|
||||
endif()
|
||||
+3
-11
@@ -26,6 +26,7 @@
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
|
||||
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||
@@ -61,7 +62,7 @@
|
||||
},
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"CMAKE_INSTALL_RPATH": ".",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}/sparkd",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-Wno-undef",
|
||||
"EXTERNALS_ROOT": "${sourceDir}/externals/linux",
|
||||
"BUILD_SDK": {
|
||||
@@ -302,16 +303,7 @@
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"hidden": true,
|
||||
"targets": [
|
||||
"data_files_dir",
|
||||
"admin_web_app_bundle",
|
||||
"install",
|
||||
"libspark",
|
||||
"rapidjson_project",
|
||||
"server_layout",
|
||||
"sparkd"
|
||||
]
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
function(get_version_from_git)
|
||||
find_package(Git QUIET)
|
||||
if(NOT Git_FOUND)
|
||||
message(WARNING "Git not found")
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} describe --tags --always
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_TAG
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE GIT_RESULT
|
||||
)
|
||||
|
||||
if(NOT GIT_RESULT EQUAL 0)
|
||||
message(WARNING "Failed to get git tag")
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_SHORT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
set(PROJECT_VERSION_BUILD_METADATA ${GIT_COMMIT_SHORT_HASH})
|
||||
set(PROJECT_VERSION_BUILD_METADATA ${GIT_COMMIT_SHORT_HASH} PARENT_SCOPE)
|
||||
|
||||
string(REGEX REPLACE "^v" "" CLEAN_TAG "${GIT_TAG}")
|
||||
if(CLEAN_TAG MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-.*)?$")
|
||||
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1} PARENT_SCOPE)
|
||||
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(PROJECT_VERSION_MINOR ${CMAKE_MATCH_2} PARENT_SCOPE)
|
||||
set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
set(PROJECT_VERSION_PATCH ${CMAKE_MATCH_3} PARENT_SCOPE)
|
||||
|
||||
set(PROJECT_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}")
|
||||
set(PROJECT_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}" PARENT_SCOPE)
|
||||
set(PROJECT_VERSION_FULL "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}+${GIT_COMMIT_SHORT_HASH}")
|
||||
set(PROJECT_VERSION_FULL "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}+${GIT_COMMIT_SHORT_HASH}" PARENT_SCOPE)
|
||||
else()
|
||||
message(WARNING "Tag '${CLEAN_TAG}' does not match semver format")
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,49 @@
|
||||
include_guard(DIRECTORY)
|
||||
|
||||
function(protobuf_generate_fs)
|
||||
set(_options)
|
||||
set(_one_value_args TARGET_NAME OUT_DIR PROTOC)
|
||||
set(_multi_value_args PROTO_INCLUDE_DIRS PROTO_SOURCES)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
|
||||
if (NOT DEFINED _ARGS_TARGET_NAME)
|
||||
set(_ARGS_TARGET_NAME "${PROJECT_NAME}_generate_fs_protos")
|
||||
endif()
|
||||
if (NOT DEFINED _ARGS_PROTOC)
|
||||
set(_ARGS_PROTOC "${SPARK_BIN}/protoc")
|
||||
endif()
|
||||
|
||||
set(_proto_include_cli_args)
|
||||
foreach(_dir ${_ARGS_PROTO_INCLUDE_DIRS})
|
||||
list(APPEND _proto_include_cli_args -I ${_dir})
|
||||
endforeach()
|
||||
|
||||
set(_outputs)
|
||||
foreach(_proto_source ${_ARGS_PROTO_SOURCES})
|
||||
# get_filename_component(_abs_proto_source ${_proto_source} ABSOLUTE)
|
||||
get_filename_component(_proto_source_we ${_proto_source} NAME_WE)
|
||||
# get_filename_component(_proto_source_dir ${_proto_source} DIRECTORY)
|
||||
list(APPEND _outputs ${_ARGS_OUT_DIR}/${_proto_source_we}.fable.fs)
|
||||
endforeach()
|
||||
|
||||
set(_venv_exec ${CMAKE_SOURCE_DIR}/tools/venv_exec.sh)
|
||||
set(_plugin_py ${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/plugin.py)
|
||||
set(_template_fs ${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/templates/fs/fable.fs)
|
||||
add_custom_command(
|
||||
OUTPUT ${_outputs}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND ${_venv_exec}
|
||||
${_ARGS_PROTOC}
|
||||
--plugin=protoc-gen-service=${_plugin_py}
|
||||
--service_out=lang=fs,strip_out_path=1:${_ARGS_OUT_DIR}
|
||||
${_proto_include_cli_args}
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
DEPENDS
|
||||
${_venv_exec}
|
||||
${_plugin_py}
|
||||
${_template_fs}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
)
|
||||
add_custom_target(${_ARGS_TARGET_NAME} ALL DEPENDS "${_outputs}")
|
||||
endfunction()
|
||||
@@ -0,0 +1,33 @@
|
||||
include_guard(DIRECTORY)
|
||||
|
||||
function(protobuf_generate_js)
|
||||
set(_options)
|
||||
set(_one_value_args TARGET_NAME OUTPUT)
|
||||
set(_multi_value_args PROTO_INCLUDE_DIRS PROTO_SOURCES)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
|
||||
if (NOT DEFINED _ARGS_TARGET_NAME)
|
||||
set(_ARGS_TARGET_NAME "${PROJECT_NAME}_generate_js_protos")
|
||||
endif()
|
||||
|
||||
set(_proto_include_cli_args)
|
||||
foreach(_dir ${_ARGS_PROTO_INCLUDE_DIRS})
|
||||
list(APPEND _proto_include_cli_args -p ${_dir})
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_ARGS_OUTPUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND npx
|
||||
--no-install pbjs
|
||||
-t json-module
|
||||
-w commonjs
|
||||
-o ${_ARGS_OUTPUT}
|
||||
${_proto_include_cli_args}
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
)
|
||||
add_custom_target(${_ARGS_TARGET_NAME} ALL DEPENDS ${_ARGS_OUTPUT})
|
||||
endfunction()
|
||||
@@ -0,0 +1,49 @@
|
||||
include_guard(DIRECTORY)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/TernaryOperator.cmake)
|
||||
|
||||
function(sym_link_dir)
|
||||
set(_options)
|
||||
set(_one_value_args TARGET_NAME SOURCE DEST COMMENT)
|
||||
set(_multi_value_args)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
add_custom_command(
|
||||
OUTPUT ${_ARGS_DEST}
|
||||
COMMAND ln ARGS -sf ${_ARGS_SOURCE} ${_ARGS_DEST}
|
||||
)
|
||||
set_ternary(_comment _ARGS_COMMENT "${_ARGS_COMMENT}" "linking dir ${_ARGS_SOURCE} -> ${_ARGS_DEST}")
|
||||
add_custom_target(
|
||||
${_ARGS_TARGET_NAME} ALL
|
||||
DEPENDS ${_ARGS_DEST}
|
||||
COMMENT "${_comment}"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(sym_link_file)
|
||||
set(_options)
|
||||
set(_one_value_args SOURCE DEST INSTALL_DEST)
|
||||
set(_multi_value_args)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
file(
|
||||
CREATE_LINK
|
||||
${_ARGS_SOURCE}
|
||||
${_ARGS_DEST}
|
||||
SYMBOLIC
|
||||
)
|
||||
if (DEFINED _ARGS_INSTALL_DEST)
|
||||
install(
|
||||
FILES
|
||||
${_ARGS_SOURCE}
|
||||
DESTINATION ${_ARGS_INSTALL_DEST}
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(sym_link_local_lib LIB_NAME)
|
||||
set(_lib_file_name "${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_NAME}.so")
|
||||
file(
|
||||
CREATE_LINK
|
||||
"${CMAKE_BINARY_DIR}/src/lib_${LIB_NAME}/${_lib_file_name}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${_lib_file_name}"
|
||||
SYMBOLIC
|
||||
)
|
||||
endfunction()
|
||||
@@ -0,0 +1,9 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
macro(set_ternary RESULT CONDITION TRUE_VALUE FALSE_VALUE)
|
||||
if(${CONDITION})
|
||||
set(${RESULT} ${TRUE_VALUE})
|
||||
else()
|
||||
set(${RESULT} ${FALSE_VALUE})
|
||||
endif()
|
||||
endmacro()
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef BTS_SPARK_VERSION_H
|
||||
#define BTS_SPARK_VERSION_H
|
||||
|
||||
#define BTS_SPARK_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define BTS_SPARK_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define BTS_SPARK_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
#define BTS_SPARK_VERSION_BUILD_METADATA @PROJECT_VERSION_BUILD_METADATA@
|
||||
#define BTS_SPARK_VERSION "@PROJECT_VERSION@"
|
||||
#define BTS_SPARK_VERSION_FULL "@PROJECT_VERSION_FULL@"
|
||||
|
||||
#endif
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
services:
|
||||
sparkd:
|
||||
volumes:
|
||||
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
|
||||
build:
|
||||
dockerfile: src/server/sparkd/Dockerfile
|
||||
args:
|
||||
- IMAGE=${SPARKD__IMAGE}
|
||||
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
|
||||
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
|
||||
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
|
||||
command: >
|
||||
sh -c "
|
||||
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
|
||||
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
|
||||
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
|
||||
fi
|
||||
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
|
||||
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
|
||||
set -a
|
||||
. /home/bts/sparkdata/conf/.env.local
|
||||
set +a
|
||||
fi
|
||||
./sparkd run"
|
||||
env_file:
|
||||
- .env
|
||||
- ${SPARKD__CONFIG_DIR}/.env
|
||||
ports:
|
||||
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
|
||||
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
|
||||
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: "${SPARKD__MEMORY:-4gb}"
|
||||
sparkd-symbols:
|
||||
volumes:
|
||||
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
|
||||
- ${SPARKD__RUNTIME_CONF_DIR:-sparkd_conf}:/home/bts/sparkdata/conf
|
||||
build:
|
||||
dockerfile: src/server/sparkd/Dockerfile
|
||||
args:
|
||||
- IMAGE=${SPARKD__IMAGE}
|
||||
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
|
||||
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
|
||||
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
|
||||
command: >
|
||||
sh -c "
|
||||
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
|
||||
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
|
||||
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
|
||||
fi
|
||||
ls -alh /home/bts/sparkdata/conf/
|
||||
cat /home/bts/sparkdata/conf/.env.local
|
||||
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
|
||||
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
|
||||
set -a
|
||||
. /home/bts/sparkdata/conf/.env.local
|
||||
set +a
|
||||
fi
|
||||
./sparkd run"
|
||||
env_file:
|
||||
- .env
|
||||
- ${SPARKD__CONFIG_DIR}/.env
|
||||
ports:
|
||||
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
|
||||
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
|
||||
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: "${SPARKD__MEMORY:-4gb}"
|
||||
|
||||
sparkd-tail:
|
||||
image: bluetradingsystems/linux_dotnet7
|
||||
volumes:
|
||||
- type: volume
|
||||
read_only: true
|
||||
source: sparkd_data
|
||||
target: /home/bts/sparkdata/data/logging.sparkd
|
||||
volume:
|
||||
subpath: logging.sparkd
|
||||
command: ["tail", "-f", "/home/bts/sparkdata/data/logging.sparkd/log"]
|
||||
|
||||
sparkd-ping-test:
|
||||
depends_on:
|
||||
- sparkd
|
||||
profiles:
|
||||
- ping-test
|
||||
build:
|
||||
context: .
|
||||
dockerfile_inline: |
|
||||
FROM bluetradingsystems/linux_dotnet7
|
||||
USER "root"
|
||||
RUN apt-get update && apt-get -y install iputils-ping
|
||||
command: [ "ping", "sparkd" ]
|
||||
|
||||
volumes:
|
||||
sparkd_data:
|
||||
name: "sparkd_${SPARKD__CONFIG_PORT:-8000}"
|
||||
@@ -0,0 +1,38 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y pkg-config
|
||||
RUN apt-get update && apt-get install -y locales
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
RUN apt-get update && apt-get install -y libssh2-1-dev
|
||||
RUN apt-get update && apt-get install -y software-properties-common
|
||||
RUN apt-get update && apt-get install -y sudo
|
||||
|
||||
# GCC
|
||||
ARG gcc_major=13
|
||||
RUN add-apt-repository ppa:ubuntu-toolchain-r/test \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y g++-$gcc_major
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y dotnet7
|
||||
|
||||
RUN locale-gen en_US.UTF-8
|
||||
|
||||
ARG docker_user="bts"
|
||||
ARG docker_uid="1000"
|
||||
ARG docker_pass="bts"
|
||||
|
||||
RUN \
|
||||
groupadd -g "$docker_uid" "$docker_user" \
|
||||
&& useradd \
|
||||
-m \
|
||||
-u "$docker_uid" \
|
||||
-g "$docker_uid" \
|
||||
-G sudo \
|
||||
--password "$(openssl passwd -1 "$docker_pass")" \
|
||||
"$docker_user"
|
||||
|
||||
USER bts
|
||||
WORKDIR /home/bts
|
||||
-1
Submodule docker/linux deleted from 56c61231f3.
Submodule docker/windows deleted from de7d40c09d.
+1
-1
Submodule externals updated: ff5f02bbde...959b63f2f6.
@@ -1,11 +1,20 @@
|
||||
astroid==2.7.3
|
||||
autopep8==1.5.7
|
||||
blinker==1.9.0
|
||||
boto3==1.37.9
|
||||
botocore==1.37.9
|
||||
certifi==2023.5.7
|
||||
charset-normalizer==3.1.0
|
||||
click==8.1.8
|
||||
colorama==0.4.4
|
||||
Flask==3.1.0
|
||||
idna==3.4
|
||||
isort==5.9.3
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
jmespath==1.0.1
|
||||
lazy-object-proxy==1.6.0
|
||||
MarkupSafe==3.0.2
|
||||
mccabe==0.6.1
|
||||
platformdirs==2.3.0
|
||||
protobuf==4.21.12
|
||||
@@ -14,9 +23,14 @@ pycodestyle==2.7.0
|
||||
pycparser==2.20
|
||||
pylint==2.10.2
|
||||
PyMeta3==0.5.1
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.1
|
||||
requests==2.31.0
|
||||
s3transfer==0.11.4
|
||||
six==1.17.0
|
||||
termcolor==1.1.0
|
||||
toml==0.10.2
|
||||
urllib3==2.0.3
|
||||
Werkzeug==3.1.3
|
||||
wrapt==1.12.1
|
||||
yapf==0.31.0
|
||||
@@ -4,7 +4,7 @@ namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
using System;
|
||||
|
||||
[DataStreamUserRespresentation("DateTimeExpanderStream")]
|
||||
public class DateTimeExpanstionStream : ViewStream {
|
||||
public DateTimeExpanstionStream(ViewStream parent_stream, string inp_col) {
|
||||
Func<IBlueTableSchema, IBlueTableSchema> get_schema = (s) => {
|
||||
@@ -24,11 +24,11 @@ namespace bts.SilverPine {
|
||||
IBlueTableSchema s = get_schema(r.schema);
|
||||
MutableBlueRow rv = new MutableBlueRow(s, r);
|
||||
rv.setValue($"{inp_col}_date", dt.Date);
|
||||
rv.setValue($"{inp_col}_day", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day_of_week", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day_of_month", dt.Day);
|
||||
rv.setValue($"{inp_col}_month", dt.Date.Month);
|
||||
rv.setValue($"{inp_col}_hour", dt.Hour);
|
||||
rv.setValue($"{inp_col}_min", dt.Minute);
|
||||
rv.setValue($"{inp_col}_minute", dt.Minute);
|
||||
rv.setValue($"{inp_col}_seconds_of_day", (dt - dt.Date).TotalSeconds);
|
||||
return rv;
|
||||
});
|
||||
|
||||
@@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "Bt
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamQueryAnalyzer", "DataStreamQueryAnalyzer\DataStreamQueryAnalyzer.csproj", "{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -113,5 +115,9 @@ Global
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -156,6 +156,14 @@ namespace bts.SilverPine {
|
||||
ViewStream rs = new FutureHistoricalBarsStream(fut_contract, bar_size.ToString(), dt_range_beg, dt_range_end);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size)
|
||||
: this(fut_contract, bar_size, DateTime.MinValue, DateTime.MaxValue) { }
|
||||
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
|
||||
string dt_range_beg, string dt_range_end)
|
||||
: this(fut_contract, bar_size, DateTime.Parse(dt_range_beg),
|
||||
DateTime.Parse(dt_range_end)) { }
|
||||
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
|
||||
DateTime dt_range_beg, DateTime dt_range_end)
|
||||
:
|
||||
|
||||
@@ -1,56 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(Spark.Admin.WebApp)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/safe_dep_glob.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Protobuf.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/SymLink.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/ProtobufGenerateJs.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/ProtobufGenerateFs.cmake)
|
||||
|
||||
# dependencies
|
||||
set(PROTO_GLOBS
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_PROTO_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
../lib/api/*.proto
|
||||
../lib/api/services/*.proto
|
||||
../lib/domain_model/*.proto
|
||||
../lib/sparkproto/*.proto
|
||||
../lib/md/protos/*.proto
|
||||
../../externals/linux/include/google/protobuf/descriptor.proto
|
||||
../../externals/linux/include/google/protobuf/timestamp.proto)
|
||||
../../externals/linux/include/google/protobuf/timestamp.proto
|
||||
)
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _proto_rel_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${PROTO_GLOBS})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _proto_abs_SOURCES
|
||||
${PROTO_GLOBS})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _ts_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.ts)
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _fs_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.fs)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
COMMAND npm install --include=dev
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/package.json
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(_tools ${CMAKE_SOURCE_DIR}/tools)
|
||||
set(_quiet_exec ${_tools}/quiet_exec.sh)
|
||||
set(_venv_exec ${_tools}/venv_exec.sh)
|
||||
set(_tools_py ${CMAKE_SOURCE_DIR}/tools/py)
|
||||
|
||||
# The tsconfig file is generated as a function of the cmake build type, and
|
||||
# binary dir. Depending on the build type, it extends either
|
||||
# 'tsconfig.debug.json' or 'tsconfig.release.json'.
|
||||
set(_tsconfig_base "${CMAKE_CURRENT_SOURCE_DIR}/tsconfig.$<IF:$<CONFIG:Debug>,debug,release>.json")
|
||||
set(WEBPACK_MODE $<IF:$<CONFIG:Debug>,"development","production">)
|
||||
set(_tsconfig "${CMAKE_CURRENT_BINARY_DIR}/tsconfig.json")
|
||||
|
||||
set(_venv_python ${CMAKE_SOURCE_DIR}/.venv/bin/python)
|
||||
set(_gen_tsconfig_py ${_tools_py}/gen_tsconfig.py)
|
||||
set(_build_admin_web_app_py ${_tools_py}/build_admin_web_app.py)
|
||||
|
||||
set(_gen_tsconfig_deps ${_venv_exec} ${_venv_python} ${_gen_tsconfig_py} ${_tsconfig_base})
|
||||
|
||||
set(_build_admin_web_app_deps
|
||||
${_quiet_exec}
|
||||
${_venv_exec}
|
||||
@@ -60,49 +46,142 @@ set(_build_admin_web_app_deps
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/webpack.config.js
|
||||
${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/templates/fs/fable.fs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
${_proto_rel_SOURCES}
|
||||
${${PROJECT_NAME}_PROTO_SOURCES}
|
||||
${_api_ts_SOURCES}
|
||||
${_ui_fs_SOURCES}
|
||||
${_tsconfig})
|
||||
|
||||
update_deps_file("${_build_admin_web_app_deps}")
|
||||
|
||||
function(npm_install path)
|
||||
add_custom_command(
|
||||
OUTPUT ${path}/package-lock.json
|
||||
COMMAND "npm" ARGS "install" "--include=dev"
|
||||
DEPENDS ${path}/package.json
|
||||
WORKING_DIRECTORY ${path}
|
||||
COMMENT "Run 'npm install' in ${path}")
|
||||
endfunction()
|
||||
|
||||
npm_install(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_tsconfig}
|
||||
COMMAND ${_venv_exec}
|
||||
ARGS
|
||||
${_venv_python}
|
||||
${_gen_tsconfig_py}
|
||||
--config-base ${_tsconfig_base}
|
||||
--config-out ${_tsconfig}
|
||||
--build-out ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDS ${_gen_tsconfig_deps})
|
||||
${_venv_python}
|
||||
${_gen_tsconfig_py}
|
||||
--config-base ${_tsconfig_base}
|
||||
--config-out ${_tsconfig}
|
||||
--build-out ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDS
|
||||
${_venv_exec}
|
||||
${_venv_python}
|
||||
${_gen_tsconfig_py}
|
||||
${_tsconfig_base}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
)
|
||||
|
||||
protobuf_generate_js(
|
||||
TARGET_NAME generate_js_protos
|
||||
PROTO_INCLUDE_DIRS
|
||||
..
|
||||
../../externals/linux/include
|
||||
PROTO_SOURCES ${${PROJECT_NAME}_PROTO_SOURCES}
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/proto.js
|
||||
)
|
||||
|
||||
protobuf_generate_fs(
|
||||
TARGET_NAME generate_fs_protos
|
||||
PROTO_INCLUDE_DIRS
|
||||
..
|
||||
../../externals/linux/include
|
||||
PROTO_SOURCES ${${PROJECT_NAME}_PROTO_SOURCES}
|
||||
OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/fable
|
||||
)
|
||||
|
||||
# Each input file maps to multiple output files with different extensions
|
||||
# (js, js.map, d.ts, fs). Transpile ts -> d.ts (and js), then d.ts -> fs.
|
||||
# Who knew front-end development could be so complicated? `\_(=_=)_/`
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_TS_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.ts
|
||||
)
|
||||
set(${PROJECT_NAME}_JS_PROTOS)
|
||||
set(${PROJECT_NAME}_TS_PROTOS)
|
||||
set(${PROJECT_NAME}_FS_PROTOS)
|
||||
foreach(_ts_source ${${PROJECT_NAME}_TS_SOURCES})
|
||||
get_filename_component(_ts_source_we ${_ts_source} NAME_WE)
|
||||
set(_prefix ${CMAKE_CURRENT_BINARY_DIR}/${_ts_source_we})
|
||||
list(APPEND ${PROJECT_NAME}_JS_PROTOS ${_prefix}.js ${_prefix}.js.map)
|
||||
list(APPEND ${PROJECT_NAME}_TS_PROTOS ${_prefix}.d.ts ${_prefix}.d.ts.map)
|
||||
list(APPEND ${PROJECT_NAME}_FS_PROTOS ${_prefix}.fs)
|
||||
add_custom_command(
|
||||
OUTPUT ${_prefix}.fs
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND npx --no-install ts2fable ${_prefix}.d.ts ${_prefix}.fs
|
||||
DEPENDS ${_prefix}.d.ts
|
||||
)
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bundle.js
|
||||
COMMAND ${_quiet_exec}
|
||||
ARGS
|
||||
${_venv_exec}
|
||||
${_venv_python}
|
||||
${_build_admin_web_app_py}
|
||||
--protos ${_proto_abs_SOURCES}
|
||||
--tsconfig ${_tsconfig}
|
||||
--mode ${WEBPACK_MODE}
|
||||
--output-dir ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Building Spark.Admin.WebApp"
|
||||
DEPENDS ${_build_admin_web_app_deps})
|
||||
OUTPUT
|
||||
${${PROJECT_NAME}_JS_PROTOS}
|
||||
${${PROJECT_NAME}_TS_PROTOS}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND npx
|
||||
--no-install tsc
|
||||
-p ${_tsconfig}
|
||||
DEPENDS
|
||||
${_tsconfig}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/proto.js
|
||||
)
|
||||
add_custom_target(
|
||||
transpile_type_script
|
||||
ALL
|
||||
DEPENDS
|
||||
${${PROJECT_NAME}_JS_PROTOS}
|
||||
${${PROJECT_NAME}_TS_PROTOS}
|
||||
)
|
||||
add_custom_target(generate_fsts_stubs ALL DEPENDS ${${PROJECT_NAME}_FS_PROTOS})
|
||||
|
||||
add_custom_target(admin_web_app_bundle ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bundle.js)
|
||||
sym_link_dir(
|
||||
TARGET_NAME link_webapp_public
|
||||
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/public
|
||||
DEST ${CMAKE_CURRENT_BINARY_DIR}/public
|
||||
)
|
||||
sym_link_dir(
|
||||
TARGET_NAME link_webapp_root
|
||||
SOURCE ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEST ${CMAKE_SOURCE_DIR}/build/Spark.Admin.WebApp
|
||||
)
|
||||
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_FS_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.fs
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/bundle.js
|
||||
$<$<CONFIG:DEBUG>:${CMAKE_CURRENT_BINARY_DIR}/bundle.js.map>
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
COMMAND dotnet restore
|
||||
COMMAND dotnet tool restore
|
||||
COMMAND export SPARK_ADMIN_WEB_APP_OUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND dotnet fable -o ${CMAKE_CURRENT_BINARY_DIR} --verbose
|
||||
COMMAND npm run build -- --mode $<IF:$<CONFIG:Debug>,"development","production">
|
||||
DEPENDS
|
||||
${${PROJECT_NAME}_FS_SOURCES}
|
||||
${${PROJECT_NAME}_FS_PROTOS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Spark.Admin.WebApp.fsproj
|
||||
)
|
||||
add_custom_target(
|
||||
bundle_spark_admin_webapp ALL
|
||||
DEPENDS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/bundle.js
|
||||
$<$<CONFIG:DEBUG>:${CMAKE_CURRENT_BINARY_DIR}/bundle.js.map>
|
||||
COMMENT "Bundling Spark.Admin.WebApp"
|
||||
)
|
||||
|
||||
# Exclude 'public' dir because it is a symlink
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
PATTERN "public" EXCLUDE
|
||||
)
|
||||
# Install actual (non-symlinked) 'public' dir
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/public"
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}"
|
||||
)
|
||||
|
||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
Generated
+2755
-15230
File diff suppressed because it is too large.
Load diff
@@ -42,7 +42,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"tsutils": "^3.21.0",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"ws": "^8.12.1"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type Tab =
|
||||
| PositionManagementTab
|
||||
| MarginTab
|
||||
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
|
||||
|
||||
let securityTypes = [| SecurityType.SPOT; SecurityType.FUTURE; SecurityType.OPTION; SecurityType.SPREAD; |]
|
||||
|
||||
@@ -74,6 +74,11 @@ type TaifexExchangeField =
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type OkxExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type SIMExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
|
||||
@@ -8,6 +8,7 @@ using MathNet.Numerics.Providers.FourierTransform;
|
||||
using BCG;
|
||||
using System.Windows.Automation;
|
||||
using bts.collections;
|
||||
using mbts.co2;
|
||||
|
||||
namespace mbts.spark {
|
||||
// Publishes theos for all options (on the side, also publishes the theos for TC2).
|
||||
@@ -114,11 +115,53 @@ namespace mbts.spark {
|
||||
}
|
||||
sym_theos.set_data(k, tp);
|
||||
}
|
||||
_override_theos(ref rv);
|
||||
foreach (var kv in rv) {
|
||||
_bb.post(SparkTags.TC2Tags.LiveTheosTag(kv.Key), kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void _override_theos(ref Dictionary<(string, Venue), ExpStrikeKeyedData<TheoPoint>> theos) {
|
||||
if (_compute_midmarket) {
|
||||
SystemContractData sc_data = _bb.read_state(SparkTags.SystemContractsTag);
|
||||
foreach (var kv in theos) {
|
||||
string sym = kv.Key.Item1;
|
||||
Venue exchange = kv.Key.Item2;
|
||||
foreach (var t in kv.Value.get_data_linearly()) {
|
||||
DateTime exp = t.first;
|
||||
double strike = t.second;
|
||||
TheoPoint tp = t.third;
|
||||
var call_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Call);
|
||||
{
|
||||
NativePriceSize call_bid, call_ask;
|
||||
ulong time = 0;
|
||||
call_sec_md.Value.md.get_bbo(ref time, out call_bid, out call_ask);
|
||||
double call_mid = (call_bid.price + call_ask.price) / 2;
|
||||
tp.call = call_mid;
|
||||
}
|
||||
{
|
||||
var put_sec_md = sc_data.get_option(exchange, sym, exp, strike, PutOrCall.Put);
|
||||
NativePriceSize put_bid, put_ask;
|
||||
ulong time = 0;
|
||||
put_sec_md.Value.md.get_bbo(ref time, out put_bid, out put_ask);
|
||||
double put_mid = (put_bid.price + put_ask.price) / 2;
|
||||
tp.put = put_mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
//replace theos with market bid
|
||||
// get it from the bus and override
|
||||
} else {
|
||||
// do nothing, passes unchanged
|
||||
}
|
||||
}
|
||||
|
||||
private bool _compute_midmarket {
|
||||
get {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void _interp_and_publish() {
|
||||
var timer = Stopwatch.StartNew();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
namespace mbts.spark {
|
||||
using bts.api;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -23,14 +25,14 @@ namespace mbts.spark {
|
||||
foreach (var kv in driver_map.get_price_map()) {
|
||||
var exp = kv.Key;
|
||||
var dmd = kv.Value;
|
||||
IEnumerable<SecurityMD> exp_secs = sc_data.get_securities(
|
||||
(sec_md) => {
|
||||
var s = sec_md.security;
|
||||
return s.security_type() == SecurityType.Option &&
|
||||
s.Symbol == driver_map.option_symbol &&
|
||||
s.earliest_expiration() == exp.expiration;
|
||||
}
|
||||
);
|
||||
IEnumerable<SecurityMD> exp_secs = sc_data.get_securities((sec_md) => {
|
||||
var s = sec_md.security;
|
||||
return s.security_type() == SecurityType.Option &&
|
||||
s.Symbol == driver_map.option_symbol &&
|
||||
s.earliest_expiration() == (Security.is_perpetual_exp(exp.expiration)
|
||||
? ExpirationDate.Perpetual
|
||||
: ExpirationDate.FixedDate(exp.expiration));
|
||||
});
|
||||
foreach (var sec_md in exp_secs) {
|
||||
rv[sec_md.security.Id] = kv.Value;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace mbts.spark {
|
||||
case SecurityType.Future:
|
||||
case SecurityType.Option:
|
||||
rv.GetOrAdd((sec.Exchange, sec.security_type(), sec.Symbol), _ => new())
|
||||
.Add(sec.earliest_expiration());
|
||||
.Add(sec.earliest_expiration().Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,5 +281,16 @@ namespace mbts.spark {
|
||||
else
|
||||
return tp.put + (tp.pdelta * driver_diff) + (0.5 * tp.pgamma * driver_diff * driver_diff);
|
||||
}
|
||||
|
||||
public static TheoPoint extrapolate(TheoPoint tp, double driver, bool is_call) {
|
||||
TheoPoint rv = tp.Clone() as TheoPoint;
|
||||
double h = driver - tp.driver;
|
||||
if (is_call) {
|
||||
rv.call = tp.call + h * (tp.cdelta);
|
||||
} else {
|
||||
rv.put = tp.put + h * (tp.cdelta);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace mbts.analytics {
|
||||
// prices (specified in the vs settings).
|
||||
// It has public setters that get
|
||||
// 1. contracts
|
||||
// 2. vs inout
|
||||
// 2. vs input
|
||||
// 3. exp params(drivers, interst rate etc)
|
||||
// 4. driver prices
|
||||
// 5. vol time
|
||||
|
||||
@@ -133,17 +133,31 @@ namespace bts {
|
||||
|
||||
// set 'unit quantity' for each sec
|
||||
foreach (var smd in exch_secs.Values) {
|
||||
var meta = _security_metadata.get(smd.security);
|
||||
if (meta.HasValue) {
|
||||
exch_secs[smd.id] = new(
|
||||
smd.security,
|
||||
smd.md,
|
||||
meta.Value.HasPriceDisplay ? meta.Value.PriceDisplay : null,
|
||||
meta.Value.HasQuantityDisplay ? meta.Value.QuantityDisplay : null);
|
||||
}
|
||||
var (priceDisplay, qtyDisplay) = GetDisplaySettings(smd.security);
|
||||
exch_secs[smd.id] = new(smd.security, smd.md, priceDisplay, qtyDisplay);
|
||||
}
|
||||
_bus.post(SparkTags.ServerSecuritiesTag, exch_secs);
|
||||
}
|
||||
|
||||
(TickDisplaySettingsImmutable? Price, TickDisplaySettingsImmutable? Quantity) GetDisplaySettings(SecurityImmutable sec) {
|
||||
var meta = _security_metadata.get(sec);
|
||||
var priceDisplay = meta?.PriceDisplay ?? Security.Defaults.price_display_settingsi;
|
||||
var qtyDisplay = meta?.QuantityDisplay ?? Security.Defaults.quantity_display_settingsi;
|
||||
if (sec.HasContractSpec) {
|
||||
var pd = priceDisplay.CloneToMutable();
|
||||
pd.TickFactors = TickUtils.get_tick(sec, 0).to_api_tick_factors();
|
||||
priceDisplay = new(pd);
|
||||
if (sec.ContractSpec.QuantityInfos.TryGetFirst(out var qtyInfo)) {
|
||||
var qd = qtyDisplay.CloneToMutable();
|
||||
qd.ShowDecimalPart = qtyInfo.TickBase.is_real_non_zero() && !(qtyInfo.TickUnit / qtyInfo.TickBase).is_int();
|
||||
qd.TickFactors ??= new();
|
||||
qd.TickFactors.TickBase = qtyInfo.TickBase;
|
||||
qd.TickFactors.TickUnit = qtyInfo.TickUnit;
|
||||
qtyDisplay = new(qd);
|
||||
}
|
||||
}
|
||||
return (priceDisplay, qtyDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
|
||||
@@ -11,8 +11,9 @@ namespace Spark.Trading {
|
||||
|
||||
using mbts.analytics;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.MarketData;
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Api.Reactive;
|
||||
using Spark.Core;
|
||||
@@ -81,6 +82,11 @@ namespace Spark.Trading {
|
||||
pnl = bus.subscribe(SparkTags.ReportBlackboardTags.PNL.PNLData)
|
||||
.Select(d => d.position_data.ToDictionary(u => u.id(), PNLRowData.from_pnl_unit))
|
||||
.Table(PNLRowData.key_fn)
|
||||
.JoinBbo(
|
||||
bus.subscribe(SparkTags.SecuritiesTag),
|
||||
pnlRow => pnlRow.key.security_id,
|
||||
(pnlRow, bbo) => pnlRow.with(bid_price: bbo.BidPrice, ask_price: bbo.AskPrice),
|
||||
PNLRowData.key_fn)
|
||||
.FullJoin(
|
||||
order_data.working_account_security_orders,
|
||||
PNLRowData.key_fn,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace mbts.spark {
|
||||
public enum MarketViewColumnType {
|
||||
None,
|
||||
Venue,
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace bts.shell.market_view {
|
||||
using bts.api;
|
||||
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
|
||||
@@ -8,8 +10,15 @@ namespace bts.shell.market_view {
|
||||
|
||||
[ProtoContract]
|
||||
class MarketViewFilterStates {
|
||||
[ProtoMember(37)]
|
||||
public Venue[] venue { get; set; }
|
||||
|
||||
[ProtoMember(38)]
|
||||
public string[] currency { get; set; }
|
||||
|
||||
[ProtoMember(1)]
|
||||
public string security { get; set; }
|
||||
|
||||
[ProtoMember(2)]
|
||||
public DoubleMinMaxState bid_price { get; set; }
|
||||
|
||||
@@ -123,6 +132,8 @@ namespace bts.shell.market_view {
|
||||
public Func<MarketViewVM.Row, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
// security
|
||||
pb.add_discrete_values(venue, r => r.venue);
|
||||
pb.add_discrete_values(currency, r => r.currency);
|
||||
pb.add_regex(security, r => r.security.ToString());
|
||||
pb.add_regex(security_key, r => r.security_key.ToString());
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -92,6 +92,7 @@ namespace bts.shell.market_view {
|
||||
}
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings venue => _mk(Columns.Venue, ColHeader.Exchange, ColWidth.Exchange, h_align: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security => _mk(Columns.Security, ColHeader.Security, ColWidth.Security, h_align: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings currency => _mk(Columns.Currency, ColHeader.Currency, ColWidth.Currency, h_align: HorizontalAlignment.Left, is_visible: false);
|
||||
public static GridColumnSettings volume => _mk(Columns.Volume, ColHeader.Volume, ColWidth.Volume, h_align: HorizontalAlignment.Left);
|
||||
|
||||
@@ -98,7 +98,14 @@ namespace mbts.spark {
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_settings_hub = settings_hub;
|
||||
security_filter = new RegexFilterVM<Row>(r => r.security.ToString(), nameof(Row.security));
|
||||
currency_filter =new RegexFilterVM<Row>(r => r.currency, nameof(Row.currency));
|
||||
currency_filter = DiscreteValuesFilterVM.Create<Row, string>(
|
||||
bus.subscribe(SparkTags.SecuritiesTag)
|
||||
.Sample(TimeSpan.FromSeconds(1))
|
||||
.Select(sec_mds => sec_mds.Values.Select(smd => smd.security.Currency).Distinct().ToList()),
|
||||
r => r.currency,
|
||||
nameof(Row.currency),
|
||||
EqualityComparer<string>.Default,
|
||||
Comparer<string>.Default);
|
||||
// rows
|
||||
_wrapped_rows = _rows.WrapSelector(r => r.security_md, s => {
|
||||
var r = new Row(_bus, s, _orders_model, _position_model);
|
||||
@@ -122,8 +129,9 @@ namespace mbts.spark {
|
||||
const double working_orders_col_width = 105;
|
||||
var price_converter = new TickDisplayConverter();
|
||||
|
||||
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
|
||||
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.DiscreteValues, ColWidth.Currency);
|
||||
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
|
||||
|
||||
// last
|
||||
@@ -388,8 +396,9 @@ namespace mbts.spark {
|
||||
internal IBus bus => _bus;
|
||||
internal OrdersModel orders_model => _orders_model;
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, Venue[]> venue_filter { get; } = DiscreteValuesFilterVM.Common.exchange<Row>(r => r.venue, nameof(Row.venue));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string[]> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.bid_qty_display, nameof(Row.bid_qty_display));
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> dbid_qty_filter { get; } = new DoubleMinMax<Row>(r => r.dbid_qty_display, nameof(Row.dbid_qty_display));
|
||||
@@ -535,6 +544,8 @@ namespace mbts.spark {
|
||||
|
||||
public MarketViewFilterStates get_filter_states() =>
|
||||
new() {
|
||||
venue = venue_filter.get_state(),
|
||||
currency = currency_filter.get_state(),
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_qty_filter.get_state(),
|
||||
@@ -580,6 +591,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
using var _ = rows_view.DeferRefresh();
|
||||
venue_filter.load_state(filters.venue);
|
||||
currency_filter.load_state(filters.currency);
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_qty_filter.load_state(filters.bid_merged_size);
|
||||
@@ -616,16 +629,16 @@ namespace mbts.spark {
|
||||
|
||||
static (string desc, Color fg) _fills_title(ColKey key) =>
|
||||
key switch {
|
||||
ColKey.Position => ("Day Fills", Colors.Black),
|
||||
ColKey.RawDayPosition => ("Raw Day Fills", Colors.Black),
|
||||
_ => ("Fills", Colors.Black),
|
||||
ColKey.Position => ("Day Fills", BTSColors.DayFills.foreground),
|
||||
ColKey.RawDayPosition => ("Raw Day Fills", BTSColors.DayFills.foreground),
|
||||
_ => ("Fills", BTSColors.UI.default_text),
|
||||
};
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(ColKey key) =>
|
||||
key switch {
|
||||
ColKey.WorkingBids => ((string desc, Color fg))("Working Buy Orders", BTSColors.MarketData.bid),
|
||||
ColKey.WorkingAsks => ((string desc, Color fg))("Working Sell Orders", BTSColors.MarketData.ask),
|
||||
_ => ("Working Orders", Colors.Black),
|
||||
_ => ("Working Orders", BTSColors.UI.default_text),
|
||||
};
|
||||
|
||||
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
@@ -127,7 +128,11 @@ namespace mbts.ui.spark {
|
||||
size,
|
||||
Observable.Return(calc_buttons));
|
||||
|
||||
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
|
||||
_show_popup(
|
||||
ticket_vm,
|
||||
new OrderTicketHeader { bbo = ticket_vm.bbo },
|
||||
resources,
|
||||
view => Behavior.SetAutoFocusTargetProvider(view, AutoFocusTargetProvider.TypeProvider(typeof(TextBox), view)));
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header) {
|
||||
@@ -136,7 +141,7 @@ namespace mbts.ui.spark {
|
||||
_show_popup(content, header, resources);
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header, ResourceDictionary resources) {
|
||||
void _show_popup(object content, object header, ResourceDictionary resources, Action<HeaderedContentControl> init_view = null) {
|
||||
var view = new HeaderedContentControl {
|
||||
DataContext = content,
|
||||
Content = content,
|
||||
@@ -146,6 +151,7 @@ namespace mbts.ui.spark {
|
||||
Resources = resources ?? new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
init_view?.Invoke(view);
|
||||
_popup_man.show(view);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ using bts.view;
|
||||
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
@@ -27,12 +29,18 @@ using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewSettings>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
|
||||
static int _window_id_counter = 0;
|
||||
|
||||
readonly string _window_id = $"{_window_type}_{Interlocked.Increment(ref _window_id_counter)}";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
readonly IPositionModel _position_model;
|
||||
@@ -122,7 +130,7 @@ namespace mbts.spark {
|
||||
Log.notnull(_state);
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_id);
|
||||
var security_md_map_src = Observable.Create<IReadOnlyDictionary<uint, SecurityMD>>(o => {
|
||||
IDisposable sub = null;
|
||||
_security_criteria_changed += on_security_criteria_changed;
|
||||
@@ -286,7 +294,32 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) {
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
ui.WindowStyle.AddWinFormsHostChangedHandler(parent_view, OnWinFormsHostChanged);
|
||||
|
||||
void OnWinFormsHostChanged(object sender, RoutedPropertyChangedEventArgs<WPFPanel> e) {
|
||||
// If this window is "activated" by the user clicking on a popup window (such as an
|
||||
// order ticket), it never receives keyboard focus (seems like a bug in Windows). As a
|
||||
// workaround we explicitly focus the view when the parent WPFPanel gets focus. If the
|
||||
// user clicks on the popup a second time, it should get keyboard focus.
|
||||
if (e.OldValue is not null) {
|
||||
e.OldValue.GotFocus -= OnGotFocus;
|
||||
}
|
||||
if (e.NewValue is not null) {
|
||||
e.NewValue.GotFocus += OnGotFocus;
|
||||
}
|
||||
|
||||
void OnGotFocus(object sender, EventArgs e) =>
|
||||
_view.Dispatcher.InvokeAsync(StealFocus, DispatcherPriority.Send);
|
||||
|
||||
void StealFocus() {
|
||||
if (_view.IsVisible) {
|
||||
Log.Ui.info($"MarketViewWindow. Stealing focus from WPFPanel");
|
||||
_view.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
|
||||
@@ -35,7 +36,9 @@
|
||||
const double _VERTICAL_SCROLLBAR_OVERLAP = 9;
|
||||
|
||||
static readonly TimeSpan _trade_poll_interval = TimeSpan.FromSeconds(0.4);
|
||||
static int _window_id_counter = 0;
|
||||
|
||||
readonly string _window_id = $"{WINDOW_TYPE_NAME}_{Interlocked.Increment(ref _window_id_counter)}";
|
||||
[DisallowNull]
|
||||
readonly MDConsumer _consumer;
|
||||
[DisallowNull]
|
||||
@@ -133,7 +136,7 @@
|
||||
}
|
||||
return result;
|
||||
});
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(WINDOW_DISPLAY_NAME);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_id);
|
||||
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
|
||||
|
||||
SparkCommand toggle_is_paused_cmd = null;
|
||||
|
||||
@@ -626,6 +626,22 @@
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_SpreaderType_Label"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Type"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_SpreaderType_DropDown"
|
||||
Grid.Column="7"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
ItemsSource="{TemplateBinding SpreaderTypeItemsSource}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding SpreaderType, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
<!-- <TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_HedgePricing_Label"
|
||||
VerticalAlignment="Center"
|
||||
@@ -642,7 +658,7 @@
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
/> -->
|
||||
</Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="PART_DataGrid"
|
||||
|
||||
@@ -237,6 +237,29 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SpreaderTypeItemsSource
|
||||
public static readonly DependencyProperty SpreaderTypeItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
|
||||
nameof(SpreaderTypeItemsSource));
|
||||
|
||||
public IEnumerable SpreaderTypeItemsSource {
|
||||
get => GetValue(SpreaderTypeItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(SpreaderTypeItemsSourceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SpreaderType
|
||||
public static readonly DependencyProperty SpreaderTypeProperty =
|
||||
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
|
||||
nameof(SpreaderType),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public object SpreaderType {
|
||||
get => GetValue(SpreaderTypeProperty);
|
||||
set => SetValue(SpreaderTypeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate() =>
|
||||
_connect_leg_tuples();
|
||||
|
||||
|
||||
@@ -117,6 +117,14 @@ namespace bts.shell {
|
||||
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
|
||||
#endregion
|
||||
|
||||
public static SniperQuoteParams SniperQuoteParams => new() {
|
||||
NTicks = 0
|
||||
};
|
||||
|
||||
public static ReverseHedgeQuoteParams ReverseHedgeQuoteParams => new() {
|
||||
NTicks = 0
|
||||
};
|
||||
|
||||
#region PreHedge
|
||||
public static PreHedgeParams PreHedgeParams => new PreHedgeParams {
|
||||
MaxPriceFromInsideMarket = PreHedgeMaxPriceFromInsideMarket,
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
ContractPickerVM="{Binding contract_picker_vm}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
SpreaderType="{Binding spreader_type}"
|
||||
SpreaderTypeItemsSource="{Binding spreader_type_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class IsQuoteModel : RowModelBase<bool> {
|
||||
public override string display_name { get; } = "Enable Quoting";
|
||||
public override string help_text { get; } = "This parameter is in the form of a check box, where if the box is checked the leg will be quoted versus not checked, it will not be shown in the market. If it is not actively quoted in the market, the leg will still be used for calculation of the entire synthetic.";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.IsActiveQuoting;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
|
||||
leg_tup.leg_params.IsActiveQuoting = value;
|
||||
|
||||
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
|
||||
!leg_tups.Any(lt => lt.leg_params.IsActiveQuoting);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteEnabledModel : RowModelBase<bool> {
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.IsActiveQuoting;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
|
||||
leg_tup.leg_params.IsActiveQuoting = value;
|
||||
|
||||
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
|
||||
!leg_tups.Any(lt => lt.leg_params.IsActiveQuoting);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using bts.shell;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
class ReverseHedgeEnabledModel : RowModelBase<bool> {
|
||||
readonly ReverseHedgeTicksModel _ticks_model;
|
||||
|
||||
public ReverseHedgeEnabledModel(ReverseHedgeTicksModel ticks_model) =>
|
||||
_ticks_model = ticks_model;
|
||||
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.ReverseHedge };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams is not null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.ReverseHedgeQuoteParams is null) {
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams = SyntheticDataHelper.Defaults.ReverseHedgeQuoteParams;
|
||||
_ticks_model.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams = null;
|
||||
}
|
||||
_ticks_model.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using bts.spark;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
internal class ReverseHedgeTicksModel : RowModelBase<int> {
|
||||
readonly Dictionary<int, int> _value_store = new();
|
||||
|
||||
public override string display_name => "Ticks";
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.ReverseHedge };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup).NTicks = _value_from_store(leg_tup);
|
||||
|
||||
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup)?.NTicks ?? _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
_value_store[leg_tup.id] = value;
|
||||
var s = _get_params(leg_tup);
|
||||
if (s is not null) {
|
||||
s.NTicks = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup) != null;
|
||||
|
||||
ReverseHedgeQuoteParams _get_params(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams;
|
||||
|
||||
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.get_value(leg_tup.id, _ => 0);
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private static class Sections {
|
||||
public static ParameterSection Quote => new ParameterSection("Quote");
|
||||
public static ParameterSection Sniper => new("Sniper", true);
|
||||
public static ParameterSection ReverseHedge => new("Reverse Hedge", true);
|
||||
public static ParameterSection Quote => new("Quote", true);
|
||||
public static ParameterSection PreHedge => new ParameterSection("Pre-Hedge");
|
||||
public static ParameterSection PostHedge => new ParameterSection("Post-Hedge");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using bts.shell;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
class SniperEnabledModel : RowModelBase<bool> {
|
||||
readonly SniperTicksModel _ticks_model;
|
||||
|
||||
public SniperEnabledModel(SniperTicksModel ticks_model) =>
|
||||
_ticks_model = ticks_model;
|
||||
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Sniper };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.SniperQuoteParams is not null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.SniperQuoteParams is null) {
|
||||
leg_tup.leg_params.SniperQuoteParams = SyntheticDataHelper.Defaults.SniperQuoteParams;
|
||||
_ticks_model.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.SniperQuoteParams = null;
|
||||
}
|
||||
_ticks_model.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using bts.spark;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
internal class SniperTicksModel : RowModelBase<int> {
|
||||
readonly Dictionary<int, int> _value_store = new();
|
||||
|
||||
public override string display_name => "Ticks";
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Sniper };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup).NTicks = _value_from_store(leg_tup);
|
||||
|
||||
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup)?.NTicks ?? _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
_value_store[leg_tup.id] = value;
|
||||
var s = _get_params(leg_tup);
|
||||
if (s is not null) {
|
||||
s.NTicks = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup) != null;
|
||||
|
||||
SniperQuoteParams _get_params(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.SniperQuoteParams;
|
||||
|
||||
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.get_value(leg_tup.id, _ => 0);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
|
||||
using SpreaderType = bts.spark.ExecutionStrategy.Types.SpreaderType;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -47,6 +48,7 @@ namespace mbts.spark {
|
||||
int _integer_tick_size = 1;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
SpreaderType _spreader_type;
|
||||
|
||||
/// <summary>
|
||||
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
|
||||
@@ -198,6 +200,8 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
var sniper_ticks_model = new SniperTicksModel();
|
||||
var reverse_hedge_ticks_model = new ReverseHedgeTicksModel();
|
||||
var show_quantity_quantity = new ShowQuantityQuantityModel();
|
||||
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
@@ -225,15 +229,18 @@ namespace mbts.spark {
|
||||
|
||||
var post_hedge_opposite_market_based_cross_threshold = new PostHedgeOppositeMarketVolumeBasedCrossThresholdModel();
|
||||
var post_hedge_opposite_market_based_cross_cross_ticks = new PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel();
|
||||
var is_quote_model = new IsQuoteModel();
|
||||
rows = new IRowModel[] {
|
||||
_quantity_model,
|
||||
_price_mult_model,
|
||||
is_quote_model,
|
||||
//new EnableHedgeModel(), //TAHA_SPREADER
|
||||
new HedgeRatioModel(),
|
||||
new SniperEnabledModel(sniper_ticks_model),
|
||||
sniper_ticks_model,
|
||||
new ReverseHedgeEnabledModel(reverse_hedge_ticks_model),
|
||||
reverse_hedge_ticks_model,
|
||||
//new MinLeanQtyModel(), //TAHA_SPREADER
|
||||
//new QuoteHiddenModel(),
|
||||
new QuoteEnabledModel(),
|
||||
new QuoteQueueHolderModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new ShowQuantityEnabledModel(show_quantity_quantity),
|
||||
@@ -337,6 +344,13 @@ namespace mbts.spark {
|
||||
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
|
||||
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
|
||||
|
||||
// spreader type
|
||||
public SpreaderType spreader_type {
|
||||
get => _spreader_type;
|
||||
set => Set(ref _spreader_type, value);
|
||||
}
|
||||
public IEnumerable<SpreaderType> spreader_type_options => EnumHelper.get_values<SpreaderType>();
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
mutable_strategy.LegParamsMap.Clear();
|
||||
@@ -352,6 +366,7 @@ namespace mbts.spark {
|
||||
_synthetic.Exchange = Venue.Bts;
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
spreader_type = strategy.SpreaderType;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
if (synthetic.HasContractSpec) {
|
||||
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
|
||||
|
||||
+20
@@ -314,6 +314,12 @@
|
||||
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Nullable Double-->
|
||||
<usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<!--Enum-->
|
||||
<usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
@@ -435,6 +441,20 @@
|
||||
</c:TickUpDownDouble>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Nullable Double-->
|
||||
<usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownNullableDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownNullableDouble.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownNullableDouble.CommandBindings>
|
||||
<c:TickUpDownNullableDouble.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownNullableDouble.InputBindings>
|
||||
</c:TickUpDownNullableDouble>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<!--Enum-->
|
||||
<usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
|
||||
+2
@@ -33,6 +33,7 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate NullableUIntTemplate { get; set; }
|
||||
public DataTemplate DoubleTemplate { get; set; }
|
||||
public DataTemplate NullableDoubleTemplate { get; set; }
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
public DataTemplate NullableEnumTemplate { get; set; }
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
@@ -52,6 +53,7 @@ namespace mbts.ui.spark {
|
||||
IAlgoParamVM pvm = item as IAlgoParamVM ?? (item as AlgoParamRowVM)?.param;
|
||||
if (pvm != null) {
|
||||
if (pvm is IAlgoParamVM<double>) return DoubleTemplate;
|
||||
if (pvm is IAlgoParamVM<double?>) return NullableDoubleTemplate;
|
||||
if (pvm is IAlgoParamVM<int>) return IntTemplate;
|
||||
if (pvm is IAlgoParamVM<int?>) return NullableIntTemplate;
|
||||
if (pvm is IAlgoParamVM<uint>) return UIntTemplate;
|
||||
|
||||
@@ -237,6 +237,17 @@ namespace mbts.spark {
|
||||
DefaultColumnWidths.Qty,
|
||||
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
|
||||
add_col(
|
||||
Columns.NotionalValue,
|
||||
DefaultColumnHeaders.NotionalValue,
|
||||
r => _get_row(r).security.spec_or_default().format(r.fill.notional_value()),
|
||||
new DoubleMinMax<FillTuple>(r => r.fill.notional_value()),
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
fs => fs.notional_value,
|
||||
(fs, cf) => fs.notional_value = cf,
|
||||
DefaultColumnWidths.NotionalValue,
|
||||
get_fg: r => r.side == Side.Buy ? BTSColors.MarketData.bid : BTSColors.MarketData.ask);
|
||||
|
||||
add_col(
|
||||
Columns.Price,
|
||||
DefaultColumnHeaders.Price,
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace mbts.ui.spark {
|
||||
Side,
|
||||
Price,
|
||||
Size,
|
||||
NotionalValue,
|
||||
FillKind,
|
||||
FillStatus,
|
||||
FillKey,
|
||||
|
||||
@@ -8,6 +8,7 @@ using mbts.ui.controls;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
@@ -61,6 +62,10 @@ namespace bts.shell.fills {
|
||||
[DIYGridColumnID(nameof(Columns.Size))]
|
||||
public DoubleMinMaxState size { get; set; }
|
||||
|
||||
[ProtoMember(18)]
|
||||
[DIYGridColumnID(nameof(Columns.NotionalValue))]
|
||||
public DoubleMinMaxState notional_value { get; set; }
|
||||
|
||||
[ProtoMember(11)]
|
||||
[DIYGridColumnID(nameof(Columns.FillKey))]
|
||||
public string fill_key { get; set; }
|
||||
@@ -87,7 +92,7 @@ namespace bts.shell.fills {
|
||||
|
||||
// PNL's Expiration group popups use this prop to filtered data by a particular exp (it isn't
|
||||
// serialized bc it's not part of the main window's settings)
|
||||
public DateTime? exp_date { get; set; }
|
||||
public ExpirationDate? exp_date { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -113,10 +118,11 @@ namespace bts.shell.fills {
|
||||
pb.add_discrete_values(fs.fill_status, ft => ft.fill.FillStatus);
|
||||
// numeric
|
||||
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
|
||||
pb.add_numeric(fs.notional_value, ft => ft.fill.notional_value());
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
if (fs.exp_date.HasValue) {
|
||||
pb.add_date_time(fs.exp_date.Value, f => f.security?.earliest_expiration());
|
||||
pb.add_exp_date(fs.exp_date.Value, f => f.security?.earliest_expiration());
|
||||
}
|
||||
return pb.to_predicate();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -66,8 +66,12 @@ namespace mbts.spark {
|
||||
_orders = orders;
|
||||
_real_price = real_price;
|
||||
_text_lines = backing(nameof(text_lines), () => {
|
||||
var fmt = MDDisplayHelper.get_value_formatter(_owner.model.security_md.quantity_display);
|
||||
return BadgeRender.format(side, _quantity, _filled, _current_badge_style, fmt);
|
||||
var tf = _owner.model.security.tick_factors();
|
||||
var fmt = MDDisplayHelper.get_value_formatter(
|
||||
owner.current_settings.size_display,
|
||||
_owner.model.security_md.quantity_display,
|
||||
tf.to_api_tick_display_settingsi());
|
||||
return BadgeRender.format(side, _quantity, _filled, price, _current_badge_style, fmt);
|
||||
});
|
||||
_clear_phantom_price = new(() => {
|
||||
_phantom_price = double.NaN;
|
||||
|
||||
@@ -100,6 +100,9 @@ namespace mbts.spark {
|
||||
readonly OrderEntryPanelItemVisibilityVM _cancel_all_visibility = new("Cancel All");
|
||||
readonly OrderEntryPanelItemVisibilityVM _status_message_visibility = new("Status Message");
|
||||
|
||||
readonly PanelPricePointVM _bid_price = new("Bid");
|
||||
readonly PanelPricePointVM _ask_price = new("Ask");
|
||||
readonly PanelPricePointVM _mid_price = new("Mid");
|
||||
readonly PanelRelativePricePointVM _day_change = new("Day Change");
|
||||
readonly PanelPricePointVM _session_high = new("High");
|
||||
readonly PanelPricePointVM _session_low = new("Low");
|
||||
@@ -139,9 +142,6 @@ namespace mbts.spark {
|
||||
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
|
||||
CustomSort = NullCheckComparer<AccountTuple>.Default
|
||||
};
|
||||
// cmds
|
||||
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
|
||||
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
|
||||
// update
|
||||
_update = new DeferrableAction(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.Invoke;
|
||||
@@ -191,26 +191,6 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
|
||||
void increment_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < 100; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, ticks_per_row + i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void decrement_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < tpr; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, tpr - i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
@@ -235,15 +215,8 @@ namespace mbts.spark {
|
||||
}
|
||||
public uint ticks_per_row {
|
||||
get => _ticks_per_row;
|
||||
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
|
||||
set => Set(ref _ticks_per_row, value);
|
||||
}
|
||||
public ICommand tick_up_ticks_per_row_cmd { get; }
|
||||
public ICommand tick_down_ticks_per_row_cmd { get; }
|
||||
// since settings profiles may be shared among ladders
|
||||
// of different securities/tick sizes, the ticks per row setting
|
||||
// may need to be rounded to a number that is a factor of $1.00
|
||||
public uint? rounded_ticks_per_row =>
|
||||
TickUtils.fit_ticks_per_row_to_dollars(_security, ticks_per_row);
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
@@ -327,7 +300,12 @@ namespace mbts.spark {
|
||||
|
||||
public IEnumerable order_entry_panel_items {
|
||||
get {
|
||||
// Order these items according to their display order in the actual ladder (from top to
|
||||
// bottom). There are a lot of them, so this is a simple convenience for the user.
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _ask_price;
|
||||
yield return _mid_price;
|
||||
yield return _bid_price;
|
||||
yield return _day_change;
|
||||
yield return _session_high;
|
||||
yield return _session_low;
|
||||
@@ -450,6 +428,12 @@ namespace mbts.spark {
|
||||
zoom_factor = settings.zoom_factor;
|
||||
// left-panel (show/hide)
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_bid_price.show = settings.bid_price_visible;
|
||||
_bid_price.load_from(settings.panel_price_point_settings.bid_price);
|
||||
_ask_price.show = settings.ask_price_visible;
|
||||
_ask_price.load_from(settings.panel_price_point_settings.ask_price);
|
||||
_mid_price.show = settings.mid_price_visible;
|
||||
_mid_price.load_from(settings.panel_price_point_settings.mid_price);
|
||||
_day_change.show = settings.day_visible;
|
||||
_day_change.load_from(settings.panel_price_point_settings.day_change);
|
||||
_session_high.show = settings.high_visible;
|
||||
@@ -537,6 +521,9 @@ namespace mbts.spark {
|
||||
.ToList();
|
||||
// left-panel price items
|
||||
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
|
||||
bid_price = _bid_price.create(),
|
||||
ask_price = _ask_price.create(),
|
||||
mid_price = _mid_price.create(),
|
||||
day_change = _day_change.create(),
|
||||
open = _open.create(),
|
||||
session_low = _session_low.create(),
|
||||
@@ -561,6 +548,9 @@ namespace mbts.spark {
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.bid_price_visible = _bid_price.show;
|
||||
clone.ask_price_visible = _ask_price.show;
|
||||
clone.mid_price_visible = _mid_price.show;
|
||||
clone.day_visible = _day_change.show;
|
||||
clone.high_visible = _session_high.show;
|
||||
clone.low_visible = _session_low.show;
|
||||
@@ -715,6 +705,7 @@ namespace mbts.spark {
|
||||
|
||||
public uint sample_quantity { get; } = 50;
|
||||
public uint sample_filled { get; } = 12;
|
||||
public double sample_price => 2175.50;
|
||||
public Side sample_side { get; } = Side.Buy;
|
||||
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
|
||||
public Brush badge_background { get; } = Brushes.White;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,9 +14,9 @@ using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -37,9 +37,6 @@ namespace mbts.spark {
|
||||
partial class LadderGridHost : Control {
|
||||
static readonly int _initial_row_count;
|
||||
static readonly bool _show_scrollbar;
|
||||
static readonly bool _lighten_market_cells;
|
||||
static readonly Color? _bid_market_cell_background;
|
||||
static readonly Color? _ask_market_cell_background;
|
||||
|
||||
readonly VisualHost _visual_host = new();
|
||||
readonly LadderOrdersHost _ordershost;
|
||||
@@ -62,6 +59,7 @@ namespace mbts.spark {
|
||||
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container = new();
|
||||
|
||||
IDisposable _settings_sub, _volume_sub, _tick_sub, _fair_value_lines_sub, _vm_sub;
|
||||
readonly ThrottledAction _scroll_to_tick_log_throttle = new(TimeSpan.FromSeconds(5));
|
||||
readonly SingleValuePump<ScrollViewer> _scroll_viewer_source;
|
||||
|
||||
ScrollViewer _scroll_viewer {
|
||||
@@ -82,6 +80,7 @@ namespace mbts.spark {
|
||||
LadderTactileAdorner _tactile_adorner;
|
||||
ulong _prices_updated;
|
||||
NativePriceSize[] _bids = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH], _asks = new NativePriceSize[NativePriceHelpers.COCONUT_MAX_DEPTH];
|
||||
SecurityMDExtensions.Book _depth = SecurityMDExtensions.Book.Empty;
|
||||
bool _price_dirty = false, _piq_dirty = false;
|
||||
LadderPriceList _price_list = new() {
|
||||
length = _initial_row_count,
|
||||
@@ -92,8 +91,10 @@ namespace mbts.spark {
|
||||
readonly MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi);
|
||||
Func<double, string> _fmt_mkt_quantity_impl = v => v.ToString();
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
Func<double, double, string> _fmt_mkt_quantity_impl = (v, p) => v.ToString();
|
||||
Func<LadderPriceLevel, string> _fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
@@ -105,17 +106,7 @@ namespace mbts.spark {
|
||||
static LadderGridHost() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
_show_scrollbar = knobs.ShowScrollbar;
|
||||
_lighten_market_cells = knobs.LightenMarketCells;
|
||||
_initial_row_count = (int)knobs.ScrollableTickWindowSize;
|
||||
// For market cell backgrounds, 0 means null. Note that the full
|
||||
// range of colors can still be expressed, as zero is merely one of
|
||||
// 2^24 possible representations of 'transparent.'
|
||||
if (knobs.BidMarketCellBackground != 0) {
|
||||
_bid_market_cell_background = BTSColors.Ladder.bid_column_background;
|
||||
}
|
||||
if (knobs.AskMarketCellBackground != 0) {
|
||||
_ask_market_cell_background = BTSColors.Ladder.ask_column_background;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderGridHost() {
|
||||
@@ -452,8 +443,8 @@ namespace mbts.spark {
|
||||
|
||||
public bool scroll_to_tick(long tick) {
|
||||
if (tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
$"Unable to scroll to tick {tick} because it exceeds int.MaxValue");
|
||||
_scroll_to_tick_log_throttle.Execute(() =>
|
||||
Log.Ui.error($"Unable to scroll to tick {tick} because it exceeds int.MaxValue"));
|
||||
return false;
|
||||
}
|
||||
return scroll_to_tick((int)tick);
|
||||
@@ -635,10 +626,30 @@ namespace mbts.spark {
|
||||
|
||||
void _send_parameterized_order(Point point, Side side) {
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
if (side == Side.Undefined)
|
||||
throw new ArgumentException("Side is undefined.", nameof(side));
|
||||
if (!(_vm.size_calculator.value is double size) || size == 0 || !(_vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false))
|
||||
void on_cannot_send(string reason) {
|
||||
Log.Ui.warn($"LadderGridHost cannot send parameterized order. {reason}");
|
||||
}
|
||||
if (_vm is null) {
|
||||
on_cannot_send("ViewModel is null");
|
||||
return;
|
||||
}
|
||||
switch (side) {
|
||||
case Side.Buy:
|
||||
case Side.Sell:
|
||||
break;
|
||||
default:
|
||||
on_cannot_send($"Invalid side '{side}'");
|
||||
return;
|
||||
}
|
||||
double size = _vm?.size_calculator?.value ?? 0;
|
||||
if (size > 0) {
|
||||
return;
|
||||
}
|
||||
bool can_expand = _vm?.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? false;
|
||||
if (!can_expand) {
|
||||
on_cannot_send("Selected algo not parameterized");
|
||||
return;
|
||||
}
|
||||
double price = _price_for_row(_row_for_point(point));
|
||||
var grid = new Grid { DataContext = _vm };
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
@@ -710,6 +721,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
async Task _show_mbo_popup(Point point) {
|
||||
if (_vm is null) {
|
||||
Log.Ui.warn("Cannot show mbo popup. ViewModel is null.");
|
||||
return;
|
||||
}
|
||||
var query = MBOPopupModel.default_query.with(
|
||||
security_id: _vm.model.security.Id,
|
||||
price_level: price_for_point(point)
|
||||
@@ -735,8 +750,8 @@ namespace mbts.spark {
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
string fmt_mbo_qty(int value) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
|
||||
string fmt_mbo_qty(double value, double price) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity(value, price)}" : _fmt_mkt_quantity(value, price);
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
@@ -870,32 +885,13 @@ namespace mbts.spark {
|
||||
switch (column.column_type) {
|
||||
case ColumnType.Order: _ordershost.set_col_width(column.settings.width); break;
|
||||
case ColumnType.Bid:
|
||||
render_market_col(_bids, _bid_market_cell_background);
|
||||
render_market_col(_depth.Bids);
|
||||
break;
|
||||
case ColumnType.Price:
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
_refresh_prices(column);
|
||||
break;
|
||||
case ColumnType.Ask:
|
||||
render_market_col(_asks, _ask_market_cell_background);
|
||||
render_market_col(_depth.Asks);
|
||||
break;
|
||||
case ColumnType.Volume:
|
||||
_volume_container.mark_dirty();
|
||||
@@ -908,9 +904,9 @@ namespace mbts.spark {
|
||||
if (_mkt_break_lines.high_render_index.HasValue)
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
|
||||
}
|
||||
void render_market_col(NativePriceSize[] depth, Color? market_cell_background) {
|
||||
void render_market_col(IEnumerable<(double Price, double Volume, double NotionalValue)> depth) {
|
||||
var mid = LadderRender.index_of(_price_list, _last_origin_price, _price_comparer);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_mkt_depth_item, _lighten_market_cells, market_cell_background);
|
||||
LadderRender.render_sizes(column.visual_tup.visual, column.visual_tup.glyph_context, mid, _price_list, depth, _settings.row_height, _spec, column.settings, _fmt_price_level);
|
||||
}
|
||||
}
|
||||
_apply_column_visual_offset();
|
||||
@@ -956,6 +952,7 @@ namespace mbts.spark {
|
||||
// **use local vm to protect against re-assignment,
|
||||
// so the events are unhooked from the same instance to which they were added
|
||||
// when the subs are disposed.
|
||||
Disposable.dispose(ref _vm_sub);
|
||||
var local_vm = _vm;
|
||||
local_vm.on_tick += _tick;
|
||||
local_vm.request_recenter += on_request_recenter;
|
||||
@@ -1014,8 +1011,10 @@ namespace mbts.spark {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
|
||||
_hide_tool_tip();
|
||||
}
|
||||
|
||||
_tooltip_view.Content = _fmt_mkt_quantity(volume);
|
||||
double price = row < 0 || row >= (_price_list?.Count ?? 0)
|
||||
? double.NaN
|
||||
: _price_for_row(row);
|
||||
_tooltip_view.Content = _fmt_mkt_quantity(volume, price);
|
||||
_tooltip_view.IsOpen = true;
|
||||
ToolTip = _tooltip_view;
|
||||
} else {
|
||||
@@ -1102,19 +1101,34 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => _can_user_send_orders_changed -= o.OnNext);
|
||||
});
|
||||
|
||||
LadderPriceList _create_price_list() {
|
||||
if (_settings is not null) {
|
||||
switch (_settings.net_change) {
|
||||
case LadderSettings.NetChangeTerms.BasisPoints:
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints:
|
||||
return new() {
|
||||
length = _initial_row_count,
|
||||
regional_tick = new RegionalTickFactors(new user.settings.TickFactors(10000, 1)),
|
||||
};
|
||||
}
|
||||
}
|
||||
return new() {
|
||||
length = _initial_row_count,
|
||||
regional_tick = _spec
|
||||
};
|
||||
}
|
||||
|
||||
#region Timer.Tick, Refresh Methods
|
||||
void _tick() {
|
||||
if (_settings != null) {
|
||||
ulong unused = 0;
|
||||
bool last_trade_changed = _vm.md.get_last_trade(ref unused, out _last_trade, out DateTime _, out ulong __);
|
||||
bool depth_changed = _vm.md.get_depth(ref _prices_updated, ref _bids, ref _asks, NativePriceHelpers.COCONUT_MAX_DEPTH);
|
||||
_depth = _vm.security_md.Depth(_bids, _asks, _vm.securities);
|
||||
_mkt_break_lines.refresh();
|
||||
if (_price_list is null) {
|
||||
Log.assert(_price_dirty);
|
||||
_price_list = new LadderPriceList {
|
||||
length = _initial_row_count,
|
||||
security = security
|
||||
};
|
||||
_price_list = _create_price_list();
|
||||
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
|
||||
if (mid_tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
@@ -1151,10 +1165,8 @@ namespace mbts.spark {
|
||||
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
|
||||
}
|
||||
}
|
||||
_refresh_prices();
|
||||
} else {
|
||||
_refresh_prices();
|
||||
}
|
||||
_refresh_prices();
|
||||
scroll_to_pending_target();
|
||||
_refresh_all_visuals();
|
||||
} else {
|
||||
@@ -1163,8 +1175,17 @@ namespace mbts.spark {
|
||||
_refresh_bid_mkt_break();
|
||||
if (_mkt_break_lines.ask_dirty)
|
||||
_refresh_ask_mkt_break();
|
||||
if (_mkt_break_lines.mid_dirty)
|
||||
if (_mkt_break_lines.mid_dirty) {
|
||||
switch (_settings.net_change) {
|
||||
case LadderSettings.NetChangeTerms.BasisPoints:
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints:
|
||||
_refresh_prices();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_refresh_mid_mkt_break();
|
||||
}
|
||||
_refresh_fair_value_lines(false);
|
||||
if (_mkt_break_lines.session_low_dirty)
|
||||
_refresh_session_low_line();
|
||||
@@ -1174,8 +1195,16 @@ namespace mbts.spark {
|
||||
_refresh_session_range();
|
||||
if (last_trade_changed) {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
if (last_index > 0) {
|
||||
LadderRender.render_last(
|
||||
_last_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_last_trade,
|
||||
last_index,
|
||||
_settings.row_height,
|
||||
_settings.last_column,
|
||||
_fmt_mkt_depth_item);
|
||||
}
|
||||
}
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
@@ -1190,25 +1219,27 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_prices() {
|
||||
void _refresh_prices(Column column = null) {
|
||||
if (_settings is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
|
||||
_settings.row_height, _settings.price_column,
|
||||
_spec);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _settings.row_height,
|
||||
_settings.price_column, _spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
column ??= _price_column;
|
||||
var ref_price = _settings.net_change switch {
|
||||
LadderSettings.NetChangeTerms.BasisPoints or
|
||||
LadderSettings.NetChangeTerms.NetBasisPoints or
|
||||
LadderSettings.NetChangeTerms.Ticks => _get_mid_market_break(),
|
||||
_ => 0.0
|
||||
};
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
ref_price,
|
||||
_settings.net_change,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
|
||||
void _refresh_all_visuals() {
|
||||
@@ -1264,33 +1295,50 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _refresh_session_low_line() {
|
||||
if (_settings.session_low_line.show && _mkt_break_lines.low_render_index.HasValue)
|
||||
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index.Value, _settings.session_low_line.color);
|
||||
if (_settings.session_low_line.show) {
|
||||
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index ?? -1, _settings.session_low_line.color);
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_session_high_line() {
|
||||
if (_settings.session_high_line.show && _mkt_break_lines.high_render_index.HasValue)
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
|
||||
if (_settings.session_high_line.show) {
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index ?? -1, _settings.session_high_line.color);
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_last_trade() {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_last(
|
||||
_last_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_last_trade,
|
||||
last_index,
|
||||
_settings.row_height,
|
||||
_settings.last_column,
|
||||
_fmt_mkt_depth_item);
|
||||
}
|
||||
|
||||
void _refresh_piq() {
|
||||
if (!_settings.piq_column.show)
|
||||
return;
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_piqs(
|
||||
_piq_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_price_list,
|
||||
_vm.price_vs_piq,
|
||||
_settings.row_height,
|
||||
_spec,
|
||||
_settings.piq_column,
|
||||
_fmt_mkt_quantity);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
Log.assert_d(_price_list != null);
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _bids, _settings.row_height, _spec, _settings.bid_column, _fmt_mkt_depth_item, _lighten_market_cells, _bid_market_cell_background);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _asks, _settings.row_height, _spec, _settings.ask_column, _fmt_mkt_depth_item, _lighten_market_cells, _ask_market_cell_background);
|
||||
LadderRender.render_sizes(_bid_vis_tup.visual, _bid_vis_tup.glyph_context, origin_index, _price_list, _depth.Bids, _settings.row_height, _spec, _settings.bid_column, _fmt_price_level);
|
||||
LadderRender.render_sizes(_ask_vis_tup.visual, _ask_vis_tup.glyph_context, origin_index, _price_list, _depth.Asks, _settings.row_height, _spec, _settings.ask_column, _fmt_price_level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1345,7 +1393,7 @@ namespace mbts.spark {
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size, _last_trade.price)} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1361,7 +1409,7 @@ namespace mbts.spark {
|
||||
return;
|
||||
if (_price_list is null) {
|
||||
_price_list = new LadderPriceList {
|
||||
security = security
|
||||
regional_tick = _spec
|
||||
};
|
||||
}
|
||||
_price_list.reset(null, null, _initial_row_count);
|
||||
@@ -1398,29 +1446,42 @@ namespace mbts.spark {
|
||||
double _get_bid_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
var v = best_bid
|
||||
.if_nan(_spec.tickdown(best_ask))
|
||||
.if_nan(_vm?.settlement ?? double.NaN);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
double _normalize_price(double price) =>
|
||||
TickUtils.normalize_price(_spec, price);
|
||||
|
||||
double _get_ask_market_break() {
|
||||
if (_vm is null) {
|
||||
return double.NaN;
|
||||
}
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
var v = _spec.tickdown(best_ask)
|
||||
.if_nan(best_bid)
|
||||
.if_nan(_vm?.settlement ?? double.NaN);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
double _get_mid_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
var v = TickUtils.mid_tick(_spec, best_bid, best_ask)
|
||||
.if_nan(best_bid)
|
||||
.if_nan(_spec?.tickdown(best_ask) ?? double.NaN)
|
||||
.if_nan(_vm?.settlement ?? double.NaN);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
double _get_session_low() => _normalize_price(_get_session_low_raw());
|
||||
double _get_session_high() => _normalize_price(_get_session_high_raw());
|
||||
|
||||
double _get_session_low_raw() => _vm.md.session_low;
|
||||
double _get_session_high_raw() => _vm.md.session_high;
|
||||
double _get_session_low_raw() => _vm?.md?.session_low ?? double.NaN;
|
||||
double _get_session_high_raw() => _vm?.md?.session_high ?? double.NaN;
|
||||
|
||||
void _render_mkt_break(DrawingVisual vis, double price, uint color) =>
|
||||
LadderRender.render_mkt_break(vis, _row_for_price(price), _settings.row_height, color);
|
||||
@@ -1441,11 +1502,17 @@ namespace mbts.spark {
|
||||
}
|
||||
/// The viewmodel will always have the most up-to-date settings, so ignore <see cref="s"/> and just get it from <see cref="_vm"/>.
|
||||
/// This is a workaround for the global settings-defaults issue: when defaults are updated, some properties are inadvertantly overwritten, such as column-widths, etc.
|
||||
_settings = _vm.get_save_state();
|
||||
_settings = _vm?.get_save_state() ?? s;
|
||||
_pending_scroll_to_origin |= _settings == null;
|
||||
_invalidate_spec();
|
||||
if (_price_list != null) {
|
||||
_price_list.security = security;
|
||||
_pending_price_list = null;
|
||||
if (_price_list.Count > 0) {
|
||||
var high_price = _price_list.First();
|
||||
var new_tickdiff = _spec.count_ticks(high_price);
|
||||
_price_list.high_tick = (long)new_tickdiff;
|
||||
}
|
||||
_price_list.regional_tick = _spec;
|
||||
}
|
||||
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
|
||||
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
|
||||
@@ -1457,9 +1524,13 @@ namespace mbts.spark {
|
||||
static GlyphContext glyph_context(LadderColumnSettings cs) =>
|
||||
GlyphContext.create(cs.font_family, cs.font_weight);
|
||||
|
||||
var price_display = security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi();
|
||||
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
|
||||
_settings.size_display, _qty_display);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(_qty_display);
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_fmt_price_level_impl = MDDisplayHelper.get_formatter_lpl(
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_set_columns();
|
||||
_apply_column_visual_offset();
|
||||
if (prev_row_height != _settings.row_height)
|
||||
@@ -1469,18 +1540,16 @@ namespace mbts.spark {
|
||||
_mkt_break_lines.fair_value_line_views.Clear();
|
||||
// Clear and re-add the columns to the viewer to ensure offset/visibility is correct
|
||||
_visual_host.Clear();
|
||||
if (LadderSettings.mimic_tt) {
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
}
|
||||
// The session_range_vis sits beneath the grid backgrounds (on the
|
||||
// z-axis) to avoid obfuscating the font color (dj wants it to be
|
||||
// pure white). Historically, it sat atop the price column, with a
|
||||
// low opacity to slightly darken the range of prices inside of the
|
||||
// session range (i.e., the price-band of the day's trades). In
|
||||
// conjuction with this change, the price column's background
|
||||
// opacity is made slightly less than 1 (in the default settings),
|
||||
// while the font color remains fully opaque, so this layer will
|
||||
// darken the background without affecting the font color.
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
// Renders the grid lines and column backgrounds
|
||||
_visual_host.AddVisualChild(_gridvis);
|
||||
// Orders column
|
||||
@@ -1509,19 +1578,19 @@ namespace mbts.spark {
|
||||
_visual_host.AddVisualChild(_mid_mkt_break_vis);
|
||||
_visual_host.AddVisualChild(_ask_mkt_break_vis);
|
||||
_visual_host.AddVisualChild(_bid_mkt_break_vis);
|
||||
_fair_value_lines_sub = ui.utility.ObservableAutoWrapper.connect(
|
||||
_vm.fair_value_lines,
|
||||
_mkt_break_lines.fair_value_line_views,
|
||||
vm => {
|
||||
var with_vis = new FairValueLineWithVis(vm);
|
||||
_visual_host.AddVisualChild(with_vis.vis);
|
||||
return with_vis;
|
||||
},
|
||||
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
|
||||
if (_vm is not null) {
|
||||
_fair_value_lines_sub = ui.utility.ObservableAutoWrapper.connect(
|
||||
_vm.fair_value_lines,
|
||||
_mkt_break_lines.fair_value_line_views,
|
||||
vm => {
|
||||
var with_vis = new FairValueLineWithVis(vm);
|
||||
_visual_host.AddVisualChild(with_vis.vis);
|
||||
return with_vis;
|
||||
},
|
||||
with_vis => _visual_host.RemoveVisualChild(with_vis.vis));
|
||||
}
|
||||
_visual_host.AddVisualChild(_session_low_vis);
|
||||
_visual_host.AddVisualChild(_session_high_vis);
|
||||
if (!LadderSettings.mimic_tt)
|
||||
_visual_host.AddVisualChild(_session_range_vis);
|
||||
_render_grid_background();
|
||||
_init_tactile_adorner();
|
||||
// this effectively sets the background of the "Last" column //deleted to fix color
|
||||
@@ -1530,6 +1599,7 @@ namespace mbts.spark {
|
||||
// }
|
||||
scroll_to_pending_target();
|
||||
visible_columns_changed?.Invoke();
|
||||
_refresh_all_visuals();
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
@@ -1537,16 +1607,27 @@ namespace mbts.spark {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_depth_item_impl;
|
||||
impl ??= MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default(), _qty_display);
|
||||
impl ??= MDDisplayHelper.get_formatter_nps(
|
||||
MarketSizeDisplay.get_default(), _qty_display, _spec.tick_factors(0).to_api_tick_display_settingsi());
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
string _fmt_price_level(LadderPriceLevel value) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_price_level_impl;
|
||||
impl ??= MDDisplayHelper.get_formatter_lpl(
|
||||
MarketSizeDisplay.get_default(), _qty_display, security.regional_ticks().tick_factors(0).to_api_tick_display_settingsi());
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
|
||||
string _fmt_mkt_quantity(double value) {
|
||||
string _fmt_mkt_quantity(double value, double price) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_quantity_impl;
|
||||
return impl is null ? value.ToString() : impl(value);
|
||||
return impl is null ? value.ToString() : impl(value, price);
|
||||
}
|
||||
|
||||
void _init_tactile_adorner() {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
namespace mbts.spark;
|
||||
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
readonly struct LadderPriceLevel : IEquatable<LadderPriceLevel> {
|
||||
public LadderPriceLevel(double price = double.NaN, double volume = 0, double notionalValue = double.NaN) {
|
||||
Price = price;
|
||||
Volume = volume;
|
||||
NotionalValue = notionalValue.is_nan() ? volume * price : notionalValue;
|
||||
}
|
||||
|
||||
public static LadderPriceLevel Empty => new();
|
||||
|
||||
public double Price { get; }
|
||||
public double Volume { get; }
|
||||
public double NotionalValue { get; }
|
||||
|
||||
public LadderPriceLevel Add(double price, double volume) {
|
||||
if (!(volume > 0)) {
|
||||
return this;
|
||||
}
|
||||
if (!(Volume > 0)) {
|
||||
return new(price, volume);
|
||||
}
|
||||
Log.assert(Price.is_real());
|
||||
Log.assert(price.is_real());
|
||||
var newVolume = Volume + volume;
|
||||
var newPrice = (Price * Volume + price * volume) / newVolume;
|
||||
return new(newPrice, newVolume);
|
||||
}
|
||||
|
||||
public LadderPriceLevel Add(IEnumerable<(double Price, double Volume)> items) {
|
||||
var (totalPrice, totalVolume) = items.Sum();
|
||||
return totalVolume > 0
|
||||
? Add(totalPrice / totalVolume, totalVolume)
|
||||
: this;
|
||||
}
|
||||
|
||||
public Builder ToBuilder() =>
|
||||
new() { Cost = Price, Volume = Volume };
|
||||
|
||||
public LadderPriceLevel With(double price = double.NaN, double volume = double.NaN, double notionalValue = double.NaN) =>
|
||||
new(price.if_nan(Price), volume.if_nan(Volume), notionalValue.if_nan(NotionalValue));
|
||||
|
||||
public bool Equals(LadderPriceLevel other) =>
|
||||
MathUtils.epsilon_eq_comparer.Equals(Price, other.Price)
|
||||
&& MathUtils.epsilon_eq_comparer.Equals(Volume, other.Volume);
|
||||
|
||||
public override bool Equals([NotNullWhen(true)] object obj) =>
|
||||
obj is LadderPriceLevel other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(
|
||||
MathUtils.epsilon_eq_comparer.GetHashCode(Price),
|
||||
MathUtils.epsilon_eq_comparer.GetHashCode(Volume));
|
||||
|
||||
public class Builder {
|
||||
double notionalValue = double.NaN;
|
||||
|
||||
public double Cost { get; set; } = double.NaN;
|
||||
public double Volume { get; set; } = 0;
|
||||
public double NotionalValue => notionalValue.is_nan() ? Volume * Cost : notionalValue;
|
||||
|
||||
public void Add(double price, double volume) {
|
||||
if (!(volume > 0)) {
|
||||
return;
|
||||
}
|
||||
Log.assert(price.is_real());
|
||||
Cost += price * volume;
|
||||
Volume += volume;
|
||||
}
|
||||
|
||||
public void Add(double price, double volume, double notionalValue) {
|
||||
if (!(volume > 0)) {
|
||||
return;
|
||||
}
|
||||
Log.assert(price.is_real());
|
||||
Cost += price * volume;
|
||||
Volume += volume;
|
||||
if (this.notionalValue.is_nan()) {
|
||||
this.notionalValue = notionalValue;
|
||||
} else{
|
||||
this.notionalValue += notionalValue;
|
||||
}
|
||||
}
|
||||
|
||||
public LadderPriceLevel ToImmutable() =>
|
||||
Volume > 0
|
||||
? new(Cost / Volume, Volume, NotionalValue)
|
||||
: Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using bts.api;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
class LadderPriceLineViewAdapter {
|
||||
public static double bid_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ask_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double mid_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (double.IsNaN(bid)) {
|
||||
if (double.IsNaN(ask)) {
|
||||
return settlement;
|
||||
}
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (double.IsNaN(ask)) {
|
||||
return bid;
|
||||
}
|
||||
if (spec is not null) {
|
||||
return TickUtils.mid_tick(spec, bid, ask);
|
||||
}
|
||||
return bid;
|
||||
}
|
||||
}
|
||||
|
||||
static double _try_tick_down(RegionalTickFactors spec, double value) {
|
||||
if (spec is not null) {
|
||||
return spec.tickdown(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,33 +16,34 @@ namespace mbts.spark {
|
||||
long _length;
|
||||
long _high_tick;
|
||||
SecurityImmutable? _security;
|
||||
RegionalTickFactors _regional_tick_backing;
|
||||
RegionalTickFactors _regional_tick =>
|
||||
_regional_tick_backing ??= TickUtils.get_regional_ticks(_security);
|
||||
|
||||
public LadderPriceList() { }
|
||||
public LadderPriceList()
|
||||
: this(RegionalTickFactors.Default) { }
|
||||
|
||||
public LadderPriceList(RegionalTickFactors regional_tick) =>
|
||||
this.regional_tick = regional_tick;
|
||||
|
||||
public LadderPriceList(LadderPriceList other) {
|
||||
_length = other._length;
|
||||
_high_tick = other._high_tick;
|
||||
_security = other._security;
|
||||
regional_tick = other.regional_tick;
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
RegionalTickFactors _regional_tick;
|
||||
public RegionalTickFactors regional_tick {
|
||||
get => _regional_tick;
|
||||
set {
|
||||
if (value == _security) {
|
||||
return;
|
||||
if (value is null) {
|
||||
_regional_tick = RegionalTickFactors.Default;
|
||||
} else {
|
||||
_regional_tick = value;
|
||||
}
|
||||
_security = value;
|
||||
_regional_tick_backing = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ContractSpecificationImmutable spec { get; set; }
|
||||
|
||||
public long high_tick {
|
||||
get => _high_tick;
|
||||
set => _high_tick = value;
|
||||
@@ -54,8 +55,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public double start {
|
||||
get => _regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = _regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
get => regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
}
|
||||
|
||||
public long length {
|
||||
@@ -80,7 +81,7 @@ namespace mbts.spark {
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public double this[int idx] {
|
||||
get => _regional_tick.tick<double>(0, high_tick - idx);
|
||||
get => regional_tick.tick<double>(0, high_tick - idx);
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ namespace mbts.spark {
|
||||
GetEnumerator();
|
||||
|
||||
public int IndexOf(double item) {
|
||||
var idx = _regional_tick.tickdiff(start, item) ?? -1;
|
||||
var idx = regional_tick.tickdiff(start, item) ?? -1;
|
||||
if (idx < 0 || idx >= length) {
|
||||
return -1;
|
||||
}
|
||||
@@ -209,7 +210,7 @@ namespace mbts.spark {
|
||||
other != null &&
|
||||
_high_tick == other._high_tick &&
|
||||
_length == other._length &&
|
||||
_regional_tick.safe_equals(other._regional_tick);
|
||||
regional_tick.safe_equals(other.regional_tick);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LadderPriceList);
|
||||
@@ -218,7 +219,7 @@ namespace mbts.spark {
|
||||
HashCode.Combine(_high_tick, _length);
|
||||
|
||||
double _tick_to_price(long tick) =>
|
||||
_regional_tick.tick<double>(0, tick);
|
||||
regional_tick.tick<double>(0, tick);
|
||||
|
||||
void _add_tick(long tick) {
|
||||
int idx;
|
||||
|
||||
@@ -31,19 +31,19 @@ namespace mbts.spark {
|
||||
Side side,
|
||||
double quantity,
|
||||
double filled,
|
||||
double price,
|
||||
LadderSettings.BadgeStyle style,
|
||||
Func<double, string> fmt_value = null
|
||||
Func<double, double, string> fmt_value = null
|
||||
) {
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<double>();
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
switch (style) {
|
||||
case LadderSettings.BadgeStyle.Alternate:
|
||||
var working_qty = quantity > filled ? quantity - filled : 0;
|
||||
return new[] {
|
||||
$"{side.abbreviate()}:{fmt_value(filled)}",
|
||||
$"W:{fmt_value(working_qty)}"
|
||||
$"{side.abbreviate()}:{fmt_value(filled, price)}",
|
||||
$"W:{fmt_value(working_qty, price)}"
|
||||
};
|
||||
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
|
||||
default: return new[] { $"{fmt_value(filled, price)}/{fmt_value(quantity, price)}" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,7 @@ namespace mbts.spark {
|
||||
static class LadderRender {
|
||||
// Note: the orders-column has a separate "row_padding" value: Badge.DefaultMargin
|
||||
// (see how it is incorporated in measure_row_height() below)
|
||||
public static Thickness row_padding => LadderSettings.mimic_tt ?
|
||||
new Thickness(4, 2, 4, 2) : new Thickness(6);
|
||||
public static Thickness row_padding => new Thickness(4, 2, 4, 2);
|
||||
|
||||
const string tick_separator_period = ".";
|
||||
const string tick_separator_dash = "-";
|
||||
@@ -79,13 +78,6 @@ namespace mbts.spark {
|
||||
public static int index_of(IList<double> prices, double price, IComparer<double> comparer, bool relative_idx) {
|
||||
for (int i = 1; i < prices.Count; i++) {
|
||||
Log.assert(prices[i - 1] > prices[i]);
|
||||
var c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
if (c >= 0) {
|
||||
c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
} else {
|
||||
c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
}
|
||||
Log.assert(c < 0);
|
||||
}
|
||||
var idx = CollectionHelper.binary_search(prices, price, comparer);
|
||||
if (relative_idx && idx < 0) {
|
||||
@@ -109,17 +101,64 @@ namespace mbts.spark {
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
|
||||
-4, VerticalAlignment.Center, row * row_height, true);
|
||||
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec) {
|
||||
public static GlyphRun[] format_prices(
|
||||
IList<double> prices,
|
||||
double reference_price,
|
||||
LadderSettings.NetChangeTerms terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
GlyphContext glyph_context,
|
||||
RegionalTickFactors spec,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default
|
||||
) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
string basis_pts_fmt = terms switch {
|
||||
LadderSettings.NetChangeTerms.BasisPoints or
|
||||
LadderSettings.NetChangeTerms.NetBasisPoints => get_basis_pts_fmt(),
|
||||
_ => null
|
||||
};
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
|
||||
result[i] = format_text(fmt(i), i, row_height, settings, glyph_context, price_display_format));
|
||||
return result;
|
||||
|
||||
string fmt(int i) {
|
||||
switch (terms) {
|
||||
case LadderSettings.NetChangeTerms.Dollars:
|
||||
return prices[i].ToString("0.00");
|
||||
case LadderSettings.NetChangeTerms.BasisPoints: {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return spec.format(double.NaN);
|
||||
}
|
||||
var basis_pts = prices[i] * 10000.0 / reference_price;
|
||||
return basis_pts.ToString(basis_pts_fmt);
|
||||
}
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints: {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return spec.format(double.NaN);
|
||||
}
|
||||
var net_basis_pts = prices[i] * 10000.0 / reference_price - 10000.0;
|
||||
return net_basis_pts.ToString(basis_pts_fmt);
|
||||
}
|
||||
default:
|
||||
return spec.format(prices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string get_basis_pts_fmt() {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return "0.000";
|
||||
}
|
||||
var tick_sz = spec.ticksize(reference_price);
|
||||
var basis_pt = reference_price.abs() / 10000.0;
|
||||
var precision_log10 = Math.Log10(tick_sz / basis_pt);
|
||||
int n = 0;
|
||||
if (precision_log10 < 0) {
|
||||
n = (int)Math.Ceiling(-precision_log10);
|
||||
}
|
||||
return n < 1
|
||||
? "0"
|
||||
: "0." + new string('0', n);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear_prices(DrawingVisual price, int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
|
||||
@@ -135,51 +174,20 @@ namespace mbts.spark {
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> price_list,
|
||||
IList<double> dprices,
|
||||
double wrt_price,
|
||||
bool format_in_tick_terms,
|
||||
LadderSettings.NetChangeTerms terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick) {
|
||||
if (price_list.IsNullOrEmpty()) {
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) {
|
||||
if (dprices.IsNullOrEmpty()) {
|
||||
return;
|
||||
}
|
||||
render_prices(
|
||||
price, glyph_context, price_list, row_height, settings, tick,
|
||||
wrt_price, format_in_tick_terms);
|
||||
}
|
||||
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick,
|
||||
double wrt_price,
|
||||
bool format_in_tick_terms) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, tick);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
for (int x = 0; x < dprices.Count; ++x)
|
||||
dc.DrawGlyphRun(foreground, fprices[x]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, tick, price_display_format);
|
||||
var fprices = format_prices(
|
||||
dprices, wrt_price, terms, row_height, settings, glyph_context, tick, price_display_format);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
@@ -205,69 +213,78 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderSizes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyphContext,
|
||||
int originIndex,
|
||||
IList<double> prices,
|
||||
IEnumerable<LadderPriceLevel> levels,
|
||||
double rowHeight,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<LadderPriceLevel, string> fmt) {
|
||||
var bg = settings.background.rgb_color().blend((BTSColor)0x2AFFFFFF).to_brush();
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, rowHeight, prices.Count))
|
||||
using (var levelsEn = (levels ?? Enumerable.Empty<LadderPriceLevel>()).GetEnumerator()) {
|
||||
var fg = settings.foreground.rgb_brush();
|
||||
var currIdx = originIndex;
|
||||
var currPrice = prices[currIdx];
|
||||
while (levelsEn.MoveNext()) {
|
||||
var level = levelsEn.Current;
|
||||
var ticks = spec.count_ticks(level.Price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : level.Price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tickDiff = spec.tickdiff(currPrice, price).assert_value();
|
||||
if (!MathUtils.try_increment(ref currIdx, tickDiff) || currIdx < 0 || currIdx >= prices.Count) {
|
||||
continue;
|
||||
}
|
||||
currPrice = price;
|
||||
if (bg is not null) {
|
||||
var y = currIdx * rowHeight + GRID_LINE_HEIGHT;
|
||||
var height = rowHeight - GRID_LINE_HEIGHT;
|
||||
dc.DrawRectangle(bg, null, new Rect(0, y, settings.width, height));
|
||||
}
|
||||
var gr = format_text(fmt(level), currIdx, rowHeight, settings, glyphContext);
|
||||
dc.DrawGlyphRun(fg, gr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<LadderPriceLevel> GetLevels(IEnumerable<(double Price, double Volume, double NotionalValue)> depth, RegionalTickFactors spec) =>
|
||||
depth.TakeWhile(nps => nps.Volume > 0 && nps.Price.is_real())
|
||||
.AggregateConsecutiveGroups(
|
||||
LadderPriceLevel.Empty.ToBuilder,
|
||||
(bd, rung) => {
|
||||
bd.Add(rung.Price, rung.Volume, rung.NotionalValue);
|
||||
return bd;
|
||||
},
|
||||
rung => {
|
||||
var ticks = spec.count_ticks(rung.Price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.Price;
|
||||
})
|
||||
.Select(bd => bd.ToImmutable());
|
||||
|
||||
public static void render_sizes(
|
||||
DrawingVisual dv,
|
||||
GlyphContext glyph_context,
|
||||
int origin_index,
|
||||
IList<double> prices,
|
||||
NativePriceSize[] rungs,
|
||||
IEnumerable<(double Price, double Volume, double NotionalValue)> rungs,
|
||||
double row_height,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<NativePriceSize, string> fmt,
|
||||
bool lighten_market_cells,
|
||||
Color? market_cell_background) {
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
Brush market_cell_background_brush;
|
||||
if (market_cell_background.HasValue) {
|
||||
market_cell_background_brush = new SolidColorBrush(market_cell_background.Value);
|
||||
} else if (lighten_market_cells) {
|
||||
market_cell_background_brush = settings.background.rgb_color()
|
||||
.blend((BTSColor)0x2AFFFFFF)
|
||||
.to_brush();
|
||||
} else {
|
||||
market_cell_background_brush = null;
|
||||
}
|
||||
// market_cell_background_brush = settings.background.rgb_color().blend((BTSColor)0xFF000000).to_brush();
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
|
||||
if (!prices.within_range(origin_index) || rungs.is_null_or_empty())
|
||||
return;
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
NativePriceSize curr_size = NativePriceSize.NO_MARKET;
|
||||
var row_tick_groups = rungs
|
||||
.TakeWhile(nps => !nps.is_nan)
|
||||
.AggregateConsecutiveGroups(() => NativePriceSize.NO_MARKET,
|
||||
(aggr, rung) => rung.with(
|
||||
direct_size: aggr.direct_size + rung.direct_size,
|
||||
implied_size: aggr.implied_size + rung.implied_size),
|
||||
rung => {
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
return ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
});
|
||||
foreach (var rung in row_tick_groups) {
|
||||
//double price = spec.round_to_tick(rung.price);
|
||||
var ticks = spec.count_ticks(rung.price);
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
|
||||
checked {
|
||||
curr_index += (int)tick_diff;
|
||||
}
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
if (market_cell_background_brush != null)
|
||||
dc.DrawRectangle(market_cell_background_brush, null,
|
||||
new Rect(0, curr_index * row_height + GRID_LINE_HEIGHT,
|
||||
settings.width, row_height - GRID_LINE_HEIGHT));
|
||||
var gr = format_text(fmt(rung), curr_index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(foreground, gr);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
Func<LadderPriceLevel, string> fmt) {
|
||||
RenderSizes(
|
||||
dv,
|
||||
glyph_context,
|
||||
origin_index,
|
||||
prices,
|
||||
GetLevels(rungs, spec),
|
||||
row_height,
|
||||
spec,
|
||||
settings,
|
||||
fmt);
|
||||
}
|
||||
|
||||
public static void render_volumes(
|
||||
@@ -280,7 +297,7 @@ namespace mbts.spark {
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction,
|
||||
Func<double, string> fmt_value) {
|
||||
Func<double, double, string> fmt_value) {
|
||||
//
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -303,12 +320,12 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
void render_cell(DrawingContext dc, int r, double price, VolumeAtPriceLevelImmutable vap) {
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
double y = row_height * r;
|
||||
var last_trade_here = r == last_index;
|
||||
@@ -335,11 +352,11 @@ namespace mbts.spark {
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(fmt_value(v));
|
||||
numbers_text.Add(fmt_value(v, vap.Price));
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(fmt_value(vap.Volume));
|
||||
numbers_text.Add(fmt_value(vap.Volume, vap.Price));
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
@@ -366,9 +383,17 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, T data_item);
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, double price, T data_item);
|
||||
|
||||
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> cells, LadderColumnSettings settings, IList<double> prices, RegionalTickFactors spec, RenderCell<T> render_cell) {
|
||||
static void _render_cells<T>(
|
||||
DrawingVisual dv,
|
||||
double row_height,
|
||||
IEnumerable<(double price, T value)> cells,
|
||||
LadderColumnSettings settings,
|
||||
IList<double> prices,
|
||||
RegionalTickFactors spec,
|
||||
RenderCell<T> render_cell
|
||||
) {
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
if (prices.is_null_or_empty() || cells.is_null_or_empty()) {
|
||||
@@ -388,7 +413,7 @@ namespace mbts.spark {
|
||||
var check_price = spec.tickdown(max_price, idx);
|
||||
Log.assert(spec.tick_equals(price, check_price));
|
||||
Log.assert(idx >= 0 && idx < prices.Count);
|
||||
render_cell(dc, idx, value);
|
||||
render_cell(dc, idx, price, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,10 +425,10 @@ namespace mbts.spark {
|
||||
int index,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
Func<double, string> fmt_value) {
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
Func<NativePriceSize, string> fmt_value) {
|
||||
fmt_value ??= nps => nps.size.ToString();
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
|
||||
var gr = format_text(fmt_value(last), index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
|
||||
}
|
||||
}
|
||||
@@ -417,13 +442,13 @@ namespace mbts.spark {
|
||||
double row_height,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<double, string> fmt_value
|
||||
Func<double, double, string> fmt_value
|
||||
) {
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
var fg = settings.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, piq) =>
|
||||
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, price, piq) =>
|
||||
dc.DrawGlyphRun(
|
||||
fg, format_text(fmt_value(piq), row, row_height, settings, glyph_context)));
|
||||
fg, format_text(fmt_value(piq, price), row, row_height, settings, glyph_context)));
|
||||
}
|
||||
|
||||
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -39,6 +39,9 @@
|
||||
|
||||
<cv:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<cv:ResponsiveTextConverter x:Key="BidPriceTextConverter" LongText="Bid:" ShortText="B:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="AskPriceTextConverter" LongText="Ask:" ShortText="A:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="MidPriceTextConverter" LongText="Mid:" ShortText="M:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
|
||||
@@ -179,7 +182,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CancelAll" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center">Cancel All</TextBlock>
|
||||
@@ -217,10 +220,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelAll" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="0"/>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -228,7 +228,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="TT_CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource CancelAll}">
|
||||
<Setter Property="Grid.Row" Value="2" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -236,83 +236,6 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelBuys" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Grid.Row" Value="0" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding bid_foreground}">
|
||||
Cancel Buys
|
||||
</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{Binding bid_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
|
||||
<Setter
|
||||
TargetName="Border"
|
||||
Property="BorderBrush"
|
||||
Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="CancelSells" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
|
||||
<Setter Property="Grid.Row" Value="1" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Foreground="{Binding ask_foreground}">
|
||||
Cancel Sells
|
||||
</TextBlock>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{Binding ask_background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="True" >
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
|
||||
<Setter
|
||||
TargetName="Border"
|
||||
Property="BorderBrush"
|
||||
Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SettingsButton" TargetType="Button" BasedOn="{StaticResource LadderMenuButton}">
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
@@ -363,13 +286,103 @@
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="0,0,0,2"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
<Setter Property="Padding" Value="4,1"/>
|
||||
</Style>
|
||||
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelPricePointButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Grid.Column" Value="2" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0,0,0,2" />
|
||||
<Setter Property="Padding" Value="4,1" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0"
|
||||
Height="Auto"
|
||||
Padding="0"
|
||||
SnapsToDevicePixels="True">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="CommonStates">
|
||||
<VisualState Name="Normal" />
|
||||
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="HoverRect" Storyboard.TargetProperty="Fill.Opacity" BeginTime="0" Duration="00:00:00.15" To="0.20">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.mouseover_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_background}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_background}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup Name="FocusStates">
|
||||
<VisualState Name="Unfocused" />
|
||||
|
||||
<VisualState Name="Focused">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.focused_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Rectangle Name="HoverRect" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsHitTestVisible="False">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="White" Opacity="0" />
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
RecognizesAccessKey="True" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
|
||||
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
|
||||
</ResourceDictionary>
|
||||
@@ -434,6 +447,9 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -441,9 +457,123 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Day Change (label) -->
|
||||
<!-- Ask Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource AskPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
ask_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Ask Price (value) -->
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Visibility="{Binding
|
||||
ask_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Ask"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding ask_price}"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding ask_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding ask_price_style.font_family}"
|
||||
FontSize="{Binding ask_price_style.font_size}"
|
||||
FontWeight="{Binding ask_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
ask_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Mid Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource MidPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
mid_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Mid Price (value) -->
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding mid_price}"
|
||||
Visibility="{Binding
|
||||
mid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Mid"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding mid_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding mid_price_style.font_family}"
|
||||
FontSize="{Binding mid_price_style.font_size}"
|
||||
FontWeight="{Binding mid_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
mid_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="mid_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Bid Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource BidPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
bid_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Bid Price (value) -->
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding bid_price}"
|
||||
Visibility="{Binding
|
||||
bid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Bid"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding bid_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding bid_price_style.font_family}"
|
||||
FontSize="{Binding bid_price_style.font_size}"
|
||||
FontWeight="{Binding bid_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
bid_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Day Change (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
|
||||
Visibility="{Binding
|
||||
day_change_style.actual_show_label,
|
||||
@@ -452,7 +582,7 @@
|
||||
|
||||
<!-- Day Change (value) -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Row="3"
|
||||
Background="{Binding
|
||||
day_change_background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -479,7 +609,7 @@
|
||||
|
||||
<!-- High (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Row="4"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
|
||||
Visibility="{Binding
|
||||
session_high_style.actual_show_label,
|
||||
@@ -487,15 +617,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- High (value) -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
Background="{Binding
|
||||
session_high_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding session_high}"
|
||||
Visibility="{Binding
|
||||
session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session High"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_high_style.font_family}"
|
||||
FontSize="{Binding session_high_style.font_size}"
|
||||
@@ -504,24 +636,19 @@
|
||||
session_high_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_high}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Low (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Row="5"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
|
||||
Visibility="{Binding
|
||||
session_low_style.actual_show_label,
|
||||
@@ -529,15 +656,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Low (value) -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
<Button
|
||||
Grid.Row="5"
|
||||
Background="{Binding
|
||||
session_low_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding session_low}"
|
||||
Visibility="{Binding
|
||||
session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session Low"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_low_style.font_family}"
|
||||
FontSize="{Binding session_low_style.font_size}"
|
||||
@@ -546,24 +675,19 @@
|
||||
session_low_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_low}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Open (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="6"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
|
||||
Visibility="{Binding
|
||||
open_style.actual_show_label,
|
||||
@@ -571,15 +695,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Open (value) -->
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
<Button
|
||||
Grid.Row="6"
|
||||
Background="{Binding
|
||||
open_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding open}"
|
||||
Visibility="{Binding
|
||||
open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Open"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding open_style.font_family}"
|
||||
FontSize="{Binding open_style.font_size}"
|
||||
@@ -587,23 +713,19 @@
|
||||
Foreground="{Binding
|
||||
open_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding open}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Settlement (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="7"
|
||||
Text="{Binding
|
||||
left_panel_width, Converter={StaticResource SettlementTextConverter}}"
|
||||
Visibility="{Binding
|
||||
@@ -612,15 +734,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Settlement (value) -->
|
||||
<Border
|
||||
Grid.Row="4"
|
||||
<Button
|
||||
Grid.Row="7"
|
||||
Background="{Binding
|
||||
settlement_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding settlement}"
|
||||
Visibility="{Binding
|
||||
settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Settlement"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding settlement_style.font_family}"
|
||||
FontSize="{Binding settlement_style.font_size}"
|
||||
@@ -629,23 +753,18 @@
|
||||
settlement_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding settlement}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Theo (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Row="8"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource TheoTextConverter}}"
|
||||
Visibility="{Binding
|
||||
theo_style.actual_show_label,
|
||||
@@ -653,15 +772,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Theo (value) -->
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
<Button
|
||||
Grid.Row="8"
|
||||
Background="{Binding
|
||||
theo_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding theo}"
|
||||
Visibility="{Binding
|
||||
theo_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Theo"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding theo_style.font_family}"
|
||||
FontSize="{Binding theo_style.font_size}"
|
||||
@@ -669,23 +790,19 @@
|
||||
Foreground="{Binding
|
||||
theo_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding theo}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="theo"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="theo"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Delta -->
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Row="9"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DeltaTextConverter}}"
|
||||
Visibility="{Binding
|
||||
delta_style.actual_show_label,
|
||||
@@ -693,7 +810,7 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<Border
|
||||
Grid.Row="6"
|
||||
Grid.Row="9"
|
||||
Background="{Binding
|
||||
delta_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -714,7 +831,7 @@
|
||||
|
||||
<!-- Vega -->
|
||||
<TextBlock
|
||||
Grid.Row="7"
|
||||
Grid.Row="10"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VegaTextConverter}}"
|
||||
Visibility="{Binding
|
||||
vega_style.actual_show_label,
|
||||
@@ -722,7 +839,7 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Row="10"
|
||||
Background="{Binding
|
||||
vega_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -743,7 +860,7 @@
|
||||
<!-- Volume (label) -->
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.Row="8"
|
||||
Grid.Row="11"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Visibility="{Binding
|
||||
volume_style.actual_show_label,
|
||||
@@ -752,7 +869,7 @@
|
||||
|
||||
<!-- Volume (price) -->
|
||||
<Border
|
||||
Grid.Row="8"
|
||||
Grid.Row="11"
|
||||
Background="{Binding
|
||||
volume_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -793,6 +910,7 @@
|
||||
Margin="2,10,2,2"
|
||||
ItemsSource="{Binding accounts}"
|
||||
SelectedItem="{Binding selected_account}"
|
||||
ToolTip="Account"
|
||||
Visibility="{Binding
|
||||
DataContext.show_account,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -802,6 +920,7 @@
|
||||
<ComboBox
|
||||
DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}"
|
||||
SelectedItem="{Binding selected_time_in_force}" Margin="2"
|
||||
ToolTip="Time In Force"
|
||||
Visibility="{Binding
|
||||
DataContext.show_tif,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -824,6 +943,7 @@
|
||||
IsOpen="{Binding is_expanded}"
|
||||
ItemsSource="{Binding algo_definitions_view}"
|
||||
SelectedItem="{Binding selected_algo_definition}"
|
||||
ToolTip="Order/Algo Type"
|
||||
Visibility="{Binding
|
||||
DataContext.show_order_type,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -836,6 +956,13 @@
|
||||
</Style>
|
||||
</uag:OrderDetailsPopupControl.Resources>
|
||||
</uag:OrderDetailsPopupControl>
|
||||
|
||||
<ComboBox
|
||||
Margin="2"
|
||||
ItemsSource="{Binding spreader_type_choices}"
|
||||
SelectedItem="{Binding spreader_type}"
|
||||
Visibility="{Binding HasItems, Mode=OneWay, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
|
||||
<!-- Position into Qty Button -->
|
||||
<Button
|
||||
@@ -879,6 +1006,7 @@
|
||||
Increment="{Binding size_calculator.unit_quantity}"
|
||||
Minimum="0"
|
||||
TickDisplaySettings="{Binding size_calculator.display_settings}"
|
||||
ToolTip="Size"
|
||||
Value="{Binding size_calculator.display_value, Mode=TwoWay}" />
|
||||
|
||||
<!-- Qty quick buttons -->
|
||||
@@ -901,7 +1029,8 @@
|
||||
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Foreground="{u:BTSColor UI.Button.normal_foreground}">
|
||||
Foreground="{u:BTSColor UI.Button.normal_foreground}"
|
||||
ToolTip="Add to Size">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding
|
||||
Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
|
||||
@@ -921,6 +1050,7 @@
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding size_calculator.clear}"
|
||||
ToolTip="Clear Size"
|
||||
Visibility="{Binding
|
||||
show_clear, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource CommonButton}">
|
||||
@@ -931,6 +1061,7 @@
|
||||
<CheckBox
|
||||
Margin="2"
|
||||
IsChecked="{Binding auto_clear}"
|
||||
ToolTip="Clear Size after order"
|
||||
Visibility="{Binding
|
||||
show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Auto Clear
|
||||
@@ -973,19 +1104,19 @@
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="Bids"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelBuysButtonStyleKey}}"
|
||||
Style="{StaticResource CancelBuys}"
|
||||
Visibility="{Binding
|
||||
show_cancel_buys, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="Asks"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelSellsButtonStyleKey}}"
|
||||
Style="{StaticResource CancelSells}"
|
||||
Visibility="{Binding
|
||||
show_cancel_sells, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<Button
|
||||
Command="{Binding cancel_cmd}"
|
||||
CommandParameter="All"
|
||||
Style="{StaticResource {x:Static sp:LadderView.CancelAllButtonStyleKey}}"
|
||||
Style="{StaticResource CancelAll}"
|
||||
Visibility="{Binding
|
||||
show_cancel_all, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
|
||||
@@ -27,22 +27,6 @@ namespace mbts.spark {
|
||||
MinZoomFactor = 0.5,
|
||||
MaxZoomFactor = 1.5;
|
||||
|
||||
static LadderView() {
|
||||
var knobs = config.Configuration.get<bts.config.LadderKnobs>();
|
||||
MimicTT = knobs.MimicTt;
|
||||
}
|
||||
|
||||
public static bool MimicTT { get; }
|
||||
|
||||
public static string CancelBuysButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelBuys" : "CancelBuys";
|
||||
|
||||
public static string CancelSellsButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelSells" : "CancelSells";
|
||||
|
||||
public static string CancelAllButtonStyleKey =>
|
||||
MimicTT ? "TT_CancelAll" : "CancelAll";
|
||||
|
||||
public static IMultiValueConverter PositionToolTipConverter =
|
||||
ValueConverter.create<AccountTuple?, bool, bool, bool, string>((account, show_day_pos, show_raw_day_pos, show_total_pos) => {
|
||||
const string day_pos_text = "day", raw_day_pos_text = "raw day", total_pos_text = "total";
|
||||
@@ -76,14 +60,8 @@ namespace mbts.spark {
|
||||
if (show_total_pos) add_part(total_pos_text, " (", ")");
|
||||
//
|
||||
if (part_count > 1) {
|
||||
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
|
||||
// Explicitly state which value will be copied to the size input.
|
||||
sb.Insert(0, "'");
|
||||
sb.Add("'");
|
||||
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
|
||||
} else {
|
||||
// Only showing 1 value, no need for single quotes,
|
||||
// nor an explicit statement that indicates which value will be copied to the size input.
|
||||
sb.Add(" position\n(click to copy to size)");
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
@@ -5,6 +5,7 @@ using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Data;
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.Core.Reflection;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
@@ -223,6 +223,10 @@ namespace mbts.spark {
|
||||
Add(qty_column = new SizeColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.order.order.quantity(), nameof(OrderBookRowVM.quantity))));
|
||||
// notional value
|
||||
Add(notional_value_column = new NotionalValueColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.notional_value, nameof(OrderBookRowVM.notional_value))));
|
||||
// edit quantity column
|
||||
// not usually part of the main order book, but it's useful to add it
|
||||
// rather than debugging the popup window
|
||||
@@ -232,6 +236,10 @@ namespace mbts.spark {
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.order.order.filled_quantity(),
|
||||
nameof(OrderBookRowVM.filled_quantity))));
|
||||
// filled notional value
|
||||
Add(filled_notional_value_column = new FilledNotionalValueColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.filled_notional_value, nameof(OrderBookRowVM.filled_notional_value))));
|
||||
// price
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
@@ -363,9 +371,11 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<OrderSubType[]> sub_type_column { get; }
|
||||
public SideColumn side_column { get; }
|
||||
public SizeColumn qty_column { get; }
|
||||
public NotionalValueColumn notional_value_column { get; }
|
||||
public EditableQtyColumn edit_qty_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> price_column { get; }
|
||||
public FilledColumn filled_qty_column { get; }
|
||||
public FilledNotionalValueColumn filled_notional_value_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> last_price_column { get; }
|
||||
public SimpleTextColumn<DoubleMinMaxState> avg_price_column { get; }
|
||||
public SimpleTextColumn<OrderStatus[]> status_column { get; }
|
||||
@@ -391,8 +401,10 @@ namespace mbts.spark {
|
||||
order_sub_types = sub_type_column.filter.get_state(),
|
||||
sides = side_column.filter.get_state(),
|
||||
qty = qty_column.filter.get_state(),
|
||||
notional_value = notional_value_column.filter.get_state(),
|
||||
price = price_column.filter.get_state(),
|
||||
qty_filled = filled_qty_column.filter.get_state(),
|
||||
filled_notional_value = filled_notional_value_column.filter.get_state(),
|
||||
avg_price = avg_price_column.filter.get_state(),
|
||||
last_price = last_price_column.filter.get_state(),
|
||||
statuses = status_column.filter.get_state(),
|
||||
@@ -415,7 +427,9 @@ namespace mbts.spark {
|
||||
account_column.filter.load_state(fs.accounts);
|
||||
price_column.filter.load_state(fs.price);
|
||||
qty_column.filter.load_state(fs.qty);
|
||||
notional_value_column.filter.load_state(fs.notional_value);
|
||||
filled_qty_column.filter.load_state(fs.qty_filled);
|
||||
filled_notional_value_column.filter.load_state(fs.filled_notional_value);
|
||||
side_column.filter.load_state(fs.sides);
|
||||
order_type_column.filter.load_state(fs.order_types);
|
||||
root_algo_type_column.filter.load_state(fs.root_algo_types);
|
||||
@@ -645,6 +659,32 @@ namespace mbts.spark {
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class NotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public NotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(
|
||||
Columns.NotionalValue,
|
||||
DefaultColumnHeaders.NotionalValue,
|
||||
r => r.notional_value_text,
|
||||
DefaultColumnWidths.NotionalValue,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.notional_value)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
dc,
|
||||
_get_glyph_context(row),
|
||||
font_size,
|
||||
row.notional_value_text,
|
||||
position,
|
||||
row_height,
|
||||
RenderUtils.get_side_brush(row),
|
||||
background_brush,
|
||||
horizontal_alignment,
|
||||
VerticalAlignment.Center,
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.display_filled_quantity.ToString(), DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.DoubleMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
@@ -664,6 +704,32 @@ namespace mbts.spark {
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class FilledNotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledNotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(
|
||||
Columns.FilledNotionalValue,
|
||||
DefaultColumnHeaders.FilledNotionalValue,
|
||||
r => r.filled_notional_value_text,
|
||||
DefaultColumnWidths.FilledNotionalValue,
|
||||
filter,
|
||||
DataGridFilterKnownTemplates.DoubleMinMax,
|
||||
_wrap_comparer(r => r.filled_notional_value)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(
|
||||
dc,
|
||||
_get_glyph_context(row),
|
||||
font_size,
|
||||
row.filled_notional_value_text,
|
||||
position,
|
||||
row_height,
|
||||
RenderUtils.get_side_brush(row),
|
||||
background_brush,
|
||||
horizontal_alignment,
|
||||
VerticalAlignment.Center,
|
||||
DefaultCellPadding);
|
||||
}
|
||||
|
||||
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
|
||||
readonly BindingBase _foreground_binding;
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using bts;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.collections;
|
||||
@@ -8,6 +8,7 @@ using bts.utils;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.ViewModel.Commands;
|
||||
@@ -108,6 +109,8 @@ namespace mbts.spark {
|
||||
public double display_quantity => quantity.safe_divide(_display_qty_divisor);
|
||||
public string display_quantity_text =>
|
||||
quantity_display_settings.to_tick_display_settings().format(display_quantity);
|
||||
public double notional_value => order_tuple.notional_value;
|
||||
public string notional_value_text => _fmt_price(notional_value);
|
||||
public TickDisplaySettingsImmutable quantity_display_settings {
|
||||
get => _quantity_display_settings;
|
||||
set {
|
||||
@@ -128,6 +131,8 @@ namespace mbts.spark {
|
||||
filled_quantity.safe_divide(_display_qty_divisor);
|
||||
public string display_filled_quantity_text =>
|
||||
quantity_display_settings.to_tick_display_settings().format(display_filled_quantity);
|
||||
public double filled_notional_value => order_tuple.filled_notional_value;
|
||||
public string filled_notional_value_text => _fmt_price(filled_notional_value);
|
||||
public string last_msg => order.last_msg();
|
||||
// piq
|
||||
public uint piq => order_tuple.order.piq ?? uint.MaxValue;
|
||||
@@ -355,9 +360,15 @@ namespace mbts.spark {
|
||||
if (_prospective_qty_translator_backing != null)
|
||||
_prospective_qty_translator_backing.actual_value = quantity;
|
||||
}
|
||||
if ((Columns.NotionalValue & dirty) == Columns.NotionalValue) {
|
||||
RaisePropertyChanged(nameof(notional_value), nameof(notional_value_text));
|
||||
}
|
||||
if ((Columns.Filled & dirty) != 0) {
|
||||
RaisePropertyChanged(nameof(filled_quantity), nameof(display_filled_quantity));
|
||||
}
|
||||
if ((Columns.FilledNotionalValue & dirty) == Columns.FilledNotionalValue) {
|
||||
RaisePropertyChanged(nameof(filled_notional_value), nameof(filled_notional_value_text));
|
||||
}
|
||||
// badge fill
|
||||
if (old_order_tuple.order.meta?.badge_color != order_tuple.order.meta?.badge_color) {
|
||||
// invalidate by setting to null (will be re-instantiated in the public getter)
|
||||
@@ -521,6 +532,12 @@ namespace mbts.spark {
|
||||
if (oldval.order.piq != newval.order.piq) {
|
||||
dirty |= Columns.Piq;
|
||||
}
|
||||
if (oldval.notional_value != newval.notional_value) {
|
||||
dirty |= Columns.NotionalValue;
|
||||
}
|
||||
if (oldval.filled_notional_value != newval.filled_notional_value) {
|
||||
dirty |= Columns.FilledNotionalValue;
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void refresh_quantity_display() {
|
||||
foreach (var row in _row_collection.all_rows) {
|
||||
foreach (var row in _row_collection.GetAllRows()) {
|
||||
if (_cached_securities.TryGetValue(row.security_key, out var smd)) {
|
||||
row.quantity_display_settings = smd.quantity_display;
|
||||
}
|
||||
|
||||
@@ -1,160 +1,174 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public string order_key_regex;
|
||||
[ProtoMember(3)]
|
||||
public TimeFilterTuple time_created;
|
||||
[ProtoMember(4)]
|
||||
public string[] users;
|
||||
[ProtoMember(5)]
|
||||
public string[] accounts;
|
||||
[ProtoMember(6)]
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState qty;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState qty_filled;
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState avg_price;
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(15)]
|
||||
public OrderStatus[] statuses;
|
||||
[ProtoMember(16)]
|
||||
public string exchange_message_regex;
|
||||
[ProtoMember(17)]
|
||||
public TimeFilterTuple last_change;
|
||||
//[ProtoMember(18)]
|
||||
//public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root.algo?.key ?? default));
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utility;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
[ProtoContract]
|
||||
class FilterStates : IEquatable<FilterStates> {
|
||||
[ProtoMember(1)]
|
||||
public SecurityCriteria security_criteria;
|
||||
[ProtoMember(2)]
|
||||
public string order_key_regex;
|
||||
[ProtoMember(3)]
|
||||
public TimeFilterTuple time_created;
|
||||
[ProtoMember(4)]
|
||||
public string[] users;
|
||||
[ProtoMember(5)]
|
||||
public string[] accounts;
|
||||
[ProtoMember(6)]
|
||||
public Venue[] exchanges;
|
||||
[ProtoMember(7)]
|
||||
public TimeInForce[] tifs;
|
||||
//[ProtoMember(8)]
|
||||
//public CustomOrderDefinition.CustomOneofCase[] order_types;
|
||||
[ProtoMember(23)]
|
||||
public AlgoID[] order_types;
|
||||
[ProtoMember(24)]
|
||||
public AlgoID[] root_algo_types;
|
||||
[ProtoMember(9)]
|
||||
public Side[] sides;
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState qty;
|
||||
[ProtoMember(26)]
|
||||
public DoubleMinMaxState notional_value;
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState price;
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState qty_filled;
|
||||
[ProtoMember(27)]
|
||||
public DoubleMinMaxState filled_notional_value;
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState avg_price;
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState last_price;
|
||||
[ProtoMember(15)]
|
||||
public OrderStatus[] statuses;
|
||||
[ProtoMember(16)]
|
||||
public string exchange_message_regex;
|
||||
[ProtoMember(17)]
|
||||
public TimeFilterTuple last_change;
|
||||
//[ProtoMember(18)]
|
||||
//public string security_regex;
|
||||
[ProtoMember(19)]
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
[ProtoMember(25)]
|
||||
public UIntMinMaxState piq;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
EqualityComparer.safe_sequence_equals(exchanges, other.exchanges) &&
|
||||
EqualityComparer.safe_sequence_equals(tifs, other.tifs) &&
|
||||
EqualityComparer.safe_sequence_equals(order_types, other.order_types) &&
|
||||
EqualityComparer.safe_sequence_equals(root_algo_types, other.root_algo_types) &&
|
||||
EqualityComparer.safe_sequence_equals(sides, other.sides) &&
|
||||
qty.safe_equals(other.qty) &&
|
||||
notional_value.safe_equals(other.notional_value) &&
|
||||
price.safe_equals(other.price) &&
|
||||
qty_filled.safe_equals(other.qty_filled) &&
|
||||
filled_notional_value.safe_equals(other.filled_notional_value) &&
|
||||
avg_price.safe_equals(other.avg_price) &&
|
||||
last_price.safe_equals(other.last_price) &&
|
||||
EqualityComparer.safe_sequence_equals(statuses, other.statuses) &&
|
||||
exchange_message_regex == other.exchange_message_regex &&
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols) &&
|
||||
piq.safe_equals(other.piq);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(root_algo_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
notional_value?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
filled_notional_value?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols),
|
||||
piq?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
static class FilterStatesExtensions {
|
||||
public static Func<OrderRecordTupleWithRoot, bool> build_predicate(this FilterStates fs) {
|
||||
var pb = new ColumnPredicateBuilder<OrderRecordTupleWithRoot>();
|
||||
// security
|
||||
pb.add_security_criteria(fs.security_criteria, o => o.order.security);
|
||||
// regex
|
||||
pb.add_regex(fs.order_key_regex, o => o.order.order.Key.ToString());
|
||||
pb.add_regex(fs.exchange_message_regex, o => o.order.order.State.LastMsg);
|
||||
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.order.attached_target_order_key().ToString());
|
||||
pb.add_regex(fs.parent_key_regex, o => o.order.order.ParentKey.ToString());
|
||||
// time
|
||||
pb.add_time_filter(fs.time_created, o => o.order.order.created());
|
||||
pb.add_time_filter(fs.last_change, o => o.order.order.last_change());
|
||||
// discrete values
|
||||
pb.add_discrete_values(fs.users, o => o.order.order.user());
|
||||
pb.add_discrete_values(fs.accounts, o => o.order.order.account());
|
||||
pb.add_discrete_values(fs.exchanges, o => o.order.security?.Exchange ?? Venue.Unset);
|
||||
pb.add_discrete_values(fs.tifs, o => o.order.order.time_in_force());
|
||||
pb.add_discrete_values(fs.order_types, o => AlgoID.from_custom_def(o.order.order.Def.CustomDef));
|
||||
pb.add_discrete_values(
|
||||
Array.ConvertAll(fs.root_algo_types, a => (AlgoID?)a),
|
||||
o => o.root.algo.HasValue
|
||||
? AlgoID.from_algo_key(o.root.algo.Value.key)
|
||||
: null);
|
||||
pb.add_discrete_values(fs.order_sub_types, o => o.order.order.sub_type());
|
||||
pb.add_discrete_values(fs.sides, o => o.order.order.side());
|
||||
pb.add_discrete_values(fs.statuses, o => o.order.order.status());
|
||||
pb.add_discrete_values(fs.symbols, o => o.order.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.order.quantity());
|
||||
pb.add_numeric(fs.notional_value, o => o.notional_value);
|
||||
pb.add_price(fs.price, o => o.order.order.ui_price(), o => o.order.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.order.filled_quantity());
|
||||
pb.add_numeric(fs.filled_notional_value, o => o.filled_notional_value);
|
||||
pb.add_numeric(fs.avg_price, o => o.order.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.order.last_price());
|
||||
pb.add_numeric(fs.piq, o => o.order.piq ?? uint.MaxValue);
|
||||
return pb.to_predicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace bts.shell.orderbook {
|
||||
static OrderBookGridSettingsManager() {
|
||||
// make font-weight immutable for all columns (the font-weight rule is hard-coded: Regular for root-orders, Light for all others)
|
||||
// TODO: Remove the immutable font-weight flags when cell animation is ready
|
||||
var col_settings_flags = EnumHelper.get_values<Columns>().ToDictionary(
|
||||
var col_settings_flags = Enum.GetValuesAsUnderlyingType<Columns>().Cast<Columns>().ToDictionary(
|
||||
c => c.ToString(), c => GridColumnSettingsFeatureFlags.ImmutableFontWeight);
|
||||
// make font-color immutable for Side, Size, and Filled columns (the font-color rule is hard-coded: MarketData.bid for Buy orders, MarketData.ask for Sells)
|
||||
// TODO: Remove the immutable font-color flags when cell animation is ready
|
||||
|
||||
@@ -1,270 +1,277 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
|
||||
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
|
||||
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
// menu display
|
||||
[ProtoMember(27)]
|
||||
public SparkMenuDisplaySettings menu_display { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criteria = default_fills.sorting_criteria.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
// standard
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings side => new(
|
||||
Columns.Side.ToString(), ColHeader.Side,
|
||||
width: ColWidth.Side,
|
||||
horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings filled_quantity => new(
|
||||
Columns.Filled.ToString(),
|
||||
ColHeader.FilledQuantity,
|
||||
width: ColWidth.Filled,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings avg_price => new(
|
||||
Columns.AvgFilled.ToString(),
|
||||
ColHeader.AvgFilled,
|
||||
width: ColWidth.AveragePrice,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// editable
|
||||
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
|
||||
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
|
||||
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
|
||||
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
|
||||
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
|
||||
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criteria {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return piq;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> editable {
|
||||
get {
|
||||
yield return cancel;
|
||||
yield return update;
|
||||
yield return edit_quantity;
|
||||
yield return edit_price;
|
||||
yield return commit;
|
||||
yield return attach_detach;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in editable)
|
||||
yield return col_setting;
|
||||
foreach (var col_setting in standard) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api.orderman;
|
||||
using bts.shell.fills;
|
||||
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.settings;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using GridSettings = mbts.ui.settings.GridSettings;
|
||||
|
||||
namespace bts.shell.orderbook {
|
||||
using ColHeader = mbts.ui.controls.DIYGrid.DefaultColumnHeaders;
|
||||
using ColWidth = mbts.ui.controls.DIYGrid.DefaultColumnWidths;
|
||||
|
||||
[ProtoContract]
|
||||
class OrderBookStartupData {
|
||||
const bool
|
||||
_default_show_right_panel = true,
|
||||
_default_show_order_params = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_order_params_height = 250,
|
||||
_default_fills_height = 300,
|
||||
_default_ladder_height = 350;
|
||||
[ProtoMember(16)]
|
||||
OrderBookRightPanelSettings _right_panel;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
public OrderBookRightPanelSettings right_panel => _right_panel ?? (_right_panel = new OrderBookRightPanelSettings());
|
||||
/// TODO: remove properties below, and redirect callers to <see cref="right_panel"/>
|
||||
// right panel (host)
|
||||
public bool show_right_panel { get => right_panel.show; set => right_panel.show = value; }
|
||||
public double right_panel_width { get => right_panel.width; set => right_panel.width = value; }
|
||||
// order params
|
||||
public bool show_order_params { get => right_panel.stack.show_order_params; set => right_panel.stack.show_order_params = value; }
|
||||
public double order_params_height { get; set; }
|
||||
// ladder
|
||||
public bool show_ladder { get; set; }
|
||||
public double ladder_height { get => right_panel.stack.ladder_height; set => right_panel.stack.ladder_height = value; }
|
||||
// fills
|
||||
public bool show_fills { get => right_panel.stack.show_fills; set => right_panel.stack.show_fills = value; }
|
||||
public double fills_height { get => right_panel.stack.fills_height; set => right_panel.stack.fills_height = value; }
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid { get; set; }
|
||||
// orderbook grid
|
||||
[ProtoMember(17)]
|
||||
public OrderBookGridSettings orderbook_grid { get; set; }
|
||||
// fills settings
|
||||
[ProtoMember(26)]
|
||||
public OrderBookFillsGridSettings fills_settings { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
// menu display
|
||||
[ProtoMember(27)]
|
||||
public SparkMenuDisplaySettings menu_display { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Please use <see cref="Default"/> to get a new instance (the only reason the default constructor still exists is for ProtoBuf deserialization).
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public OrderBookStartupData() {
|
||||
show_right_panel = _default_show_right_panel;
|
||||
show_order_params = _default_show_order_params;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
order_params_height = _default_order_params_height;
|
||||
fills_height = _default_fills_height;
|
||||
ladder_height = _default_ladder_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, OrderBookGridSettings orderbook_grid = default, bool show_right_panel = false, double right_panel_width = 0, double fills_height = 0, bool show_fills = false, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.show_right_panel = show_right_panel;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_height = fills_height;
|
||||
this.show_fills = show_fills;
|
||||
this.orderbook_grid = orderbook_grid;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
_predicate_animations.AddRange(predicate_animations);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
[Obsolete]
|
||||
public IReadOnlyList<OrderRecordTuplePredicateAnimations> predicate_animations => _predicate_animations ?? (_predicate_animations = new List<OrderRecordTuplePredicateAnimations>());
|
||||
public OrderBookGridSettings grid_settings_wrapper() {
|
||||
if (use_defaults)
|
||||
return null;
|
||||
if (orderbook_grid == null) {
|
||||
if (grid == null)
|
||||
grid = _default_general_grid;
|
||||
orderbook_grid = new OrderBookGridSettings(grid.Clone(), _predicate_animations);
|
||||
}
|
||||
return orderbook_grid;
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
DefaultGrid,
|
||||
_default_show_right_panel,
|
||||
_default_right_panel_width,
|
||||
_default_fills_height,
|
||||
use_defaults: true) {
|
||||
show_order_params = _default_show_order_params,
|
||||
order_params_height = _default_order_params_height,
|
||||
fills_settings = DefaultFillsGrid
|
||||
};
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
public static OrderBookFillsGridSettings DefaultFillsGrid {
|
||||
get {
|
||||
var default_fills = FillsSettings.Default();
|
||||
var rv = new OrderBookFillsGridSettings {
|
||||
sorting_criteria = default_fills.sorting_criteria.ToList(),
|
||||
filters = default_fills.filters,
|
||||
general = FillsSettings.MiniGridSettings
|
||||
};
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
static GridSettings _default_general_grid => GridSettings.Default.with(DefaultColumns.standard, DefaultColumns.sorting_criteria);
|
||||
public static OrderBookGridSettings default_mini_grid => new OrderBookGridSettings(GridSettings.Default.with(DefaultColumns.mini, DefaultColumns.sorting_criteria)) {
|
||||
algo_tree_display_mode = AlgoTreeDisplayMode.RootAndLeafNodes
|
||||
};
|
||||
|
||||
static class DefaultColumns {
|
||||
// standard
|
||||
public static GridColumnSettings security => new GridColumnSettings(Columns.Security.ToString(), DIYGrid.DefaultColumnHeaders.Security, width: DIYGrid.DefaultColumnWidths.Security);
|
||||
public static GridColumnSettings symbol => new GridColumnSettings(Columns.Symbol.ToString(), DIYGrid.DefaultColumnHeaders.Symbol, width: DIYGrid.DefaultColumnWidths.Symbol, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_created => new GridColumnSettings(Columns.Time.ToString(), "Time", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_change => new GridColumnSettings(Columns.LastChange.ToString(), "Last Change", width: DIYGrid.DefaultColumnWidths.Time, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings user => new GridColumnSettings(Columns.User.ToString(), DIYGrid.DefaultColumnHeaders.User, width: DIYGrid.DefaultColumnWidths.User, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings account => new GridColumnSettings(Columns.Account.ToString(), DIYGrid.DefaultColumnHeaders.Account, width: DIYGrid.DefaultColumnWidths.Account, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings exchange => new GridColumnSettings(Columns.Exchange.ToString(), DIYGrid.DefaultColumnHeaders.Exchange, width: DIYGrid.DefaultColumnWidths.Exchange, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings time_in_force => new GridColumnSettings(Columns.TimeInForce.ToString(), DIYGrid.DefaultColumnHeaders.TimeInForce, width: DIYGrid.DefaultColumnWidths.TimeInForce);
|
||||
public static GridColumnSettings order_type => new GridColumnSettings(Columns.OrderType.ToString(), DIYGrid.DefaultColumnHeaders.OrderType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings root_algo_type => new GridColumnSettings(Columns.RootAlgoType.ToString(), DIYGrid.DefaultColumnHeaders.RootAlgoType, width: DIYGrid.DefaultColumnWidths.OrderType);
|
||||
public static GridColumnSettings sub_type => new GridColumnSettings(Columns.SubType.ToString(), "Sub Type", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings side => new(
|
||||
Columns.Side.ToString(), ColHeader.Side,
|
||||
width: ColWidth.Side,
|
||||
horizontal_alignment: HorizontalAlignment.Left);
|
||||
|
||||
public static GridColumnSettings quantity => new GridColumnSettings(Columns.Size.ToString(), "Qty", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings notional_value => new GridColumnSettings(Columns.NotionalValue.ToString(), DIYGrid.DefaultColumnHeaders.NotionalValue, width: DIYGrid.DefaultColumnWidths.NotionalValue, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings price => new GridColumnSettings(Columns.Price.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings filled_quantity => new(
|
||||
Columns.Filled.ToString(),
|
||||
ColHeader.FilledQuantity,
|
||||
width: ColWidth.Filled,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings filled_notional_value => new(
|
||||
Columns.FilledNotionalValue.ToString(),
|
||||
ColHeader.FilledNotionalValue,
|
||||
width: ColWidth.FilledNotionalValue,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings avg_price => new(
|
||||
Columns.AvgFilled.ToString(),
|
||||
ColHeader.AvgFilled,
|
||||
width: ColWidth.AveragePrice,
|
||||
horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings last_price => new GridColumnSettings(Columns.LastPrice.ToString(), "Last Filled", width: DIYGrid.DefaultColumnWidths.LastPrice, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings status => new GridColumnSettings(Columns.Status.ToString(), "Status", width: 80, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings last_message => new GridColumnSettings(Columns.LastMessage.ToString(), "Last Message", width: DIYGrid.DefaultColumnWidths.LastMessage, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey, horizontal_alignment: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings piq => new GridColumnSettings(Columns.Piq.ToString(), "PIQ", width: DIYGrid.DefaultColumnWidths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// editable
|
||||
public static GridColumnSettings cancel => new GridColumnSettings(Columns.Cancel.ToString(), DIYGrid.DefaultColumnHeaders.Cancel, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Cancel);
|
||||
public static GridColumnSettings attach_detach => new GridColumnSettings(Columns.Attach.ToString(), DIYGrid.DefaultColumnHeaders.AttachDetach, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.AttachDetach);
|
||||
public static GridColumnSettings update => new GridColumnSettings(Columns.Update.ToString(), DIYGrid.DefaultColumnHeaders.Update, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Update);
|
||||
public static GridColumnSettings edit_quantity => new GridColumnSettings(Columns.EditableQty.ToString(), DIYGrid.DefaultColumnHeaders.Qty, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditableSize);
|
||||
public static GridColumnSettings edit_price => new GridColumnSettings(Columns.EditablePrice.ToString(), DIYGrid.DefaultColumnHeaders.Price, width: DIYGrid.DefaultColumnWidths.MiniOrderBook.EditablePrice);
|
||||
public static GridColumnSettings commit => new GridColumnSettings(Columns.Commit.ToString(), width: DIYGrid.DefaultColumnWidths.MiniOrderBook.Commit);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criteria {
|
||||
get {
|
||||
yield return new SortingCriteria(time_created.id, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> standard {
|
||||
get {
|
||||
yield return security;
|
||||
yield return symbol;
|
||||
yield return time_created;
|
||||
yield return last_change;
|
||||
yield return user;
|
||||
yield return account;
|
||||
yield return exchange;
|
||||
yield return time_in_force;
|
||||
yield return order_type;
|
||||
yield return root_algo_type;
|
||||
yield return sub_type;
|
||||
yield return side;
|
||||
yield return quantity;
|
||||
yield return price;
|
||||
yield return piq;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_message;
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<GridColumnSettings> editable {
|
||||
get {
|
||||
yield return cancel;
|
||||
yield return update;
|
||||
yield return edit_quantity;
|
||||
yield return edit_price;
|
||||
yield return commit;
|
||||
yield return attach_detach;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
symbol, last_change, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
get {
|
||||
foreach (var col_setting in editable)
|
||||
yield return col_setting;
|
||||
foreach (var col_setting in standard) {
|
||||
if (_mini_mode_columns_to_hide.Contains(col_setting)) {
|
||||
yield return col_setting.with(is_visible: false);
|
||||
} else {
|
||||
yield return col_setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,109 @@
|
||||
using bts.api.orderman;
|
||||
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
RowStyleKey = "RowStyle",
|
||||
RowTemplateKey = "RowTemplateKey",
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys {
|
||||
Security = 1,
|
||||
Symbol = 1 << 1,
|
||||
Time = 1 << 2,
|
||||
LastChange = 1 << 3,
|
||||
User = 1 << 4,
|
||||
Account = 1 << 5,
|
||||
Exchange = 1 << 6,
|
||||
TimeInForce = 1 << 7,
|
||||
OrderType = 1 << 8,
|
||||
SubType = 1 << 9,
|
||||
Side = 1 << 10,
|
||||
Size = 1 << 11,
|
||||
Price = 1 << 12,
|
||||
Filled = 1 << 13,
|
||||
AvgFilled = 1 << 14,
|
||||
LastPrice = 1 << 15,
|
||||
Status = 1 << 16,
|
||||
LastMessage = 1 << 17,
|
||||
SecurityKey = 1 << 18,
|
||||
OrderKey = 1 << 19,
|
||||
ParentKey = 1 << 20,
|
||||
AttachedTargetOrderKey = 1 << 21,
|
||||
Cancel = 1 << 22,
|
||||
EditableQty = 1 << 23,
|
||||
EditablePrice = 1 << 24,
|
||||
Attach = 1 << 25,
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
InitializeComponent();
|
||||
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
|
||||
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
|
||||
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
|
||||
}
|
||||
|
||||
static class RowBackgroundHelper {
|
||||
public static readonly BindingBase row_background_binding;
|
||||
|
||||
static RowBackgroundHelper() {
|
||||
var bg_binding = new MultiBinding();
|
||||
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
|
||||
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
|
||||
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
|
||||
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
|
||||
});
|
||||
} else {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
if (is_selected)
|
||||
return null;
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) : null;
|
||||
});
|
||||
}
|
||||
row_background_binding = bg_binding;
|
||||
}
|
||||
|
||||
static Color? _status_color(OrderStatus status) {
|
||||
switch (status) {
|
||||
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
|
||||
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
|
||||
case OrderStatus.Disconnected:
|
||||
case OrderStatus.RejectedBySpark:
|
||||
case OrderStatus.Error:
|
||||
return BTSColors.Orders.error_order_background;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
using bts.api.orderman;
|
||||
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
RowStyleKey = "RowStyle",
|
||||
RowTemplateKey = "RowTemplateKey",
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys : long {
|
||||
Security = 1,
|
||||
Symbol = 1 << 1,
|
||||
Time = 1 << 2,
|
||||
LastChange = 1 << 3,
|
||||
User = 1 << 4,
|
||||
Account = 1 << 5,
|
||||
Exchange = 1 << 6,
|
||||
TimeInForce = 1 << 7,
|
||||
OrderType = 1 << 8,
|
||||
SubType = 1 << 9,
|
||||
Side = 1 << 10,
|
||||
Size = 1 << 11,
|
||||
Price = 1 << 12,
|
||||
Filled = 1 << 13,
|
||||
AvgFilled = 1 << 14,
|
||||
LastPrice = 1 << 15,
|
||||
Status = 1 << 16,
|
||||
LastMessage = 1 << 17,
|
||||
SecurityKey = 1 << 18,
|
||||
OrderKey = 1 << 19,
|
||||
ParentKey = 1 << 20,
|
||||
AttachedTargetOrderKey = 1 << 21,
|
||||
Cancel = 1 << 22,
|
||||
EditableQty = 1 << 23,
|
||||
EditablePrice = 1 << 24,
|
||||
Attach = 1 << 25,
|
||||
Detach = 1 << 26,
|
||||
Update = 1 << 27,
|
||||
Commit = 1 << 28,
|
||||
RootAlgoType = 1 << 29,
|
||||
Piq = 1 << 30,
|
||||
Check = 1 << 31,
|
||||
NotionalValue = 1L << 32,
|
||||
FilledNotionalValue = 1L << 33,
|
||||
}
|
||||
|
||||
public OrderBookGridView() {
|
||||
InitializeComponent();
|
||||
var row_template_factory = new FrameworkElementFactory(typeof(OrderBookRowView));
|
||||
row_template_factory.SetValue(OrderBookRowView.BackgroundProperty, RowBackgroundHelper.row_background_binding);
|
||||
RowTemplate = new DataTemplate { VisualTree = row_template_factory };
|
||||
}
|
||||
|
||||
static class RowBackgroundHelper {
|
||||
public static readonly BindingBase row_background_binding;
|
||||
|
||||
static RowBackgroundHelper() {
|
||||
var bg_binding = new MultiBinding();
|
||||
bg_binding.Bindings.Add(new Binding(nameof(OrderBookRowVM.status)));
|
||||
bg_binding.Bindings.Add(new Binding(nameof(ListBoxItem.IsSelected)) { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ListBoxItem), 1) });
|
||||
double overlay_opacity = config.Configuration.get<bts.config.OrderBookOptions>().StatusColorOpacityWhenSelected;
|
||||
if (overlay_opacity > 0.1 && overlay_opacity < 0.9) {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) { Opacity = is_selected ? overlay_opacity : 1.0 } : null;
|
||||
});
|
||||
} else {
|
||||
bg_binding.Converter = ValueConverter.create<OrderStatus, bool, Brush>((status, is_selected) => {
|
||||
if (is_selected)
|
||||
return null;
|
||||
var color = _status_color(status);
|
||||
return color.HasValue ? new SolidColorBrush(color.Value) : null;
|
||||
});
|
||||
}
|
||||
row_background_binding = bg_binding;
|
||||
}
|
||||
|
||||
static Color? _status_color(OrderStatus status) {
|
||||
switch (status) {
|
||||
case OrderStatus.Canceled: return BTSColors.Orders.canceled_order_background;
|
||||
case OrderStatus.Completed: return BTSColors.Orders.completed_order_background;
|
||||
case OrderStatus.Disconnected:
|
||||
case OrderStatus.RejectedBySpark:
|
||||
case OrderStatus.Error:
|
||||
return BTSColors.Orders.error_order_background;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -1,526 +1,317 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.analytics;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Api.Position;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
public enum PNLCalculationType {
|
||||
None,
|
||||
Bid,
|
||||
Ask,
|
||||
Opposite,
|
||||
Same,
|
||||
[Description("Cascade")]
|
||||
Waterfall,
|
||||
Last,
|
||||
Mid,
|
||||
Settle
|
||||
}
|
||||
|
||||
readonly struct WorkingOrderData {
|
||||
public readonly WorkingOrderSideData long_data, short_data;
|
||||
|
||||
WorkingOrderData(WorkingOrderSideData long_data, WorkingOrderSideData short_data) {
|
||||
this.long_data = long_data;
|
||||
this.short_data = short_data;
|
||||
}
|
||||
|
||||
public static WorkingOrderData Empty =>
|
||||
new WorkingOrderData(WorkingOrderSideData.EmptyLong, WorkingOrderSideData.EmptyShort);
|
||||
|
||||
public double top_bid =>
|
||||
long_data.data.Count > 0 ?
|
||||
long_data.data.First().Key :
|
||||
double.NaN;
|
||||
|
||||
public double top_ask =>
|
||||
short_data.data.Count > 0 ?
|
||||
short_data.data.First().Key :
|
||||
double.NaN;
|
||||
|
||||
public PositionPriceSize summary =>
|
||||
new PositionPriceSize(long_data.working_total, long_data.avg_price, short_data.working_total, short_data.avg_price);
|
||||
|
||||
public static WorkingOrderData Create(
|
||||
IEnumerable<OrderRecordImmutable> orders, QuantityConversionData<double> qty_conversion) =>
|
||||
Create(orders.Select(o => (o, qty_conversion)));
|
||||
|
||||
public static WorkingOrderData Create(
|
||||
IEnumerable<(OrderRecordImmutable order, QuantityConversionData<double> qty_conversion)> orders) =>
|
||||
orders.Aggregate(Empty, (seed, tup) => seed.Add(tup.order, tup.qty_conversion));
|
||||
|
||||
public WorkingOrderData Add(OrderRecordImmutable order, QuantityConversionData<double> qty_conversion) {
|
||||
if (order.working_quantity() <= 0)
|
||||
return new WorkingOrderData(long_data, short_data);
|
||||
switch (order.side()) {
|
||||
case Side.Buy: return new WorkingOrderData(long_data.Add(order, qty_conversion), short_data);
|
||||
case Side.Sell: return new WorkingOrderData(long_data, short_data.Add(order, qty_conversion));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
|
||||
}
|
||||
}
|
||||
|
||||
public WorkingOrderData Remove(OrderRecordImmutable order) {
|
||||
if (order.working_quantity() <= 0)
|
||||
return new WorkingOrderData(long_data, short_data);
|
||||
switch (order.side()) {
|
||||
case Side.Buy: return new WorkingOrderData(long_data.Remove(order.Key, order.price()), short_data);
|
||||
case Side.Sell: return new WorkingOrderData(long_data, short_data.Remove(order.Key, order.price()));
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct WorkingOrderSideData {
|
||||
public readonly double avg_price, display_avg_price;
|
||||
public readonly ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable order, QuantityConversionData<double> qty_conversion)>> data;
|
||||
public readonly double working_total;
|
||||
public readonly double display_working_total;
|
||||
|
||||
WorkingOrderSideData(
|
||||
double avg_price,
|
||||
double working_total,
|
||||
double display_avg_price,
|
||||
double display_working_total,
|
||||
ImmutableSortedDictionary<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>> data
|
||||
) {
|
||||
this.avg_price = avg_price;
|
||||
this.working_total = working_total;
|
||||
this.display_avg_price = display_avg_price;
|
||||
this.data = data;
|
||||
this.display_working_total = display_working_total;
|
||||
}
|
||||
|
||||
public static WorkingOrderSideData EmptyLong => new(
|
||||
double.NaN, 0, double.NaN, 0,
|
||||
ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>>(
|
||||
new ReverseComparer<double>(new OrderPriceComparer())));
|
||||
|
||||
public static WorkingOrderSideData EmptyShort => new(
|
||||
double.NaN, 0, double.NaN, 0,
|
||||
ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>>(
|
||||
new OrderPriceComparer()));
|
||||
|
||||
public double top_price =>
|
||||
data.Count > 0 ?
|
||||
data.First().Key :
|
||||
double.NaN;
|
||||
|
||||
public double top_volume =>
|
||||
data.Count > 0 ?
|
||||
data.Values.First().Values.Sum(o => o.order.working_quantity()) :
|
||||
0;
|
||||
|
||||
public double display_top_volume =>
|
||||
data.Count > 0 ?
|
||||
data.Values.First().Values.Sum(o => o.qty_conversion
|
||||
.convert_numeric_type<double>()
|
||||
.to_display(o.order.working_quantity())) :
|
||||
0;
|
||||
|
||||
public WorkingOrderSideData Add(OrderRecordImmutable order_record) =>
|
||||
Add(order_record, new());
|
||||
|
||||
public WorkingOrderSideData Add(
|
||||
OrderRecordImmutable order_record, QuantityConversionData<double> qty_conversion) {
|
||||
var working_qty = order_record.working_quantity();
|
||||
if (working_qty <= 0)
|
||||
throw new ArgumentException(nameof(working_qty));
|
||||
uint order = order_record.Key;
|
||||
var dworking_qty = qty_conversion.to_display(working_qty);
|
||||
double price = order_record.price();
|
||||
if (working_total == 0) {
|
||||
Log.assert(display_working_total == 0);
|
||||
return new WorkingOrderSideData(price, working_qty, display_avg_price, dworking_qty, data.Add(
|
||||
price, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>.Empty.Add(order, (order_record, qty_conversion))));
|
||||
} else return new WorkingOrderSideData(
|
||||
avg_price + ((price - avg_price) * ((double)working_qty / (working_total + working_qty))),
|
||||
working_total + working_qty,
|
||||
display_avg_price + ((price - display_avg_price) * ((double)dworking_qty / (display_working_total + dworking_qty))),
|
||||
display_working_total + dworking_qty,
|
||||
data.SetItem(price, data.TryGetValue(price, out var curr) ?
|
||||
curr.Add(order, (order_record, qty_conversion)) :
|
||||
ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>.Empty.Add(order, (order_record, qty_conversion))));
|
||||
}
|
||||
|
||||
public WorkingOrderSideData Remove(uint order, double price) {
|
||||
var tmp_data = data;
|
||||
if (!tmp_data.TryGetValue(price, out var wdata))
|
||||
throw new KeyNotFoundException(nameof(price));
|
||||
if (!wdata.TryGetValue(order, out var worder))
|
||||
throw new KeyNotFoundException(nameof(order));
|
||||
var wqty = worder.order.working_quantity();
|
||||
var dwqty = worder.qty_conversion.to_display(wqty);
|
||||
wdata = wdata.Remove(order);
|
||||
if (wdata.Count == 0)
|
||||
tmp_data = tmp_data.Remove(price);
|
||||
if (tmp_data.Count == 0)
|
||||
return new(
|
||||
double.NaN, 0, double.NaN, 0,
|
||||
ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, (OrderRecordImmutable, QuantityConversionData<double>)>>(
|
||||
data.KeyComparer));
|
||||
return new(
|
||||
avg_price - ((price - avg_price) * (wqty / (working_total - wqty))),
|
||||
working_total - wqty,
|
||||
display_avg_price - ((price - display_avg_price) * (dwqty / (display_working_total - dwqty))),
|
||||
display_working_total - dwqty,
|
||||
tmp_data);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
if (data.Count > 0) {
|
||||
var best_order = data.Values.First().Values.First();
|
||||
return OrderUtils.order_collection_summary(
|
||||
best_order.order.price(),
|
||||
display_avg_price,
|
||||
best_order.qty_conversion.to_display(best_order.order.working_quantity()),
|
||||
display_working_total,
|
||||
Security.Defaults.ContractSpecificationImmutable);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct PNLRowData : IEquatable<PNLRowData> {
|
||||
public static readonly Func<PNLRowData, PNLKey> key_fn = d => d.key;
|
||||
public static IComparer<PNLRowData> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowData>(d => d.key);
|
||||
|
||||
static readonly WaterfallPriceFlags[] _waterfall_preferences = new[] {
|
||||
WaterfallPriceFlags.LastInsideSpread,
|
||||
WaterfallPriceFlags.Mid,
|
||||
WaterfallPriceFlags.LastGTEBid,
|
||||
WaterfallPriceFlags.Bid,
|
||||
WaterfallPriceFlags.LastLTEAsk,
|
||||
WaterfallPriceFlags.Ask,
|
||||
WaterfallPriceFlags.Last,
|
||||
WaterfallPriceFlags.Settle
|
||||
};
|
||||
|
||||
public readonly PNLKey key;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly QuantityConversionData<double> qty_conversion;
|
||||
public readonly uint account_id;
|
||||
public readonly string account_name;
|
||||
public readonly PNLCalculationType calc_type;
|
||||
public readonly PositionPriceSizeWithGreeks sod;
|
||||
public readonly PositionPriceSizeWithGreeks day;
|
||||
public readonly WorkingOrderData working;
|
||||
|
||||
public readonly double
|
||||
default_cpp,
|
||||
bid_price,
|
||||
ask_price,
|
||||
last_price,
|
||||
settlement_price;
|
||||
|
||||
public readonly double? override_cpp;
|
||||
|
||||
public PNLRowData(
|
||||
PNLKey key,
|
||||
double default_cpp = 1,
|
||||
double? override_cpp = null,
|
||||
double pnl_price = double.NaN,
|
||||
double bid_price = 0,
|
||||
double ask_price = 0,
|
||||
double last_price = 0,
|
||||
double settlement_price = 0,
|
||||
double sod_pnl = 0,
|
||||
PositionPriceSizeWithGreeks? sod = null,
|
||||
double day_pnl = 0,
|
||||
PositionPriceSizeWithGreeks? day = null,
|
||||
WorkingOrderData? working = null,
|
||||
SecurityImmutable? security = null,
|
||||
uint account_id = 0,
|
||||
string account_name = null,
|
||||
PNLCalculationType calc_type = PNLCalculationType.Mid,
|
||||
QuantityConversionData<double>? qty_conversion = null) {
|
||||
//
|
||||
this.key = key;
|
||||
this.default_cpp = default_cpp;
|
||||
this.override_cpp = override_cpp;
|
||||
this.pnl_price = pnl_price;
|
||||
this.bid_price = bid_price;
|
||||
this.ask_price = ask_price;
|
||||
this.last_price = last_price;
|
||||
this.settlement_price = settlement_price;
|
||||
this.sod_pnl = sod_pnl;
|
||||
this.sod = sod ?? PositionPriceSizeWithGreeks.empty;
|
||||
this.day_pnl = day_pnl;
|
||||
this.day = day ?? PositionPriceSizeWithGreeks.empty;
|
||||
this.working = working ?? WorkingOrderData.Empty;
|
||||
this.security = security;
|
||||
this.account_id = account_id;
|
||||
this.account_name = account_name;
|
||||
this.calc_type = calc_type;
|
||||
this.qty_conversion = qty_conversion ?? new();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum WaterfallPriceFlags {
|
||||
None = 0,
|
||||
Bid = 1,
|
||||
Ask = 1 << 1,
|
||||
Last = 1 << 2,
|
||||
Settle = 1 << 3,
|
||||
Mid = Bid | Ask,
|
||||
LastGTEBid = Bid | Last | 1 << 4,
|
||||
LastLTEAsk = Ask | Last | 1 << 5,
|
||||
LastInsideSpread = LastGTEBid | LastLTEAsk,
|
||||
BidGTESettle = Bid | Settle | 1 << 6,
|
||||
AskLTESettle = Ask | Settle | 1 << 7
|
||||
}
|
||||
|
||||
public AccountTuple account => new(account_id, account_name);
|
||||
public string symbol => security?.pnl_symbol() ?? string.Empty;
|
||||
public DateTime expiration => security?.earliest_expiration() ?? DateTime.MinValue;
|
||||
public double effective_cpp => override_cpp ?? default_cpp;
|
||||
|
||||
/// Market/Calc
|
||||
public double mid_price => (bid_price + ask_price) / 2;
|
||||
public double pnl_price { get; }
|
||||
|
||||
/// Sod
|
||||
public double sod_pnl { get; }
|
||||
public double sod_long_pos => sod.long_pos;
|
||||
public double sod_long_avg_price => sod.long_avg;
|
||||
public double sod_short_pos => sod.short_pos;
|
||||
public double sod_short_avg_price => sod.short_avg;
|
||||
public double sod_net_pos => sod.net_pos;
|
||||
public double sod_net_avg_price => sod.net_avg;
|
||||
public double sod_delta => sod.delta;
|
||||
public double sod_gamma => sod.gamma;
|
||||
public double sod_vega => sod.vega;
|
||||
public double sod_theta => sod.theta;
|
||||
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
|
||||
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
|
||||
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
|
||||
|
||||
/// Day
|
||||
public double day_pnl { get; }
|
||||
public double day_long_pos => day.long_pos;
|
||||
public double day_long_avg_price => day.long_avg;
|
||||
public double day_short_pos => day.short_pos;
|
||||
public double day_short_avg_price => day.short_avg;
|
||||
public double day_delta => day.delta;
|
||||
public double day_gamma => day.gamma;
|
||||
public double day_vega => day.vega;
|
||||
public double day_theta => day.theta;
|
||||
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
|
||||
public double day_net_pos => day.net_pos;
|
||||
public double day_net_avg => day.net_avg;
|
||||
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
|
||||
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
|
||||
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
|
||||
|
||||
/// Total
|
||||
public double total_pnl => new[] { sod_pnl, day_pnl }.sum_real();
|
||||
public double total_long_pos => new[] { sod.long_pos, day.long_pos }.sum_real();
|
||||
public double total_long_avg_price =>
|
||||
MathUtils.weighted_average(
|
||||
sod_long_avg_price.if_nan(0),
|
||||
sod_long_pos.if_nan(0),
|
||||
day_long_avg_price.if_nan(0),
|
||||
day_long_pos.if_nan(0));
|
||||
public double total_short_pos => new[] { sod.short_pos, day.short_pos }.sum_real();
|
||||
public double total_short_avg_price =>
|
||||
MathUtils.weighted_average(
|
||||
sod_short_avg_price.if_nan(0),
|
||||
sod_short_pos.if_nan(0),
|
||||
day_short_avg_price.if_nan(0),
|
||||
day_short_pos.if_nan(0));
|
||||
public double total_net_pos => new[] { sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos }.Where(n => n.is_real()).Sum();
|
||||
public double total_delta => sod.delta.add_real(day.delta);
|
||||
public double total_gamma => sod.gamma.add_real(day.gamma);
|
||||
public double total_vega => sod.vega.add_real(day.vega);
|
||||
public double total_theta => sod.theta.add_real(day.theta);
|
||||
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
|
||||
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
|
||||
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
|
||||
|
||||
/// Working
|
||||
public double working_long_pos => working.long_data.working_total;
|
||||
public double working_long_avg_price => effective_cpp * working.long_data.avg_price;
|
||||
public double working_long_top_price => effective_cpp * working.long_data.top_price;
|
||||
public double working_short_pos => working.short_data.working_total;
|
||||
public double working_short_avg_price => effective_cpp * working.short_data.avg_price;
|
||||
public double working_short_top_price => effective_cpp * working.short_data.top_price;
|
||||
public double working_net_pos => working_long_pos - working_short_pos;
|
||||
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
|
||||
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
|
||||
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
|
||||
|
||||
public static PNLRowData from_pnl_unit(IPNLUnit u) =>
|
||||
new(
|
||||
u.id(),
|
||||
default_cpp: 1.0,
|
||||
override_cpp: null,
|
||||
pnl_price: u.theo,
|
||||
bid_price: double.NaN,
|
||||
ask_price: double.NaN,
|
||||
last_price: double.NaN,
|
||||
settlement_price: u.settle_price,
|
||||
sod_pnl: u.start_pnl,
|
||||
sod: new() {
|
||||
price_size = new(
|
||||
u.start_long_pos,
|
||||
u.start_long_avg_cost,
|
||||
u.start_short_pos,
|
||||
u.start_short_avg_cost),
|
||||
greeks = new() {
|
||||
delta = u.start_delta,
|
||||
gamma = u.start_gamma,
|
||||
vega = u.start_vega,
|
||||
theta = u.start_theta
|
||||
}
|
||||
},
|
||||
day_pnl: u.day_pnl,
|
||||
day: new() {
|
||||
price_size = new(
|
||||
u.day_long_pos,
|
||||
u.day_long_avg_cost,
|
||||
u.day_short_pos,
|
||||
u.day_short_avg_cost),
|
||||
greeks = new() {
|
||||
delta = u.day_delta,
|
||||
gamma = u.day_gamma,
|
||||
vega = u.day_vega,
|
||||
theta = u.day_theta
|
||||
}
|
||||
},
|
||||
working: null,
|
||||
security: u.security,
|
||||
account_id: u.account_id,
|
||||
account_name: u.account,
|
||||
calc_type: PNLCalculationType.Mid,
|
||||
qty_conversion: null);
|
||||
|
||||
public PNLRowData with(
|
||||
SecurityImmutable? security = null,
|
||||
uint? account_id = null,
|
||||
string account_name = null,
|
||||
WorkingOrderData? working = null,
|
||||
PNLCalculationType? calc_type = null) =>
|
||||
new PNLRowData(
|
||||
key,
|
||||
default_cpp,
|
||||
override_cpp,
|
||||
pnl_price,
|
||||
bid_price,
|
||||
ask_price,
|
||||
last_price,
|
||||
settlement_price,
|
||||
sod_pnl: sod_pnl,
|
||||
sod,
|
||||
day_pnl: day_pnl,
|
||||
day,
|
||||
working ?? this.working,
|
||||
security ?? this.security,
|
||||
account_id ?? this.account_id,
|
||||
account_name ?? this.account_name,
|
||||
calc_type ?? this.calc_type,
|
||||
qty_conversion: qty_conversion);
|
||||
|
||||
WaterfallPriceFlags _waterfall_flags() {
|
||||
var flags = WaterfallPriceFlags.None;
|
||||
if (!double.IsNaN(bid_price)) flags |= WaterfallPriceFlags.Bid;
|
||||
if (!double.IsNaN(ask_price)) flags |= WaterfallPriceFlags.Ask;
|
||||
if (!double.IsNaN(last_price)) flags |= WaterfallPriceFlags.Last;
|
||||
if (last_price >= bid_price) flags |= WaterfallPriceFlags.LastGTEBid;
|
||||
if (last_price <= ask_price) flags |= WaterfallPriceFlags.LastLTEAsk;
|
||||
if (!double.IsNaN(settlement_price)) flags |= WaterfallPriceFlags.Settle;
|
||||
if (bid_price >= settlement_price) flags |= WaterfallPriceFlags.BidGTESettle;
|
||||
if (ask_price <= settlement_price) flags |= WaterfallPriceFlags.AskLTESettle;
|
||||
return flags;
|
||||
}
|
||||
|
||||
double _waterfall_price() {
|
||||
var flags = _waterfall_flags();
|
||||
// return price for the best match
|
||||
for (int i = 0; i < _waterfall_preferences.Length; i++) {
|
||||
if ((_waterfall_preferences[i] & flags) == _waterfall_preferences[i])
|
||||
return _waterfall_price(_waterfall_preferences[i]);
|
||||
}
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
double _waterfall_price(WaterfallPriceFlags ltp) {
|
||||
switch (ltp) {
|
||||
case WaterfallPriceFlags.Last:
|
||||
case WaterfallPriceFlags.LastInsideSpread:
|
||||
case WaterfallPriceFlags.LastGTEBid:
|
||||
case WaterfallPriceFlags.LastLTEAsk: return last_price;
|
||||
case WaterfallPriceFlags.Mid: return mid_price;
|
||||
case WaterfallPriceFlags.Bid:
|
||||
case WaterfallPriceFlags.BidGTESettle: return bid_price;
|
||||
case WaterfallPriceFlags.Ask:
|
||||
case WaterfallPriceFlags.AskLTESettle: return ask_price;
|
||||
case WaterfallPriceFlags.Settle: return settlement_price;
|
||||
default: return double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowData other) =>
|
||||
key == other.key &&
|
||||
default_cpp == other.default_cpp &&
|
||||
override_cpp == other.override_cpp &&
|
||||
security.HasValue == other.security.HasValue &&
|
||||
account_name == other.account_name &&
|
||||
sod == other.sod &&
|
||||
day == other.day &&
|
||||
bid_price.Equals(other.bid_price) &&
|
||||
ask_price.Equals(other.ask_price) &&
|
||||
last_price.Equals(other.last_price) &&
|
||||
mid_price.Equals(other.mid_price) &&
|
||||
settlement_price.Equals(other.settlement_price) &&
|
||||
calc_type.Equals(other.calc_type) &&
|
||||
working.summary == other.working.summary &&
|
||||
qty_conversion == other.qty_conversion;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLRowData other && Equals(other);
|
||||
|
||||
// assuming callers will not combine different values with the same key,
|
||||
// the other components shouldn't be included in the hash calculation.
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public static bool operator ==(PNLRowData a, PNLRowData b) => a.Equals(b);
|
||||
public static bool operator !=(PNLRowData a, PNLRowData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class PNLDataExtensions {
|
||||
public static string pnl_symbol(this SecurityImmutable security) {
|
||||
return bts.api.ExchangeSpecificUtilities.get_pnl_symbol(security);
|
||||
}
|
||||
|
||||
// while override_cpp could use double.NaN to indicate the lack of an override,
|
||||
// the nullable double makes it clear that it is an optional value.
|
||||
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
|
||||
security.HasValue ?
|
||||
pnl_cpp_tuple(pnl_unit, security.Value) :
|
||||
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
|
||||
|
||||
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
|
||||
// TODO: actual impl
|
||||
double calced_pnl = bts.api.ExchangeSpecificUtilities.get_cpp(pnl_unit, security);
|
||||
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.api.position;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.analytics;
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Position;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
public enum PNLCalculationType {
|
||||
None,
|
||||
Bid,
|
||||
Ask,
|
||||
Opposite,
|
||||
Same,
|
||||
[Description("Cascade")]
|
||||
Waterfall,
|
||||
Last,
|
||||
Mid,
|
||||
Settle
|
||||
}
|
||||
|
||||
readonly struct PNLRowData : IEquatable<PNLRowData> {
|
||||
public static readonly Func<PNLRowData, PNLKey> key_fn = d => d.key;
|
||||
public static IComparer<PNLRowData> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowData>(d => d.key);
|
||||
|
||||
public readonly PNLKey key;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly QuantityConversionData<double> qty_conversion;
|
||||
public readonly uint account_id;
|
||||
public readonly string account_name;
|
||||
public readonly PNLCalculationType calc_type;
|
||||
public readonly PositionPriceSizeWithGreeks sod;
|
||||
public readonly PositionPriceSizeWithGreeks day;
|
||||
public readonly WorkingOrderData working;
|
||||
|
||||
public readonly double
|
||||
default_cpp,
|
||||
bid_price,
|
||||
ask_price,
|
||||
last_price,
|
||||
settlement_price;
|
||||
|
||||
public readonly double? override_cpp;
|
||||
|
||||
public PNLRowData(
|
||||
PNLKey key,
|
||||
double default_cpp = 1,
|
||||
double? override_cpp = null,
|
||||
double pnl_price = double.NaN,
|
||||
double bid_price = 0,
|
||||
double ask_price = 0,
|
||||
double last_price = 0,
|
||||
double settlement_price = 0,
|
||||
PositionPriceSizeWithGreeks? sod = null,
|
||||
PositionPriceSizeWithGreeks? day = null,
|
||||
WorkingOrderData? working = null,
|
||||
SecurityImmutable? security = null,
|
||||
uint account_id = 0,
|
||||
string account_name = null,
|
||||
PNLCalculationType calc_type = PNLCalculationType.Mid,
|
||||
QuantityConversionData<double>? qty_conversion = null) {
|
||||
//
|
||||
this.key = key;
|
||||
this.default_cpp = default_cpp;
|
||||
this.override_cpp = override_cpp;
|
||||
this.pnl_price = pnl_price;
|
||||
this.bid_price = bid_price;
|
||||
this.ask_price = ask_price;
|
||||
this.last_price = last_price;
|
||||
this.settlement_price = settlement_price;
|
||||
this.sod = sod ?? PositionPriceSizeWithGreeks.empty;
|
||||
this.day = day ?? PositionPriceSizeWithGreeks.empty;
|
||||
this.working = working ?? WorkingOrderData.Empty;
|
||||
this.security = security;
|
||||
this.account_id = account_id;
|
||||
this.account_name = account_name;
|
||||
this.calc_type = calc_type;
|
||||
this.qty_conversion = qty_conversion ?? new();
|
||||
}
|
||||
|
||||
public AccountTuple account => new(account_id, account_name);
|
||||
public string symbol => security?.pnl_symbol() ?? string.Empty;
|
||||
public ExpirationDate expiration =>
|
||||
security?.normal_expirations()?.MinOrDefault(ExpirationDate.None) ?? ExpirationDate.None;
|
||||
public double effective_cpp => override_cpp ?? default_cpp;
|
||||
|
||||
/// Market/Calc
|
||||
public double mid_price => (bid_price + ask_price) / 2;
|
||||
public double pnl_price { get; }
|
||||
|
||||
/// Sod
|
||||
public double sod_pnl => new[] { sod_realized_pnl, sod_unrealized_pnl }.sum_real();
|
||||
public double sod_long_pos => sod.long_pos;
|
||||
public double sod_long_avg_price => sod.long_avg;
|
||||
public double sod_long_avg_pnl => bid_price - sod.long_avg;
|
||||
public double sod_short_pos => sod.short_pos;
|
||||
public double sod_short_avg_price => sod.short_avg;
|
||||
public double sod_short_avg_pnl => sod.short_avg - ask_price;
|
||||
public double sod_net_pos => sod.net_pos;
|
||||
public double sod_net_avg_price => sod.net_avg;
|
||||
public double sod_unrealized_pnl => sod_long_pos > sod_short_pos
|
||||
? (sod_long_pos - sod_short_pos) * effective_cpp * (bid_price - sod_long_avg_price)
|
||||
: (sod_short_pos - sod_long_pos) * effective_cpp * (sod_short_avg_price - ask_price);
|
||||
public double sod_realized_pnl => (sod_short_avg_price - sod_long_avg_price) * effective_cpp * Math.Min(sod_long_pos, sod_short_pos);
|
||||
public double sod_delta => sod.delta;
|
||||
public double sod_gamma => sod.gamma;
|
||||
public double sod_vega => sod.vega;
|
||||
public double sod_theta => sod.theta;
|
||||
public double display_sod_net_pos => qty_conversion.to_display(sod_net_pos);
|
||||
public double display_sod_long_pos => qty_conversion.to_display(sod_long_pos);
|
||||
public double display_sod_short_pos => qty_conversion.to_display(sod_short_pos);
|
||||
|
||||
/// Day
|
||||
public double day_pnl => new[] { day_realized_pnl + day_unrealized_pnl }.sum_real();
|
||||
public double day_long_pos => day.long_pos;
|
||||
public double day_long_avg_price => day.long_avg;
|
||||
public double day_long_avg_pnl => bid_price - day.long_avg;
|
||||
public double day_short_pos => day.short_pos;
|
||||
public double day_short_avg_price => day.short_avg;
|
||||
public double day_short_avg_pnl => day.short_avg - ask_price;
|
||||
public double day_delta => day.delta;
|
||||
public double day_gamma => day.gamma;
|
||||
public double day_vega => day.vega;
|
||||
public double day_theta => day.theta;
|
||||
public bool has_day_trades => day.long_pos > 0 || day.short_pos > 0;
|
||||
public double day_net_pos => day.net_pos;
|
||||
public double day_net_avg => day.net_avg;
|
||||
public double day_unrealized_pnl => day_long_pos > day_short_pos
|
||||
? (day_long_pos - day_short_pos) * effective_cpp * (bid_price - day_long_avg_price)
|
||||
: (day_short_pos - day_long_pos) * effective_cpp * (day_short_avg_price - ask_price);
|
||||
public double day_realized_pnl => (day_short_avg_price - day_long_avg_price) * effective_cpp * Math.Min(day_long_pos, day_short_pos);
|
||||
public double display_day_net_pos => qty_conversion.to_display(day_net_pos);
|
||||
public double display_day_long_pos => qty_conversion.to_display(day_long_pos);
|
||||
public double display_day_short_pos => qty_conversion.to_display(day_short_pos);
|
||||
|
||||
/// Total
|
||||
public double total_pnl => new[] { sod_pnl, day_pnl }.sum_real();
|
||||
public double total_long_pos => new[] { sod.long_pos, day.long_pos }.sum_real();
|
||||
public double total_long_avg_price =>
|
||||
MathUtils.weighted_average(
|
||||
sod_long_avg_price.if_nan(0),
|
||||
sod_long_pos.if_nan(0),
|
||||
day_long_avg_price.if_nan(0),
|
||||
day_long_pos.if_nan(0));
|
||||
public double total_long_avg_pnl => new[] { sod_long_avg_pnl, day_long_avg_pnl }.sum_real();
|
||||
public double total_short_pos => new[] { sod.short_pos, day.short_pos }.sum_real();
|
||||
public double total_short_avg_price =>
|
||||
MathUtils.weighted_average(
|
||||
sod_short_avg_price.if_nan(0),
|
||||
sod_short_pos.if_nan(0),
|
||||
day_short_avg_price.if_nan(0),
|
||||
day_short_pos.if_nan(0));
|
||||
public double total_short_avg_pnl => new[] { sod_short_avg_pnl, day_short_avg_pnl }.sum_real();
|
||||
public double total_net_pos => new[] { sod.long_pos, -sod.short_pos, day.long_pos, -day.short_pos }.Where(n => n.is_real()).Sum();
|
||||
public double total_unrealized_pnl => new[] { sod_unrealized_pnl, day_unrealized_pnl }.sum_real();
|
||||
public double total_realized_pnl => new[] { sod_realized_pnl, day_realized_pnl }.sum_real();
|
||||
public double total_delta => sod.delta.add_real(day.delta);
|
||||
public double total_gamma => sod.gamma.add_real(day.gamma);
|
||||
public double total_vega => sod.vega.add_real(day.vega);
|
||||
public double total_theta => sod.theta.add_real(day.theta);
|
||||
public double display_total_net_pos => qty_conversion.to_display(total_net_pos);
|
||||
public double display_total_long_pos => qty_conversion.to_display(total_long_pos);
|
||||
public double display_total_short_pos => qty_conversion.to_display(total_short_pos);
|
||||
|
||||
/// Working
|
||||
public double working_long_pos => working.long_data.working_total;
|
||||
public double working_long_avg_price => effective_cpp * working.long_data.avg_price;
|
||||
public double working_long_top_price => effective_cpp * working.long_data.top_price;
|
||||
public double working_short_pos => working.short_data.working_total;
|
||||
public double working_short_avg_price => effective_cpp * working.short_data.avg_price;
|
||||
public double working_short_top_price => effective_cpp * working.short_data.top_price;
|
||||
public double working_net_pos => working_long_pos - working_short_pos;
|
||||
public double display_working_net_pos => qty_conversion.to_display(working_net_pos);
|
||||
public double display_working_long_pos => qty_conversion.to_display(working_long_pos);
|
||||
public double display_working_short_pos => qty_conversion.to_display(working_short_pos);
|
||||
|
||||
public static PNLRowData from_pnl_unit(IPNLUnit u) =>
|
||||
new(
|
||||
u.id(),
|
||||
default_cpp: u.security.spec_or_default().CashPerPoint,
|
||||
override_cpp: null,
|
||||
pnl_price: u.theo,
|
||||
bid_price: double.NaN,
|
||||
ask_price: double.NaN,
|
||||
last_price: double.NaN,
|
||||
settlement_price: u.settle_price,
|
||||
// Now computed as the sum: sod_realized_pnl + sod_unrealized_pnl
|
||||
// sod_pnl: u.start_pnl,
|
||||
sod: new() {
|
||||
price_size = new(
|
||||
u.start_long_pos,
|
||||
u.start_long_avg_cost,
|
||||
u.start_short_pos,
|
||||
-u.start_short_avg_cost),
|
||||
greeks = new() {
|
||||
delta = u.start_delta,
|
||||
gamma = u.start_gamma,
|
||||
vega = u.start_vega,
|
||||
theta = u.start_theta
|
||||
}
|
||||
},
|
||||
// Now computed as the sum: day_realized_pnl + day_unrealized_pnl
|
||||
// day_pnl: u.day_pnl,
|
||||
day: new() {
|
||||
price_size = new(
|
||||
u.day_long_pos,
|
||||
u.day_long_avg_cost,
|
||||
u.day_short_pos,
|
||||
-u.day_short_avg_cost),
|
||||
greeks = new() {
|
||||
delta = u.day_delta,
|
||||
gamma = u.day_gamma,
|
||||
vega = u.day_vega,
|
||||
theta = u.day_theta
|
||||
}
|
||||
},
|
||||
working: null,
|
||||
security: u.security,
|
||||
account_id: u.account_id,
|
||||
account_name: u.account,
|
||||
calc_type: PNLCalculationType.Mid,
|
||||
qty_conversion: null);
|
||||
|
||||
public PNLRowData with(
|
||||
SecurityImmutable? security = null,
|
||||
uint? account_id = null,
|
||||
string account_name = null,
|
||||
WorkingOrderData? working = null,
|
||||
PNLCalculationType? calc_type = null,
|
||||
double? bid_price = null,
|
||||
double? ask_price = null) =>
|
||||
new PNLRowData(
|
||||
key,
|
||||
default_cpp,
|
||||
override_cpp,
|
||||
pnl_price,
|
||||
bid_price ?? this.bid_price,
|
||||
ask_price ?? this.ask_price,
|
||||
last_price,
|
||||
settlement_price,
|
||||
sod,
|
||||
day,
|
||||
working ?? this.working,
|
||||
security ?? this.security,
|
||||
account_id ?? this.account_id,
|
||||
account_name ?? this.account_name,
|
||||
calc_type ?? this.calc_type,
|
||||
qty_conversion: qty_conversion);
|
||||
|
||||
public PNLRowData with(SecurityMD smd) {
|
||||
ulong time = 0;
|
||||
if (!smd.md.get_bbo(ref time, out var bid, out var ask)) {
|
||||
return this;
|
||||
}
|
||||
return with(security: smd.security, bid_price: bid.price, ask_price: ask.price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowData other) =>
|
||||
key == other.key &&
|
||||
default_cpp == other.default_cpp &&
|
||||
override_cpp == other.override_cpp &&
|
||||
security.HasValue == other.security.HasValue &&
|
||||
account_name == other.account_name &&
|
||||
sod == other.sod &&
|
||||
day == other.day &&
|
||||
bid_price.Equals(other.bid_price) &&
|
||||
ask_price.Equals(other.ask_price) &&
|
||||
last_price.Equals(other.last_price) &&
|
||||
mid_price.Equals(other.mid_price) &&
|
||||
settlement_price.Equals(other.settlement_price) &&
|
||||
calc_type.Equals(other.calc_type) &&
|
||||
working.summary == other.working.summary &&
|
||||
qty_conversion == other.qty_conversion;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is PNLRowData other && Equals(other);
|
||||
|
||||
// assuming callers will not combine different values with the same key,
|
||||
// the other components shouldn't be included in the hash calculation.
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public static bool operator ==(PNLRowData a, PNLRowData b) => a.Equals(b);
|
||||
public static bool operator !=(PNLRowData a, PNLRowData b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static class PNLDataExtensions {
|
||||
public static string pnl_symbol(this SecurityImmutable security) {
|
||||
return bts.api.ExchangeSpecificUtilities.get_pnl_symbol(security);
|
||||
}
|
||||
|
||||
// while override_cpp could use double.NaN to indicate the lack of an override,
|
||||
// the nullable double makes it clear that it is an optional value.
|
||||
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable? security) =>
|
||||
security.HasValue ?
|
||||
pnl_cpp_tuple(pnl_unit, security.Value) :
|
||||
(pnl_unit.CashValue, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
|
||||
|
||||
public static (double default_cpp, double? override_cpp) pnl_cpp_tuple(this PNLUnitImmutable pnl_unit, SecurityImmutable security) {
|
||||
// TODO: actual impl
|
||||
double calced_pnl = bts.api.ExchangeSpecificUtilities.get_cpp(pnl_unit, security);
|
||||
return (calced_pnl, pnl_unit.HasCppOverride ? pnl_unit.CppOverride.Value : (double?)null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// sod
|
||||
public decimal sod_pnl { get; init; }
|
||||
public decimal sod_realized_pnl { get; init; }
|
||||
public decimal sod_unrealized_pnl { get; init; }
|
||||
public decimal sod_long_pos { get; init; }
|
||||
public decimal sod_short_pos { get; init; }
|
||||
public decimal sod_net_pos => sod_long_pos - sod_short_pos;
|
||||
@@ -51,6 +53,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
public decimal day_pnl { get; init; }
|
||||
public decimal day_realized_pnl { get; init; }
|
||||
public decimal day_unrealized_pnl { get; init; }
|
||||
public decimal day_long_pos { get; init; }
|
||||
public decimal day_short_pos { get; init; }
|
||||
public decimal day_net_pos => day_long_pos - day_short_pos;
|
||||
@@ -64,6 +68,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
public decimal total_pnl => sod_pnl + day_pnl;
|
||||
public decimal total_realized_pnl => sod_realized_pnl + day_realized_pnl;
|
||||
public decimal total_unrealized_pnl => sod_unrealized_pnl + day_unrealized_pnl;
|
||||
public decimal total_net_pos => total_long_pos - total_short_pos;
|
||||
public decimal total_long_pos => sod_long_pos + day_long_pos;
|
||||
public decimal total_short_pos => sod_short_pos + day_short_pos;
|
||||
@@ -86,6 +92,8 @@ namespace bts.shell.pnl {
|
||||
public bool Equals(PNLNormalizedData other) =>
|
||||
// sod
|
||||
sod_pnl == other.sod_pnl &&
|
||||
sod_realized_pnl == other.sod_realized_pnl &&
|
||||
sod_unrealized_pnl == other.sod_unrealized_pnl &&
|
||||
sod_long_pos == other.sod_long_pos &&
|
||||
sod_short_pos == other.sod_short_pos &&
|
||||
sod_delta == other.sod_delta &&
|
||||
@@ -97,6 +105,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl == other.day_pnl &&
|
||||
day_realized_pnl == other.day_realized_pnl &&
|
||||
day_unrealized_pnl == other.day_unrealized_pnl &&
|
||||
day_long_pos == other.day_long_pos &&
|
||||
day_short_pos == other.day_short_pos &&
|
||||
day_delta == other.day_delta &&
|
||||
@@ -154,6 +164,8 @@ namespace bts.shell.pnl {
|
||||
new() {
|
||||
// sod
|
||||
sod_pnl = op(a.sod_pnl, b.sod_pnl),
|
||||
sod_realized_pnl = op(a.sod_realized_pnl, b.sod_realized_pnl),
|
||||
sod_unrealized_pnl = op(a.sod_unrealized_pnl, b.sod_unrealized_pnl),
|
||||
sod_long_pos = op(a.sod_long_pos, b.sod_long_pos),
|
||||
sod_short_pos = op(a.sod_short_pos, b.sod_short_pos),
|
||||
sod_delta = op(a.sod_delta, b.sod_delta),
|
||||
@@ -165,6 +177,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl = op(a.day_pnl, b.day_pnl),
|
||||
day_realized_pnl = op(a.day_realized_pnl, b.day_realized_pnl),
|
||||
day_unrealized_pnl = op(a.day_unrealized_pnl, b.day_unrealized_pnl),
|
||||
day_long_pos = op(a.day_long_pos, b.day_long_pos),
|
||||
day_short_pos = op(a.day_short_pos, b.day_short_pos),
|
||||
day_delta = op(a.day_delta, b.day_delta),
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace bts.shell {
|
||||
class PNLWindow : SparkWindow<PNLSaveState>, ISharedWindow {
|
||||
public const string WINDOW_TYPE = "PNL";
|
||||
public const string WINDOW_TITLE = "P&L Summary";
|
||||
// Cpp overrides aren't respected in pnl calc. Hide the "Override Cpp"
|
||||
// menu item until we fix it.
|
||||
const bool CAN_OVERRIDE_CPP = false;
|
||||
|
||||
static int _window_id_counter = 0;
|
||||
|
||||
@@ -137,6 +140,10 @@ namespace bts.shell {
|
||||
_cached_columns = ImmutableList<DIYGrid.ColumnBase>.Empty.AddRange(_view._grid.Columns);
|
||||
|
||||
_commands = new() {
|
||||
SparkCommands.ShowMarketView.complete(() => {
|
||||
var rows = _vm.selected_rows.null_if_empty() ?? _vm.rows;
|
||||
return SecurityCriteria.from_keys(rows.Select(r => r.security_key.value).Distinct());
|
||||
}),
|
||||
new SparkCommandCommand(
|
||||
"Collapse All", Icons.RightArrow, new Command(() => _vm.set_all_expanded(false))),
|
||||
new SparkCommandCommand(
|
||||
@@ -152,7 +159,7 @@ namespace bts.shell {
|
||||
},
|
||||
new SparkCommandCommand(
|
||||
"Override CPP...", Icons.CurrencyExchange, new Command(_override_cpp)) {
|
||||
show_in_context_menu = true,
|
||||
show_in_context_menu = CAN_OVERRIDE_CPP,
|
||||
show_in_toolbar = false
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(_mk_settings_win)),
|
||||
@@ -211,13 +218,16 @@ namespace bts.shell {
|
||||
.ToArray();
|
||||
var calc_type = _vm.calculation_type;
|
||||
var state = _settings_manager.instance_settings;
|
||||
var menu_display = _menu_display_vm?.create();
|
||||
if (state is null) {
|
||||
// don't use defaults if the user manipulated the window state outside of the
|
||||
// settings dialog (e.g., resized column(s) via drag, or changed sort/filter)
|
||||
var defaults = _settings_manager.default_settings;
|
||||
if (calc_type == defaults.calculation_type &&
|
||||
defaults.sorting_criteria.SequenceEqual(sorting_criteria) &&
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
defaults.grid.columns.SequenceEqual(cols) &&
|
||||
defaults.filters.safe_equals(filters) &&
|
||||
(!menu_display.HasValue || menu_display.Value.Equals(defaults.menu_display))) {
|
||||
// the user's current state still matches the defaults (i.e., no outside
|
||||
// manipulation was performed); return null so this window will use the latest
|
||||
// default settings when restored
|
||||
@@ -234,7 +244,7 @@ namespace bts.shell {
|
||||
state.grid.with(columns: cols, background: _vm.grid_background_color.to_uint()),
|
||||
calc_type,
|
||||
_vm.header_row_display,
|
||||
menu_display: _menu_display_vm?.create());
|
||||
menu_display: menu_display);
|
||||
return new PNLSaveState { use_defaults = false, settings = state };
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace bts.shell {
|
||||
|
||||
static bool _apply(ref PNLSettings settings, IEnumerable<PNLAnimationTriggerData> actual_triggers, IEnumerable<PNLAnimationTriggerData> default_triggers, Func<PNLSettings, PNLAnimationTriggerData[], PNLSettings> set_triggers) {
|
||||
if (_apply(actual_triggers, default_triggers, out var result)) {
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
settings = set_triggers(settings, result.ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
@@ -33,8 +35,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// expiration
|
||||
[ProtoMember(53)]
|
||||
DateTime[] _expiration;
|
||||
public DateTime[] expiration {
|
||||
ExpirationDate[] _expiration;
|
||||
public ExpirationDate[] expiration {
|
||||
get => _expiration.empty_if_null();
|
||||
set => _expiration = value;
|
||||
}
|
||||
@@ -65,16 +67,24 @@ namespace bts.shell.pnl {
|
||||
/// sod
|
||||
[ProtoMember(8)]
|
||||
public DoubleMinMaxState sod_pnl { get; set; }
|
||||
[ProtoMember(54)]
|
||||
public DoubleMinMaxState sod_realized_pnl { get; set; }
|
||||
[ProtoMember(55)]
|
||||
public DoubleMinMaxState sod_unrealized_pnl { get; set; }
|
||||
// sod long
|
||||
[ProtoMember(9)]
|
||||
public DoubleMinMaxState sod_long_pos { get; set; }
|
||||
[ProtoMember(10)]
|
||||
public DoubleMinMaxState sod_long_avg_price { get; set; }
|
||||
[ProtoMember(60)]
|
||||
public DoubleMinMaxState sod_long_avg_pnl { get; set; }
|
||||
// sod short
|
||||
[ProtoMember(11)]
|
||||
public DoubleMinMaxState sod_short_pos { get; set; }
|
||||
[ProtoMember(12)]
|
||||
public DoubleMinMaxState sod_short_avg_price { get; set; }
|
||||
[ProtoMember(61)]
|
||||
public DoubleMinMaxState sod_short_avg_pnl { get; set; }
|
||||
// sod net
|
||||
[ProtoMember(13)]
|
||||
public DoubleMinMaxState sod_net_pos { get; set; }
|
||||
@@ -92,16 +102,24 @@ namespace bts.shell.pnl {
|
||||
/// day
|
||||
[ProtoMember(14)]
|
||||
public DoubleMinMaxState day_pnl { get; set; }
|
||||
[ProtoMember(56)]
|
||||
public DoubleMinMaxState day_realized_pnl { get; set; }
|
||||
[ProtoMember(57)]
|
||||
public DoubleMinMaxState day_unrealized_pnl { get; set; }
|
||||
// day long
|
||||
[ProtoMember(15)]
|
||||
public DoubleMinMaxState day_long_pos { get; set; }
|
||||
[ProtoMember(16)]
|
||||
public DoubleMinMaxState day_long_avg_price { get; set; }
|
||||
[ProtoMember(62)]
|
||||
public DoubleMinMaxState day_long_avg_pnl { get; set; }
|
||||
// day short
|
||||
[ProtoMember(17)]
|
||||
public DoubleMinMaxState day_short_pos { get; set; }
|
||||
[ProtoMember(18)]
|
||||
public DoubleMinMaxState day_short_avg_price { get; set; }
|
||||
[ProtoMember(63)]
|
||||
public DoubleMinMaxState day_short_avg_pnl { get; set; }
|
||||
// day net
|
||||
[ProtoMember(19)]
|
||||
public DoubleMinMaxState day_net_pos { get; set; }
|
||||
@@ -149,16 +167,24 @@ namespace bts.shell.pnl {
|
||||
/// total
|
||||
[ProtoMember(29)]
|
||||
public DoubleMinMaxState total_pnl { get; set; }
|
||||
[ProtoMember(58)]
|
||||
public DoubleMinMaxState total_realized_pnl { get; set; }
|
||||
[ProtoMember(59)]
|
||||
public DoubleMinMaxState total_unrealized_pnl { get; set; }
|
||||
// total long
|
||||
[ProtoMember(30)]
|
||||
public DoubleMinMaxState total_long_pos { get; set; }
|
||||
[ProtoMember(31)]
|
||||
public DoubleMinMaxState total_long_avg_price { get; set; }
|
||||
[ProtoMember(64)]
|
||||
public DoubleMinMaxState total_long_avg_pnl { get; set; }
|
||||
// total short
|
||||
[ProtoMember(32)]
|
||||
public DoubleMinMaxState total_short_pos { get; set; }
|
||||
[ProtoMember(33)]
|
||||
public DoubleMinMaxState total_short_avg_price { get; set; }
|
||||
[ProtoMember(65)]
|
||||
public DoubleMinMaxState total_short_avg_pnl { get; set; }
|
||||
// total net
|
||||
[ProtoMember(34)]
|
||||
public DoubleMinMaxState total_net_pos { get; set; }
|
||||
@@ -199,6 +225,8 @@ namespace bts.shell.pnl {
|
||||
cpp.safe_equals(other.cpp) &&
|
||||
/// sod
|
||||
sod_pnl.safe_equals(other.sod_pnl) &&
|
||||
sod_realized_pnl.safe_equals(other.sod_realized_pnl) &&
|
||||
sod_unrealized_pnl.safe_equals(other.sod_unrealized_pnl) &&
|
||||
sod_long_pos.safe_equals(other.sod_long_pos) &&
|
||||
sod_long_avg_price.safe_equals(other.sod_long_avg_price) &&
|
||||
sod_short_pos.safe_equals(other.sod_short_pos) &&
|
||||
@@ -210,6 +238,8 @@ namespace bts.shell.pnl {
|
||||
sod_theta.safe_equals(other.sod_theta) &&
|
||||
/// day
|
||||
day_pnl.safe_equals(other.day_pnl) &&
|
||||
day_realized_pnl.safe_equals(other.day_realized_pnl) &&
|
||||
day_unrealized_pnl.safe_equals(other.day_unrealized_pnl) &&
|
||||
day_long_pos.safe_equals(other.day_long_pos) &&
|
||||
day_long_avg_price.safe_equals(other.day_long_avg_price) &&
|
||||
day_short_pos.safe_equals(other.day_short_pos) &&
|
||||
@@ -232,6 +262,8 @@ namespace bts.shell.pnl {
|
||||
working_net_pos.safe_equals(other.working_net_pos) &&
|
||||
/// total
|
||||
total_pnl.safe_equals(other.total_pnl) &&
|
||||
total_realized_pnl.safe_equals(other.total_realized_pnl) &&
|
||||
total_unrealized_pnl.safe_equals(other.total_unrealized_pnl) &&
|
||||
total_long_pos.safe_equals(other.total_long_pos) &&
|
||||
total_long_avg_price.safe_equals(other.total_long_avg_price) &&
|
||||
total_short_pos.safe_equals(other.total_short_pos) &&
|
||||
@@ -270,6 +302,8 @@ namespace bts.shell.pnl {
|
||||
bd.add_numeric(fs.cpp, d => d.effective_cpp);
|
||||
/// sod
|
||||
bd.add_numeric(fs.sod_pnl, d => d.sod_pnl);
|
||||
bd.add_numeric(fs.sod_realized_pnl, d => d.sod_realized_pnl);
|
||||
bd.add_numeric(fs.sod_unrealized_pnl, d => d.sod_unrealized_pnl);
|
||||
bd.add_numeric(fs.sod_long_pos, d => d.sod_long_pos);
|
||||
bd.add_numeric(fs.sod_long_avg_price, d => d.sod_long_avg_price);
|
||||
bd.add_numeric(fs.sod_short_pos, d => d.sod_short_pos);
|
||||
@@ -281,6 +315,8 @@ namespace bts.shell.pnl {
|
||||
bd.add_numeric(fs.sod_theta, d => d.sod_theta);
|
||||
/// day
|
||||
bd.add_numeric(fs.day_pnl, d => d.day_pnl);
|
||||
bd.add_numeric(fs.day_realized_pnl, d => d.day_realized_pnl);
|
||||
bd.add_numeric(fs.day_unrealized_pnl, d => d.day_unrealized_pnl);
|
||||
bd.add_numeric(fs.day_long_pos, d => d.day_long_pos);
|
||||
bd.add_numeric(fs.day_long_avg_price, d => d.day_long_avg_price);
|
||||
bd.add_numeric(fs.day_short_pos, d => d.day_short_pos);
|
||||
@@ -299,6 +335,8 @@ namespace bts.shell.pnl {
|
||||
bd.add_numeric(fs.working_net_pos, d => d.working_net_pos);
|
||||
/// total
|
||||
bd.add_numeric(fs.total_pnl, d => d.total_pnl);
|
||||
bd.add_numeric(fs.total_realized_pnl, d => d.total_realized_pnl);
|
||||
bd.add_numeric(fs.total_unrealized_pnl, d => d.total_unrealized_pnl);
|
||||
bd.add_numeric(fs.total_long_pos, d => d.total_long_pos);
|
||||
bd.add_numeric(fs.total_long_avg_price, d => d.total_long_avg_price);
|
||||
bd.add_numeric(fs.total_short_pos, d => d.total_short_pos);
|
||||
|
||||
@@ -9,9 +9,13 @@ using mbts.user.settings;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Core;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -110,7 +114,22 @@ namespace bts.shell.pnl {
|
||||
GridSettings _grid_backing;
|
||||
PNLCellAppearanceProto _grid_cell_appearance_backing;
|
||||
|
||||
public static PNLSettings Default =>
|
||||
static LazyOnce<PNLSettings> defaultProfile;
|
||||
|
||||
static PNLSettings() {
|
||||
defaultProfile = new(() => {
|
||||
var binDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
var store = ShellWindowSettingsDirectoryStore.Create<PNLSettings>(
|
||||
Path.Combine(binDir, "ShellWindowSettings", "Pnl"), "*." + mbts.spark.UserSettings.FileExtensions.PNL);
|
||||
return store.Profiles.GetValueOrDefault("Default")
|
||||
?? store.Profiles.Values.FirstOrDefault()
|
||||
?? historicDefault;
|
||||
});
|
||||
}
|
||||
|
||||
public static PNLSettings Default => defaultProfile.Value;
|
||||
|
||||
static PNLSettings historicDefault =>
|
||||
new PNLSettings {
|
||||
_sorting_criteria = new[] {
|
||||
new SortingCriteria(nameof(Columns.Account), true),
|
||||
@@ -192,6 +211,10 @@ namespace bts.shell.pnl {
|
||||
yield return _trigger($"{nameof(PNLRowData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, font_weight: FontWeights.Bold, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, font_weight: FontWeights.Bold, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, font_weight: FontWeights.Bold, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, font_weight: FontWeights.Bold, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLRowData.total_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, font_weight: FontWeights.Bold, column: Columns.TotalUnrealizedPNL));
|
||||
// working
|
||||
yield return _trigger($"{nameof(PNLRowData.working_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.WorkingNetPos));
|
||||
yield return _trigger($"{nameof(PNLRowData.working_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.WorkingNetPos));
|
||||
@@ -202,16 +225,28 @@ namespace bts.shell.pnl {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.DayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.TotalNetPos));
|
||||
// working
|
||||
@@ -224,16 +259,28 @@ namespace bts.shell.pnl {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.DayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.TotalNetPos));
|
||||
// working
|
||||
@@ -392,10 +439,14 @@ namespace bts.shell.pnl {
|
||||
public static GridColumnSettings cpp => new GridColumnSettings(nameof(Columns.CPP), "CPP", false, width: 50, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// sod
|
||||
public static GridColumnSettings sod_pnl => new GridColumnSettings(nameof(Columns.SODPNL), Headers.SOD.PNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_realized_pnl => new GridColumnSettings(nameof(Columns.SODRealizedPNL), Headers.SOD.RealizedPNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_unrealized_pnl => new GridColumnSettings(nameof(Columns.SODUnrealizedPNL), Headers.SOD.UnrealizedPNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_long_pos => new GridColumnSettings(nameof(Columns.SODLongPos), Headers.SOD.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_long_avg_price => new GridColumnSettings(nameof(Columns.SODLongAvgPrice), Headers.SOD.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_long_avg_pnl => new(nameof(Columns.SODLongAvgPNL), Headers.SOD.LongAvgPNL, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_short_pos => new GridColumnSettings(nameof(Columns.SODShortPos), Headers.SOD.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_short_avg_price => new GridColumnSettings(nameof(Columns.SODShortAvgPrice), Headers.SOD.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_short_avg_pnl => new(nameof(Columns.SODShortAvgPNL), Headers.SOD.ShortAvgPNL, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings sod_net_pos => new GridColumnSettings(nameof(Columns.SODNetPos), Headers.SOD.Net, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings sod_delta => new(
|
||||
@@ -428,10 +479,14 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
public static GridColumnSettings day_pnl => new GridColumnSettings(nameof(Columns.DayPNL), Headers.Day.PNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_realized_pnl => new GridColumnSettings(nameof(Columns.DayRealizedPNL), Headers.Day.RealizedPNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_unrealized_pnl => new GridColumnSettings(nameof(Columns.DayUnrealizedPNL), Headers.Day.UnrealizedPNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_pos => new GridColumnSettings(nameof(Columns.DayLongPos), Headers.Day.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_avg_price => new GridColumnSettings(nameof(Columns.DayLongAvgPrice), Headers.Day.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_long_avg_pnl => new GridColumnSettings(nameof(Columns.DayLongAvgPNL), Headers.Day.LongAvgPNL, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_pos => new GridColumnSettings(nameof(Columns.DayShortPos), Headers.Day.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_avg_price => new GridColumnSettings(nameof(Columns.DayShortAvgPrice), Headers.Day.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_short_avg_pnl => new GridColumnSettings(nameof(Columns.DayShortAvgPNL), Headers.Day.ShortAvgPNL, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings day_net_pos => new GridColumnSettings(nameof(Columns.DayNetPos), Headers.Day.Net, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings day_delta => new(
|
||||
@@ -471,10 +526,14 @@ namespace bts.shell.pnl {
|
||||
public static GridColumnSettings working_net_pos => new GridColumnSettings(nameof(Columns.WorkingNetPos), Headers.Working.Net, width: Widths.WorkingQty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
// total
|
||||
public static GridColumnSettings total_pnl => new GridColumnSettings(nameof(Columns.TotalPNL), Headers.Total.PNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_realized_pnl => new GridColumnSettings(nameof(Columns.TotalRealizedPNL), Headers.Total.RealizedPNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_unrealized_pnl => new GridColumnSettings(nameof(Columns.TotalUnrealizedPNL), Headers.Total.UnrealizedPNL, width: Widths.PriceBig, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_long_pos => new GridColumnSettings(nameof(Columns.TotalLongPos), Headers.Total.Long, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_long_avg_price => new GridColumnSettings(nameof(Columns.TotalLongAvgPrice), Headers.Total.LongAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_long_avg_pnl => new(nameof(Columns.TotalLongAvgPNL), Headers.Total.LongAvgPNL, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_short_pos => new GridColumnSettings(nameof(Columns.TotalShortPos), Headers.Total.Short, false, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_short_avg_price => new GridColumnSettings(nameof(Columns.TotalShortAvgPrice), Headers.Total.ShortAvg, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_short_avg_pnl => new(nameof(Columns.TotalShortAvgPNL), Headers.Total.ShortAvgPNL, false, width: Widths.Price, horizontal_alignment: HorizontalAlignment.Right);
|
||||
public static GridColumnSettings total_net_pos => new GridColumnSettings(nameof(Columns.TotalNetPos), Headers.Total.Net, width: Widths.Qty, horizontal_alignment: HorizontalAlignment.Right);
|
||||
|
||||
public static GridColumnSettings total_delta => new(
|
||||
@@ -528,28 +587,40 @@ namespace bts.shell.pnl {
|
||||
|
||||
// pnl
|
||||
yield return sod_pnl;
|
||||
yield return sod_realized_pnl;
|
||||
yield return sod_unrealized_pnl;
|
||||
yield return day_pnl;
|
||||
yield return day_realized_pnl;
|
||||
yield return day_unrealized_pnl;
|
||||
yield return total_pnl;
|
||||
yield return total_realized_pnl;
|
||||
yield return total_unrealized_pnl;
|
||||
|
||||
// sod pos, price
|
||||
yield return sod_long_pos; // hidden
|
||||
yield return sod_long_avg_price; // hidden
|
||||
yield return sod_long_avg_pnl; // hidden
|
||||
yield return sod_short_pos; // hidden
|
||||
yield return sod_short_avg_price; // hidden
|
||||
yield return sod_short_avg_pnl; // hidden
|
||||
yield return sod_net_pos;
|
||||
|
||||
// day pos, price
|
||||
yield return day_long_pos; // hidden
|
||||
yield return day_long_avg_price; // hidden
|
||||
yield return day_long_avg_pnl; // hidden
|
||||
yield return day_short_pos; // hidden
|
||||
yield return day_short_avg_price; // hidden
|
||||
yield return day_short_avg_pnl; // hidden
|
||||
yield return day_net_pos;
|
||||
|
||||
// total pos, price
|
||||
yield return total_long_pos; // hidden
|
||||
yield return total_long_avg_price; // hidden
|
||||
yield return total_long_avg_pnl; // hidden
|
||||
yield return total_short_pos; // hidden
|
||||
yield return total_short_avg_price; // hidden
|
||||
yield return total_short_avg_pnl; // hidden
|
||||
yield return total_net_pos;
|
||||
|
||||
// sod greeks
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace bts.shell.pnl {
|
||||
_cell_map.set_appearances(default_appearances);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_realized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODRealizedPNL, d => d.sod_realized_pnl);
|
||||
sod_unrealized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODUnrealizedPNL, d => d.sod_unrealized_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
@@ -44,6 +46,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_realized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.DayRealizedPNL, d => d.day_realized_pnl);
|
||||
day_unrealized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.DayUnrealizedPNL, d => d.day_unrealized_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
@@ -58,6 +62,8 @@ namespace bts.shell.pnl {
|
||||
working_short_pos = _cell_map.add_short_pos(ColumnKeys.WorkingShortPos, r => r.working_short_pos);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_realized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalRealizedPNL, d => d.total_realized_pnl);
|
||||
total_unrealized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalUnrealizedPNL, d => d.total_unrealized_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
@@ -88,6 +94,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
|
||||
@@ -98,6 +106,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// sod
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
|
||||
@@ -108,6 +118,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
|
||||
|
||||
@@ -1,378 +1,407 @@
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
api.domain.TickDisplaySettingsImmutable? _quantity_display_backing;
|
||||
|
||||
public PNLRowVM(
|
||||
PNLRowData data,
|
||||
ISecurityMetadataStore sec_meta_store,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
|
||||
Func<double, string> fmt_fill_price,
|
||||
Func<double, string> fmt_market_price) {
|
||||
//
|
||||
this.data = data;
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_cell = _cell_map.add(ColumnKeys.Account, d => d.account);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => SecurityFormatter.format_expiration(e));
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
d => d.security,
|
||||
null,
|
||||
EqualityComparer<SecurityImmutable?>.Default,
|
||||
Comparer<SecurityImmutable?>.Default);
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.SODNetPos, d => d.display_sod_net_pos, fmt_qty);
|
||||
sod_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.SODLongPos, d => d.display_sod_long_pos, fmt_qty);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price);
|
||||
sod_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.SODShortPos, d => d.display_sod_short_pos, fmt_qty);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price);
|
||||
sod_delta = _cell_map.add_pnl_double(ColumnKeys.SODDelta, d => d.sod_delta, fmt_greek);
|
||||
sod_gamma = _cell_map.add_pnl_double(ColumnKeys.SODGamma, d => d.sod_gamma, fmt_greek);
|
||||
sod_vega = _cell_map.add_pnl_double(ColumnKeys.SODVega, d => d.sod_vega, fmt_greek);
|
||||
sod_theta = _cell_map.add_pnl_double(ColumnKeys.SODTheta, d => d.sod_theta, fmt_greek);
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.DayNetPos, d => d.display_day_net_pos, fmt_qty);
|
||||
day_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.DayLongPos, d => d.display_day_long_pos, fmt_qty);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price);
|
||||
day_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.DayShortPos, d => d.display_day_short_pos, fmt_qty);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price);
|
||||
day_delta = _cell_map.add_pnl_double(ColumnKeys.DayDelta, d => d.day_delta, fmt_greek);
|
||||
day_gamma = _cell_map.add_pnl_double(ColumnKeys.DayGamma, d => d.day_gamma, fmt_greek);
|
||||
day_vega = _cell_map.add_pnl_double(ColumnKeys.DayVega, d => d.day_vega, fmt_greek);
|
||||
day_theta = _cell_map.add_pnl_double(ColumnKeys.DayTheta, d => d.day_theta, fmt_greek);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.WorkingNetPos, d => d.display_working_net_pos, fmt_qty);
|
||||
var qty_display = _sec_meta_store.get_qty_display(security);
|
||||
working_long_pos = _cell_map.add_working_long(
|
||||
ColumnKeys.WorkingLongPos,
|
||||
d => d.working.long_data,
|
||||
d => d.bid_price,
|
||||
qty_display);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price);
|
||||
working_short_pos = _cell_map.add_working_short(
|
||||
ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price, qty_display);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.TotalNetPos, d => d.display_total_net_pos, fmt_qty);
|
||||
total_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.TotalLongPos, d => d.display_total_long_pos, fmt_qty);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price);
|
||||
total_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.TotalShortPos, d => d.display_total_short_pos, fmt_qty);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price);
|
||||
total_delta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalDelta, d => d.total_delta, fmt_greek);
|
||||
total_gamma = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalGamma, d => d.total_gamma, fmt_greek);
|
||||
total_vega = _cell_map.add_pnl_double(ColumnKeys.TotalVega, d => d.total_vega, fmt_greek);
|
||||
total_theta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalTheta, d => d.total_theta, fmt_greek);
|
||||
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
|
||||
static string fmt_greek(double v) =>
|
||||
v.is_nan() ? "-" : v.ToString("#,0.00");
|
||||
|
||||
string fmt_qty(double v) =>
|
||||
quantity_display.format(v);
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// Only count outrights (spread positions should be normalized into outrights)
|
||||
var sec_type = security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
var (
|
||||
sod_long,
|
||||
sod_short,
|
||||
sod_delta,
|
||||
sod_gamma,
|
||||
sod_vega,
|
||||
sod_theta,
|
||||
day_long,
|
||||
day_short,
|
||||
day_delta,
|
||||
day_gamma,
|
||||
day_vega,
|
||||
day_theta) = sec_type switch {
|
||||
Security.ContractOneofCase.Future or Security.ContractOneofCase.Option => (
|
||||
data.sod.long_pos.round_to_decimal(),
|
||||
data.sod.short_pos.round_to_decimal(),
|
||||
data.sod.delta.round_to_decimal(),
|
||||
data.sod.gamma.round_to_decimal(),
|
||||
data.sod.vega.round_to_decimal(),
|
||||
data.sod.theta.round_to_decimal(),
|
||||
data.day.long_pos.round_to_decimal(),
|
||||
data.day.short_pos.round_to_decimal(),
|
||||
data.day.delta.round_to_decimal(),
|
||||
data.day.gamma.round_to_decimal(),
|
||||
data.day.vega.round_to_decimal(),
|
||||
data.day.theta.round_to_decimal()),
|
||||
_ => (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var working_long = data.working_long_pos.round_to_decimal();
|
||||
var working_short = data.working_short_pos.round_to_decimal();
|
||||
|
||||
var nd = new PNLNormalizedData() {
|
||||
// sod
|
||||
sod_pnl = sod_pnl.value.round_to_decimal(),
|
||||
sod_long_pos = sod_long,
|
||||
sod_short_pos = sod_short,
|
||||
sod_delta = sod_delta,
|
||||
sod_gamma = sod_gamma,
|
||||
sod_vega = sod_vega,
|
||||
sod_theta = sod_theta,
|
||||
display_sod_long_pos = sod_long,
|
||||
display_sod_short_pos = sod_short,
|
||||
|
||||
// day
|
||||
day_pnl = day_pnl.value.round_to_decimal(),
|
||||
day_long_pos = day_long,
|
||||
day_short_pos = day_short,
|
||||
day_delta = day_delta,
|
||||
day_gamma = day_gamma,
|
||||
day_vega = day_vega,
|
||||
day_theta = day_theta,
|
||||
display_day_long_pos = day_long,
|
||||
display_day_short_pos = day_short,
|
||||
|
||||
// working
|
||||
working_long_pos = working_long,
|
||||
working_short_pos = working_short,
|
||||
display_working_long_pos = working_long,
|
||||
display_working_short_pos = working_short
|
||||
};
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public DateTime expiration => data.expiration;
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, AccountTuple> account_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, DateTime> expiration_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_theta { get; }
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_theta { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_theta { get; }
|
||||
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
// display
|
||||
public api.domain.TickDisplaySettingsImmutable quantity_display {
|
||||
get {
|
||||
if (_quantity_display_backing.HasValue) {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
_quantity_display_backing = _sec_meta_store.get_qty_display(security.Value);
|
||||
}
|
||||
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
|
||||
get {
|
||||
yield return pnl_price;
|
||||
yield return last_price;
|
||||
yield return bid_price;
|
||||
yield return mid_price;
|
||||
yield return ask_price;
|
||||
yield return settlement_price;
|
||||
}
|
||||
}
|
||||
|
||||
// includes sod, day, total, and working avg price columns
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
|
||||
get {
|
||||
// sod
|
||||
yield return sod_long_avg_price;
|
||||
yield return sod_short_avg_price;
|
||||
// day
|
||||
yield return day_long_avg_price;
|
||||
yield return day_short_avg_price;
|
||||
// total
|
||||
yield return total_long_avg_price;
|
||||
yield return total_short_avg_price;
|
||||
// working
|
||||
yield return working_long_avg_price;
|
||||
yield return working_short_avg_price;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_quantity_display_backing = null;
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
|
||||
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
foreach (var cell in _fill_price_cells) {
|
||||
cell.format = fmt_fill_price;
|
||||
}
|
||||
foreach (var cell in _market_price_cells) {
|
||||
cell.format = fmt_market_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
using ColumnKeys = bts.shell.pnl.PNLView.Columns;
|
||||
using PNLKey = bts.AccountSecurityKey;
|
||||
|
||||
namespace bts.shell.pnl {
|
||||
class PNLRowVM : DisposableViewModelBase, IEquatable<PNLRowVM> {
|
||||
public static readonly IComparer<PNLRowVM> key_comparer = Comparer<PNLKey>.Default.wrap<PNLKey, PNLRowVM>(r => r.key);
|
||||
static readonly Func<bool, string> _fmt_has_day_trades = hdt => hdt ? "Yes" : "No";
|
||||
//
|
||||
readonly PNLCellIndexBuilder<PNLRowData> _cell_map = new PNLCellIndexBuilder<PNLRowData>();
|
||||
readonly PNLCellEmbeddedAnimationModel<PNLRowData> _animator;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
bool _refresh_normalized_data = true, _is_selected;
|
||||
PNLNormalizedData _normalized_data;
|
||||
api.domain.TickDisplaySettingsImmutable? _quantity_display_backing;
|
||||
|
||||
public PNLRowVM(
|
||||
PNLRowData data,
|
||||
ISecurityMetadataStore sec_meta_store,
|
||||
ImmutableDictionary<ColumnKeys, PNLCellAppearance> default_appearances,
|
||||
IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers,
|
||||
Func<double, string> fmt_fill_price,
|
||||
Func<double, string> fmt_market_price) {
|
||||
//
|
||||
this.data = data;
|
||||
_sec_meta_store = sec_meta_store;
|
||||
_animator = new PNLCellEmbeddedAnimationModel<PNLRowData>(default_appearances, triggers);
|
||||
_animator.on_row(data);
|
||||
pnl_price = _cell_map.add_price_double(ColumnKeys.PNLPrice, d => d.pnl_price);
|
||||
account_cell = _cell_map.add(ColumnKeys.Account, d => d.account);
|
||||
security_type_cell = _cell_map.add(ColumnKeys.SecurityType, d => d.security?.ContractCase ?? Security.ContractOneofCase.None);
|
||||
exchange_cell = _cell_map.add(ColumnKeys.Exchange, d => d.security?.Exchange ?? Venue.Unset);
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => e.ToString());
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
d => d.security,
|
||||
null,
|
||||
EqualityComparer<SecurityImmutable?>.Default,
|
||||
Comparer<SecurityImmutable?>.Default);
|
||||
security_key = _cell_map.add(ColumnKeys.SecurityKey, d => d.key.security_id);
|
||||
cpp = _cell_map.add(ColumnKeys.CPP, d => d.effective_cpp);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_double(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_realized_pnl = _cell_map.add_pnl_double(ColumnKeys.SODRealizedPNL, d => d.sod_realized_pnl);
|
||||
sod_unrealized_pnl = _cell_map.add_pnl_double(ColumnKeys.SODUnrealizedPNL, d => d.sod_unrealized_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.SODNetPos, d => d.display_sod_net_pos, fmt_qty);
|
||||
sod_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.SODLongPos, d => d.display_sod_long_pos, fmt_qty);
|
||||
sod_long_avg_price = _cell_map.add_long_price(ColumnKeys.SODLongAvgPrice, d => d.sod_long_avg_price);
|
||||
sod_long_avg_pnl = _cell_map.add_long_price(ColumnKeys.SODLongAvgPNL, d => d.sod_long_avg_pnl);
|
||||
sod_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.SODShortPos, d => d.display_sod_short_pos, fmt_qty);
|
||||
sod_short_avg_price = _cell_map.add_short_price(ColumnKeys.SODShortAvgPrice, d => d.sod_short_avg_price);
|
||||
sod_short_avg_pnl = _cell_map.add_short_price(ColumnKeys.SODShortAvgPNL, d => d.sod_short_avg_pnl);
|
||||
sod_delta = _cell_map.add_pnl_double(ColumnKeys.SODDelta, d => d.sod_delta, fmt_greek);
|
||||
sod_gamma = _cell_map.add_pnl_double(ColumnKeys.SODGamma, d => d.sod_gamma, fmt_greek);
|
||||
sod_vega = _cell_map.add_pnl_double(ColumnKeys.SODVega, d => d.sod_vega, fmt_greek);
|
||||
sod_theta = _cell_map.add_pnl_double(ColumnKeys.SODTheta, d => d.sod_theta, fmt_greek);
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl_double(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_realized_pnl = _cell_map.add_pnl_double(ColumnKeys.DayRealizedPNL, d => d.day_realized_pnl);
|
||||
day_unrealized_pnl = _cell_map.add_pnl_double(ColumnKeys.DayUnrealizedPNL, d => d.day_unrealized_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.DayNetPos, d => d.display_day_net_pos, fmt_qty);
|
||||
day_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.DayLongPos, d => d.display_day_long_pos, fmt_qty);
|
||||
day_long_avg_price = _cell_map.add_long_price(ColumnKeys.DayLongAvgPrice, d => d.day_long_avg_price);
|
||||
day_long_avg_pnl = _cell_map.add_long_price(ColumnKeys.DayLongAvgPNL, d => d.day_long_avg_pnl);
|
||||
day_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.DayShortPos, d => d.display_day_short_pos, fmt_qty);
|
||||
day_short_avg_price = _cell_map.add_short_price(ColumnKeys.DayShortAvgPrice, d => d.day_short_avg_price);
|
||||
day_short_avg_pnl = _cell_map.add_short_price(ColumnKeys.DayShortAvgPNL, d => d.day_short_avg_pnl);
|
||||
day_delta = _cell_map.add_pnl_double(ColumnKeys.DayDelta, d => d.day_delta, fmt_greek);
|
||||
day_gamma = _cell_map.add_pnl_double(ColumnKeys.DayGamma, d => d.day_gamma, fmt_greek);
|
||||
day_vega = _cell_map.add_pnl_double(ColumnKeys.DayVega, d => d.day_vega, fmt_greek);
|
||||
day_theta = _cell_map.add_pnl_double(ColumnKeys.DayTheta, d => d.day_theta, fmt_greek);
|
||||
has_day_trades = _cell_map.add(ColumnKeys.HasDayTrades, d => d.has_day_trades, _fmt_has_day_trades);
|
||||
// working
|
||||
working_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.WorkingNetPos, d => d.display_working_net_pos, fmt_qty);
|
||||
var qty_display = _sec_meta_store.get_qty_display(security);
|
||||
working_long_pos = _cell_map.add_working_long(
|
||||
ColumnKeys.WorkingLongPos,
|
||||
d => d.working.long_data,
|
||||
d => d.bid_price,
|
||||
qty_display);
|
||||
working_long_avg_price = _cell_map.add_long_price(ColumnKeys.WorkingLongAvgPrice, d => d.working_long_avg_price);
|
||||
working_short_pos = _cell_map.add_working_short(
|
||||
ColumnKeys.WorkingShortPos, r => r.working.short_data, r => r.ask_price, qty_display);
|
||||
working_short_avg_price = _cell_map.add_short_price(ColumnKeys.WorkingShortAvgPrice, d => d.working_short_avg_price);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_realized_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalRealizedPNL, d => d.total_realized_pnl);
|
||||
total_unrealized_pnl = _cell_map.add_pnl_double(ColumnKeys.TotalUnrealizedPNL, d => d.total_unrealized_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(
|
||||
ColumnKeys.TotalNetPos, d => d.display_total_net_pos, fmt_qty);
|
||||
total_long_pos = _cell_map.add_long_pos(
|
||||
ColumnKeys.TotalLongPos, d => d.display_total_long_pos, fmt_qty);
|
||||
total_long_avg_price = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPrice, d => d.total_long_avg_price);
|
||||
total_long_avg_pnl = _cell_map.add_long_price(ColumnKeys.TotalLongAvgPNL, d => d.total_long_avg_pnl);
|
||||
total_short_pos = _cell_map.add_short_pos(
|
||||
ColumnKeys.TotalShortPos, d => d.display_total_short_pos, fmt_qty);
|
||||
total_short_avg_price = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPrice, d => d.total_short_avg_price);
|
||||
total_short_avg_pnl = _cell_map.add_short_price(ColumnKeys.TotalShortAvgPNL, d => d.total_short_avg_pnl);
|
||||
total_delta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalDelta, d => d.total_delta, fmt_greek);
|
||||
total_gamma = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalGamma, d => d.total_gamma, fmt_greek);
|
||||
total_vega = _cell_map.add_pnl_double(ColumnKeys.TotalVega, d => d.total_vega, fmt_greek);
|
||||
total_theta = _cell_map.add_pnl_double(
|
||||
ColumnKeys.TotalTheta, d => d.total_theta, fmt_greek);
|
||||
|
||||
// market
|
||||
bid_price = _cell_map.add_price_double(ColumnKeys.BidPrice, d => d.bid_price);
|
||||
ask_price = _cell_map.add_price_double(ColumnKeys.AskPrice, d => d.ask_price);
|
||||
mid_price = _cell_map.add_price_double(ColumnKeys.MidPrice, d => d.mid_price);
|
||||
last_price = _cell_map.add_price_double(ColumnKeys.LastPrice, d => d.last_price);
|
||||
settlement_price = _cell_map.add_price_double(ColumnKeys.SettlementPrice, d => d.settlement_price);
|
||||
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
_cell_map.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_dispose = _cell_map.animation_model.OnDispatcher().Subscribe(new_animations => {
|
||||
animations = new_animations.Select(kv => new mbts.ui.spark.CellAnimation(kv.Key.column, kv.Key.type, kv.Value.color.to_brush())).ToList();
|
||||
RaisePropertyChanged(nameof(animations));
|
||||
});
|
||||
|
||||
static string fmt_greek(double v) =>
|
||||
v.is_nan() ? "-" : v.ToString("#,0.00");
|
||||
|
||||
string fmt_qty(double v) =>
|
||||
quantity_display.format(v);
|
||||
}
|
||||
|
||||
// events
|
||||
public event Action<PNLNormalizedData> updated;
|
||||
public event Action<bool> is_selected_changed;
|
||||
|
||||
// data
|
||||
public PNLKey key => data.key;
|
||||
public PNLRowData data { get; private set; }
|
||||
public PNLNormalizedData normalized_data {
|
||||
get {
|
||||
if (_refresh_normalized_data) {
|
||||
_refresh_normalized_data = false;
|
||||
// Only count outrights (spread positions should be normalized into outrights)
|
||||
var sec_type = security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
var (
|
||||
sod_long,
|
||||
sod_short,
|
||||
sod_delta,
|
||||
sod_gamma,
|
||||
sod_vega,
|
||||
sod_theta,
|
||||
day_long,
|
||||
day_short,
|
||||
day_delta,
|
||||
day_gamma,
|
||||
day_vega,
|
||||
day_theta) = sec_type switch {
|
||||
Security.ContractOneofCase.Future or Security.ContractOneofCase.Option => (
|
||||
data.sod.long_pos.round_to_decimal(),
|
||||
data.sod.short_pos.round_to_decimal(),
|
||||
data.sod.delta.round_to_decimal(),
|
||||
data.sod.gamma.round_to_decimal(),
|
||||
data.sod.vega.round_to_decimal(),
|
||||
data.sod.theta.round_to_decimal(),
|
||||
data.day.long_pos.round_to_decimal(),
|
||||
data.day.short_pos.round_to_decimal(),
|
||||
data.day.delta.round_to_decimal(),
|
||||
data.day.gamma.round_to_decimal(),
|
||||
data.day.vega.round_to_decimal(),
|
||||
data.day.theta.round_to_decimal()),
|
||||
_ => (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
};
|
||||
|
||||
var working_long = data.working_long_pos.round_to_decimal();
|
||||
var working_short = data.working_short_pos.round_to_decimal();
|
||||
|
||||
var nd = new PNLNormalizedData() {
|
||||
// sod
|
||||
sod_pnl = sod_pnl.value.round_to_decimal(),
|
||||
sod_realized_pnl = sod_realized_pnl.value.round_to_decimal(),
|
||||
sod_unrealized_pnl = sod_unrealized_pnl.value.round_to_decimal(),
|
||||
sod_long_pos = sod_long,
|
||||
sod_short_pos = sod_short,
|
||||
sod_delta = sod_delta,
|
||||
sod_gamma = sod_gamma,
|
||||
sod_vega = sod_vega,
|
||||
sod_theta = sod_theta,
|
||||
display_sod_long_pos = sod_long,
|
||||
display_sod_short_pos = sod_short,
|
||||
|
||||
// day
|
||||
day_pnl = day_pnl.value.round_to_decimal(),
|
||||
day_realized_pnl = day_realized_pnl.value.round_to_decimal(),
|
||||
day_unrealized_pnl = day_unrealized_pnl.value.round_to_decimal(),
|
||||
day_long_pos = day_long,
|
||||
day_short_pos = day_short,
|
||||
day_delta = day_delta,
|
||||
day_gamma = day_gamma,
|
||||
day_vega = day_vega,
|
||||
day_theta = day_theta,
|
||||
display_day_long_pos = day_long,
|
||||
display_day_short_pos = day_short,
|
||||
|
||||
// working
|
||||
working_long_pos = working_long,
|
||||
working_short_pos = working_short,
|
||||
display_working_long_pos = working_long,
|
||||
display_working_short_pos = working_short
|
||||
};
|
||||
if (_normalized_data != nd) {
|
||||
_normalized_data = nd;
|
||||
updated?.Invoke(nd);
|
||||
}
|
||||
}
|
||||
return _normalized_data;
|
||||
}
|
||||
}
|
||||
// security
|
||||
public string account_name => data.account_name;
|
||||
public Venue exchange => security?.Exchange ?? Venue.Unset;
|
||||
public string symbol => security.symbol_or_default();
|
||||
public ExpirationDate expiration => data.expiration;
|
||||
public Security.ContractOneofCase security_type => security?.ContractCase ?? Security.ContractOneofCase.None;
|
||||
public SecurityImmutable? security => data.security;
|
||||
// cells
|
||||
public PNLCellVM<PNLRowData, AccountTuple> account_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Venue> exchange_cell { get; }
|
||||
public PNLCellVM<PNLRowData, ExpirationDate> expiration_cell { get; }
|
||||
public PNLCellVM<PNLRowData, string> symbol_cell { get; }
|
||||
public PNLCellVM<PNLRowData, Security.ContractOneofCase> security_type_cell { get; }
|
||||
public PNLCellVM<PNLRowData, SecurityImmutable?> security_cell { get; }
|
||||
public PNLCellVM<PNLRowData, uint> security_key { get; }
|
||||
public PNLCellVM<PNLRowData, double> cpp { get; }
|
||||
// sod
|
||||
public PNLCellVM<PNLRowData, double> sod_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_realized_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_long_avg_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_short_avg_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> sod_theta { get; }
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLRowData, double> day_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_realized_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_long_avg_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_short_avg_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> day_theta { get; }
|
||||
public PNLCellVM<PNLRowData, bool> has_day_trades { get; }
|
||||
// working
|
||||
public PNLCellVM<PNLRowData, double> working_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, WorkingOrderSideData> working_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> working_short_avg_price { get; }
|
||||
// total
|
||||
public PNLCellVM<PNLRowData, double> total_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_realized_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_net_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_long_avg_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_pos { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_short_avg_pnl { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_delta { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_gamma { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_vega { get; }
|
||||
public PNLCellVM<PNLRowData, double> total_theta { get; }
|
||||
|
||||
// (market)
|
||||
public PNLCellVM<PNLRowData, double> bid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> mid_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> ask_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> last_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> settlement_price { get; }
|
||||
public PNLCellVM<PNLRowData, double> pnl_price { get; }
|
||||
|
||||
// display
|
||||
public api.domain.TickDisplaySettingsImmutable quantity_display {
|
||||
get {
|
||||
if (_quantity_display_backing.HasValue) {
|
||||
return _quantity_display_backing.Value;
|
||||
}
|
||||
if (security.HasValue) {
|
||||
_quantity_display_backing = _sec_meta_store.get_qty_display(security.Value);
|
||||
}
|
||||
return _quantity_display_backing ??= Security.Defaults.quantity_display_settingsi;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<mbts.ui.spark.CellAnimation> animations { get; private set; }
|
||||
|
||||
public bool is_selected {
|
||||
get => _is_selected;
|
||||
set {
|
||||
if (Set(ref _is_selected, value))
|
||||
is_selected_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _market_price_cells {
|
||||
get {
|
||||
yield return pnl_price;
|
||||
yield return last_price;
|
||||
yield return bid_price;
|
||||
yield return mid_price;
|
||||
yield return ask_price;
|
||||
yield return settlement_price;
|
||||
}
|
||||
}
|
||||
|
||||
// includes sod, day, total, and working avg price columns
|
||||
IEnumerable<PNLCellVM<PNLRowData, double>> _fill_price_cells {
|
||||
get {
|
||||
// sod
|
||||
yield return sod_long_avg_price;
|
||||
yield return sod_short_avg_price;
|
||||
// day
|
||||
yield return day_long_avg_price;
|
||||
yield return day_short_avg_price;
|
||||
// total
|
||||
yield return total_long_avg_price;
|
||||
yield return total_short_avg_price;
|
||||
// working
|
||||
yield return working_long_avg_price;
|
||||
yield return working_short_avg_price;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(PNLRowData data) {
|
||||
_quantity_display_backing = null;
|
||||
_refresh_normalized_data = true;
|
||||
this.data = data;
|
||||
_cell_map.on_row(data);
|
||||
_animator.on_row(data);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
}
|
||||
|
||||
public void on_settings(ImmutableDictionary<ColumnKeys, PNLCellAppearance> defaults, IReadOnlyList<IPNLAnimationTrigger<PNLRowData>> triggers, Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
_animator.on_settings(defaults, triggers);
|
||||
_cell_map.set_appearances(_animator.cell_appearances);
|
||||
_set_price_formatting_delegates(fmt_fill_price, fmt_market_price);
|
||||
}
|
||||
|
||||
public bool Equals(PNLRowVM other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override string ToString() =>
|
||||
$"account: {account_name} exchange: {exchange} symbol: {symbol} security: {security} buy_quantity: {day_long_pos} sell_quantity: {day_short_pos} pnl: {total_pnl}";
|
||||
|
||||
void _set_price_formatting_delegates(Func<double, string> fmt_fill_price, Func<double, string> fmt_market_price) {
|
||||
foreach (var cell in _fill_price_cells) {
|
||||
cell.format = fmt_fill_price;
|
||||
}
|
||||
foreach (var cell in _market_price_cells) {
|
||||
cell.format = fmt_market_price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ namespace bts.shell.pnl {
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Core;
|
||||
using Spark.Core.Collections;
|
||||
@@ -119,7 +120,7 @@ namespace bts.shell.pnl {
|
||||
out var symbol_filter_items_observer,
|
||||
out var symbol_filter_items_view);
|
||||
symbol_filter_items_view.CustomSort = Comparer<string>.Default.to_safe().to_non_generic();
|
||||
expiration_filter = DiscreteValuesFilterVM.CreateTableUpdate<PNLRowData, DateTime, DateTime>(
|
||||
expiration_filter = DiscreteValuesFilterVM.CreateTableUpdate<PNLRowData, ExpirationDate, ExpirationDate>(
|
||||
r => r.expiration,
|
||||
exp => exp,
|
||||
out var expiration_filter_items_observer,
|
||||
@@ -226,7 +227,7 @@ namespace bts.shell.pnl {
|
||||
/// security
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, uint[]> account_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, Venue[]> exchange_filter { get; } = DiscreteValuesFilterVM.Common.exchange<PNLRowData>(r => r.security.exchange_or_default(), nameof(PNLRowVM.exchange));
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DateTime[]> expiration_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, ExpirationDate[]> expiration_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, string[]> symbol_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, string> security_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.security?.ToString(), nameof(PNLRowData.security));
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, string> security_key_filter { get; } = new RegexFilterVM<PNLRowData>(r => r.key.security_id.ToString());
|
||||
@@ -237,10 +238,14 @@ namespace bts.shell.pnl {
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> cpp_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.effective_cpp);
|
||||
/// sod
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_realized_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_realized_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_unrealized_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_unrealized_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_long_avg_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_long_avg_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_long_avg_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_short_avg_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_short_avg_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod_short_avg_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.net_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.delta);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> sod_gamma_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.sod.gamma);
|
||||
@@ -249,10 +254,14 @@ namespace bts.shell.pnl {
|
||||
|
||||
/// day
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_realized_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_realized_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_unrealized_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_unrealized_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_avg_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_long_avg_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_long_avg_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_avg_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_short_avg_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_short_avg_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day_net_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day.delta);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> day_gamma_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.day.gamma);
|
||||
@@ -267,10 +276,14 @@ namespace bts.shell.pnl {
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> working_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.working_net_pos);
|
||||
/// total
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_realized_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_realized_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_unrealized_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_unrealized_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_avg_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_long_avg_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_long_avg_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_avg_price_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_avg_price);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_short_avg_pnl_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_short_avg_pnl);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_net_pos_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_net_pos);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_delta_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_delta);
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DoubleMinMaxState> total_gamma_filter { get; } = new DoubleMinMax<PNLRowData>(r => r.total_gamma);
|
||||
@@ -310,6 +323,8 @@ namespace bts.shell.pnl {
|
||||
cpp = cpp_filter.get_state(),
|
||||
/// sod
|
||||
sod_pnl = sod_pnl_filter.get_state(),
|
||||
sod_realized_pnl = sod_realized_pnl_filter.get_state(),
|
||||
sod_unrealized_pnl = sod_unrealized_pnl_filter.get_state(),
|
||||
sod_long_pos = sod_long_pos_filter.get_state(),
|
||||
sod_long_avg_price = sod_long_avg_price_filter.get_state(),
|
||||
sod_short_pos = sod_short_pos_filter.get_state(),
|
||||
@@ -321,10 +336,14 @@ namespace bts.shell.pnl {
|
||||
sod_theta = sod_theta_filter.get_state(),
|
||||
/// day
|
||||
day_pnl = day_pnl_filter.get_state(),
|
||||
day_realized_pnl = day_realized_pnl_filter.get_state(),
|
||||
day_unrealized_pnl = day_unrealized_pnl_filter.get_state(),
|
||||
day_long_pos = day_long_pos_filter.get_state(),
|
||||
day_long_avg_price = day_long_avg_price_filter.get_state(),
|
||||
day_long_avg_pnl = day_long_avg_price_filter.get_state(),
|
||||
day_short_pos = day_short_pos_filter.get_state(),
|
||||
day_short_avg_price = day_short_avg_price_filter.get_state(),
|
||||
day_short_avg_pnl = day_short_avg_pnl_filter.get_state(),
|
||||
day_net_pos = day_net_pos_filter.get_state(),
|
||||
day_delta = day_delta_filter.get_state(),
|
||||
day_gamma = day_gamma_filter.get_state(),
|
||||
@@ -333,6 +352,8 @@ namespace bts.shell.pnl {
|
||||
has_day_trades = has_day_trades_filter.get_state(),
|
||||
/// total
|
||||
total_pnl = total_pnl_filter.get_state(),
|
||||
total_realized_pnl = total_realized_pnl_filter.get_state(),
|
||||
total_unrealized_pnl = total_unrealized_pnl_filter.get_state(),
|
||||
total_long_pos = total_long_pos_filter.get_state(),
|
||||
total_long_avg_price = total_long_avg_price_filter.get_state(),
|
||||
total_short_pos = total_short_pos_filter.get_state(),
|
||||
@@ -375,6 +396,8 @@ namespace bts.shell.pnl {
|
||||
cpp_filter.load_state(f.cpp);
|
||||
/// sod
|
||||
sod_pnl_filter.load_state(f.sod_pnl);
|
||||
sod_realized_pnl_filter.load_state(f.sod_realized_pnl);
|
||||
sod_unrealized_pnl_filter.load_state(f.sod_unrealized_pnl);
|
||||
sod_long_pos_filter.load_state(f.sod_long_pos);
|
||||
sod_long_avg_price_filter.load_state(f.sod_long_avg_price);
|
||||
sod_short_pos_filter.load_state(f.sod_short_pos);
|
||||
@@ -383,6 +406,8 @@ namespace bts.shell.pnl {
|
||||
sod_delta_filter.load_state(f.sod_delta);
|
||||
/// day
|
||||
day_pnl_filter.load_state(f.day_pnl);
|
||||
day_realized_pnl_filter.load_state(f.day_realized_pnl);
|
||||
day_unrealized_pnl_filter.load_state(f.day_unrealized_pnl);
|
||||
day_long_pos_filter.load_state(f.day_long_pos);
|
||||
day_long_avg_price_filter.load_state(f.day_long_avg_price);
|
||||
day_short_pos_filter.load_state(f.day_short_pos);
|
||||
@@ -391,6 +416,8 @@ namespace bts.shell.pnl {
|
||||
has_day_trades_filter.load_state(f.has_day_trades);
|
||||
/// total
|
||||
total_pnl_filter.load_state(f.total_pnl);
|
||||
total_realized_pnl_filter.load_state(f.total_realized_pnl);
|
||||
total_unrealized_pnl_filter.load_state(f.total_unrealized_pnl);
|
||||
total_long_pos_filter.load_state(f.total_long_pos);
|
||||
total_long_avg_price_filter.load_state(f.total_long_avg_price);
|
||||
total_short_pos_filter.load_state(f.total_short_pos);
|
||||
@@ -566,10 +593,14 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.CPP: return wrap(r => r.cpp);
|
||||
/// SOD
|
||||
case ColumnKeys.SODPNL: return wrap(r => r.sod_pnl);
|
||||
case ColumnKeys.SODRealizedPNL: return wrap(r => r.sod_realized_pnl);
|
||||
case ColumnKeys.SODUnrealizedPNL: return wrap(r => r.sod_unrealized_pnl);
|
||||
case ColumnKeys.SODLongPos: return wrap(r => r.sod_long_pos);
|
||||
case ColumnKeys.SODLongAvgPrice: return wrap(r => r.sod_long_avg_price);
|
||||
case ColumnKeys.SODLongAvgPNL: return wrap(r => r.sod_long_avg_pnl);
|
||||
case ColumnKeys.SODShortPos: return wrap(r => r.sod_short_pos);
|
||||
case ColumnKeys.SODShortAvgPrice: return wrap(r => r.sod_short_avg_price);
|
||||
case ColumnKeys.SODShortAvgPNL: return wrap(r => r.sod_short_avg_pnl);
|
||||
case ColumnKeys.SODNetPos: return wrap(r => r.sod_net_pos);
|
||||
case ColumnKeys.SODDelta: return wrap(r => r.sod_delta);
|
||||
case ColumnKeys.SODGamma: return wrap(r => r.sod_gamma);
|
||||
@@ -577,10 +608,14 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.SODTheta: return wrap(r => r.sod_theta);
|
||||
/// Day
|
||||
case ColumnKeys.DayPNL: return wrap(r => r.day_pnl);
|
||||
case ColumnKeys.DayRealizedPNL: return wrap(r => r.day_realized_pnl);
|
||||
case ColumnKeys.DayUnrealizedPNL: return wrap(r => r.day_unrealized_pnl);
|
||||
case ColumnKeys.DayLongPos: return wrap(r => r.day_long_pos);
|
||||
case ColumnKeys.DayLongAvgPrice: return wrap(r => r.day_long_avg_price);
|
||||
case ColumnKeys.DayLongAvgPNL: return wrap(r => r.day_long_avg_pnl);
|
||||
case ColumnKeys.DayShortPos: return wrap(r => r.day_short_pos);
|
||||
case ColumnKeys.DayShortAvgPrice: return wrap(r => r.day_short_avg_price);
|
||||
case ColumnKeys.DayShortAvgPNL: return wrap(r => r.day_short_avg_pnl);
|
||||
case ColumnKeys.DayNetPos: return wrap(r => r.day_net_pos);
|
||||
case ColumnKeys.DayDelta: return wrap(r => r.day_delta);
|
||||
case ColumnKeys.DayGamma: return wrap(r => r.day_gamma);
|
||||
@@ -589,10 +624,14 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.HasDayTrades: return wrap(r => r.has_day_trades);
|
||||
/// Total
|
||||
case ColumnKeys.TotalPNL: return wrap(r => r.total_pnl);
|
||||
case ColumnKeys.TotalRealizedPNL: return wrap(r => r.total_realized_pnl);
|
||||
case ColumnKeys.TotalUnrealizedPNL: return wrap(r => r.total_unrealized_pnl);
|
||||
case ColumnKeys.TotalLongPos: return wrap(r => r.total_long_pos);
|
||||
case ColumnKeys.TotalLongAvgPrice: return wrap(r => r.total_long_avg_price);
|
||||
case ColumnKeys.TotalLongAvgPNL: return wrap(r => r.total_long_avg_pnl);
|
||||
case ColumnKeys.TotalShortPos: return wrap(r => r.total_short_pos);
|
||||
case ColumnKeys.TotalShortAvgPrice: return wrap(r => r.total_short_avg_price);
|
||||
case ColumnKeys.TotalShortAvgPNL: return wrap(r => r.total_short_avg_pnl);
|
||||
case ColumnKeys.TotalNetPos: return wrap(r => r.total_net_pos);
|
||||
case ColumnKeys.TotalDelta: return wrap(r => r.total_delta);
|
||||
case ColumnKeys.TotalGamma: return wrap(r => r.total_gamma);
|
||||
@@ -627,13 +666,13 @@ namespace bts.shell.pnl {
|
||||
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
|
||||
}
|
||||
|
||||
static IObservable<TableUpdate<DateTime, DateTime>> _expirations_source(
|
||||
static IObservable<TableUpdate<ExpirationDate, ExpirationDate>> _expirations_source(
|
||||
IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) =>
|
||||
pnl_source
|
||||
.StartWith(TableUpdate<PNLRowData, PNLKey>.mk_empty(PNLRowData.key_fn))
|
||||
.distinct_on(
|
||||
PNLRowData.key_fn, r => r.expiration, e => e, EqualityComparer<DateTime>.Default)
|
||||
.Filter(e => e != DateTime.MinValue);
|
||||
PNLRowData.key_fn, r => r.expiration, e => e, EqualityComparer<ExpirationDate>.Default)
|
||||
.Filter(e => e != ExpirationDate.None);
|
||||
|
||||
// combines the known system symbols
|
||||
static IObservable<TableUpdate<string, string>> _symbols_source(IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) =>
|
||||
@@ -707,7 +746,7 @@ namespace bts.shell.pnl {
|
||||
filter.security = SecurityCriteria.from_securities(sec));
|
||||
break;
|
||||
case ColumnKeys.Expiration:
|
||||
try_set_filter_prop<DateTime>(exp_date => filter.exp_date = exp_date);
|
||||
try_set_filter_prop<ExpirationDate>(exp_date => filter.exp_date = exp_date);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
@@ -774,6 +813,17 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.Account: filter.accounts = new[] { g.Name.ToString() }; break;
|
||||
case ColumnKeys.Symbol: filter.symbols = new[] { g.Name.ToString() }; break;
|
||||
case ColumnKeys.Exchange: filter.exchanges = new[] { (Venue)g.Name }; break;
|
||||
case ColumnKeys.Expiration: {
|
||||
if (g.Name is ExpirationDate exp) {
|
||||
if (filter.security_criteria is null) {
|
||||
filter.security_criteria = SecurityCriteria.from_expirations(exp);
|
||||
} else {
|
||||
filter.security_criteria.clauses = ArrayHelper.add(
|
||||
filter.security_criteria.clauses, SecurityCriteriaClause.from_expirations(exp));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ColumnKeys.Security:
|
||||
if (g.Name is SecurityImmutable sec)
|
||||
filter.security_criteria = SecurityCriteria.from_securities(sec);
|
||||
|
||||
@@ -118,6 +118,8 @@ namespace bts.shell.pnl {
|
||||
});
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_realized_pnl = _cell_map.add_pnl(ColumnKeys.SODRealizedPNL, d => d.sod_realized_pnl);
|
||||
sod_unrealized_pnl = _cell_map.add_pnl(ColumnKeys.SODUnrealizedPNL, d => d.sod_unrealized_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.display_sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.display_sod_short_pos);
|
||||
@@ -128,6 +130,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_realized_pnl = _cell_map.add_pnl(ColumnKeys.DayRealizedPNL, d => d.day_realized_pnl);
|
||||
day_unrealized_pnl = _cell_map.add_pnl(ColumnKeys.DayUnrealizedPNL, d => d.day_unrealized_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.display_day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.display_day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.display_day_short_pos);
|
||||
@@ -138,6 +142,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
|
||||
total_realized_pnl = _cell_map.add_pnl(ColumnKeys.TotalRealizedPNL, d => d.total_realized_pnl);
|
||||
total_unrealized_pnl = _cell_map.add_pnl(ColumnKeys.TotalUnrealizedPNL, d => d.total_unrealized_pnl);
|
||||
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.display_total_net_pos);
|
||||
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.display_total_long_pos);
|
||||
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.display_total_short_pos);
|
||||
@@ -181,6 +187,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// sod
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
|
||||
@@ -191,6 +199,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
|
||||
@@ -201,6 +211,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Margin" Value="4,0,0,0"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HeaderRowCellText" TargetType="TextBlock">
|
||||
@@ -570,6 +571,8 @@
|
||||
</TextBlock>
|
||||
<!--SOD-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODPNL" DataContext="{Binding total_aggregate.sod_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODRealizedPNL" DataContext="{Binding total_aggregate.sod_realized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODUnrealizedPNL" DataContext="{Binding total_aggregate.sod_unrealized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODNetPos" DataContext="{Binding total_aggregate.sod_net_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODLongPos" DataContext="{Binding total_aggregate.sod_long_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODShortPos" DataContext="{Binding total_aggregate.sod_short_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
@@ -580,6 +583,8 @@
|
||||
|
||||
<!--Day-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayPNL" DataContext="{Binding total_aggregate.day_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayRealizedPNL" DataContext="{Binding total_aggregate.day_realized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayUnrealizedPNL" DataContext="{Binding total_aggregate.day_unrealized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding total_aggregate.day_net_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding total_aggregate.day_long_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding total_aggregate.day_short_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
@@ -598,6 +603,8 @@
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingShortPos" DataContext="{Binding total_aggregate.working_short_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<!--Total-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalPNL" DataContext="{Binding total_aggregate.total_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalRealizedPNL" DataContext="{Binding total_aggregate.total_realized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalUnrealizedPNL" DataContext="{Binding total_aggregate.total_unrealized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalNetPos" DataContext="{Binding total_aggregate.total_net_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalLongPos" DataContext="{Binding total_aggregate.total_long_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalShortPos" DataContext="{Binding total_aggregate.total_short_pos}" Style="{StaticResource TotalRowHyperlinkCell}"/>
|
||||
@@ -632,6 +639,8 @@
|
||||
<TextBlock Text="{Binding selected_rows.Count, StringFormat='Selected ({0})'}" Style="{StaticResource HeaderRowHeaderText}"/>
|
||||
<!--SOD-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODPNL" DataContext="{Binding selected_aggregate.sod_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODRealizedPNL" DataContext="{Binding selected_aggregate.sod_realized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODUnrealizedPNL" DataContext="{Binding selected_aggregate.sod_unrealized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODNetPos" DataContext="{Binding selected_aggregate.sod_net_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODLongPos" DataContext="{Binding selected_aggregate.sod_long_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODShortPos" DataContext="{Binding selected_aggregate.sod_short_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
@@ -642,6 +651,8 @@
|
||||
|
||||
<!--Day-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayPNL" DataContext="{Binding selected_aggregate.day_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayRealizedPNL" DataContext="{Binding selected_aggregate.day_realized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayUnrealizedPNL" DataContext="{Binding selected_aggregate.day_unrealized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding selected_aggregate.day_net_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding selected_aggregate.day_long_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding selected_aggregate.day_short_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
@@ -660,6 +671,8 @@
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingShortPos" DataContext="{Binding selected_aggregate.working_short_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<!--Total-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalPNL" DataContext="{Binding selected_aggregate.total_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalRealizedPNL" DataContext="{Binding selected_aggregate.total_realized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalUnrealizedPNL" DataContext="{Binding selected_aggregate.total_unrealized_pnl}" Style="{StaticResource HeaderRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalNetPos" DataContext="{Binding selected_aggregate.total_net_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalLongPos" DataContext="{Binding selected_aggregate.total_long_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalShortPos" DataContext="{Binding selected_aggregate.total_short_pos}" Style="{StaticResource SelectedTotalRowHyperlinkCell}"/>
|
||||
@@ -791,6 +804,26 @@
|
||||
data_member_path="sod_pnl"
|
||||
Header="SOD PnL"
|
||||
/>
|
||||
<!--SOD Realized PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_realized_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_realized_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_realized_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="SODRealizedPNL"
|
||||
data_member_path="sod_realized_pnl"
|
||||
Header="SOD Realized PnL"
|
||||
/>
|
||||
<!--SOD Unrealized PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_unrealized_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_unrealized_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_unrealized_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="SODUnrealizedPNL"
|
||||
data_member_path="sod_unrealized_pnl"
|
||||
Header="SOD Unrealized PnL"
|
||||
/>
|
||||
<!--SOD Net Pos-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_net_pos_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -819,6 +852,15 @@
|
||||
key="SODLongAvgPrice"
|
||||
data_member_path="sod_long_avg_price"
|
||||
/>
|
||||
<!--SOD Long Avg PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_long_avg_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_long_avg_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_long_avg_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="SODLongAvgPNL"
|
||||
data_member_path="sod_long_avg_pnl"
|
||||
/>
|
||||
<!--SOD Short Pos-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_short_pos_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -837,6 +879,15 @@
|
||||
key="SODShortAvgPrice"
|
||||
data_member_path="sod_short_avg_price"
|
||||
/>
|
||||
<!--SOD Short Avg PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_short_avg_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.sod_short_avg_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.sod_short_avg_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="SODShortAvgPNL"
|
||||
data_member_path="sod_short_avg_pnl"
|
||||
/>
|
||||
<!--SOD Delta-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.sod_delta_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -880,6 +931,26 @@
|
||||
data_member_path="day_pnl"
|
||||
sort_member_path="day_pnl"
|
||||
/>
|
||||
<!--Day Realized PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_realized_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_realized_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_realized_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="DayRealizedPNL"
|
||||
data_member_path="day_realized_pnl"
|
||||
Header="Day Realized PnL"
|
||||
/>
|
||||
<!--Day Unrealized PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_unrealized_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_unrealized_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_unrealized_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="DayUnrealizedPNL"
|
||||
data_member_path="day_unrealized_pnl"
|
||||
Header="Day Unrealized PnL"
|
||||
/>
|
||||
<!--Day Net Pos-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_net_pos_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -910,6 +981,16 @@
|
||||
data_member_path="day_long_avg_price"
|
||||
sort_member_path="day_long_avg_price"
|
||||
/>
|
||||
<!--Day Long Avg PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_long_avg_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_long_avg_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_long_avg_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="DayLongAvgPNL"
|
||||
data_member_path="day_long_avg_pnl"
|
||||
sort_member_path="day_long_avg_pnl"
|
||||
/>
|
||||
<!--Day Delta-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_delta_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -1012,6 +1093,16 @@
|
||||
data_member_path="day_short_avg_price"
|
||||
sort_member_path="day_short_avg_price"
|
||||
/>
|
||||
<!--Day Short Avg PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_short_avg_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.day_short_avg_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.day_short_avg_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="DayShortAvgPNL"
|
||||
data_member_path="day_short_avg_pnl"
|
||||
sort_member_path="day_short_avg_pnl"
|
||||
/>
|
||||
<!--Day Short Pos-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.day_short_pos_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -1090,6 +1181,26 @@
|
||||
key="TotalPNL"
|
||||
data_member_path="total_pnl"
|
||||
/>
|
||||
<!--Total Realized PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_realized_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_realized_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_realized_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="TotalRealizedPNL"
|
||||
data_member_path="total_realized_pnl"
|
||||
Header="Total Realized PnL"
|
||||
/>
|
||||
<!--Total Unrealized PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_unrealized_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_unrealized_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_unrealized_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="TotalUnrealizedPNL"
|
||||
data_member_path="total_unrealized_pnl"
|
||||
Header="Total Unrealized PnL"
|
||||
/>
|
||||
<!--Total Net Pos-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_net_pos_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -1120,6 +1231,16 @@
|
||||
data_member_path="total_long_avg_price"
|
||||
sort_member_path="total_long_avg_price"
|
||||
/>
|
||||
<!--Total Long Avg PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_long_avg_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_long_avg_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_long_avg_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="TotalLongAvgPNL"
|
||||
data_member_path="total_long_avg_pnl"
|
||||
sort_member_path="total_long_avg_pnl"
|
||||
/>
|
||||
<!--Total Short Pos-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_short_pos_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -1140,6 +1261,16 @@
|
||||
data_member_path="total_short_avg_price"
|
||||
sort_member_path="total_short_avg_price"
|
||||
/>
|
||||
<!--Total Short Avg PNL-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_short_avg_pnl_filter, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterTemplate="{StaticResource {x:Static ft:DataGridFilterKnownTemplates.DoubleMinMax}}"
|
||||
c:DataGridFilterProperties.FilterActive="{Binding DataContext.total_short_avg_pnl_filter.active, Source={u:DirectReference _grid}}"
|
||||
c:DataGridFilterProperties.FilterOpen="{Binding DataContext.total_short_avg_pnl_filter.open, Source={u:DirectReference _grid}}"
|
||||
key="TotalShortAvgPNL"
|
||||
data_member_path="total_short_avg_pnl"
|
||||
sort_member_path="total_short_avg_pnl"
|
||||
/>
|
||||
<!--Total Delta-->
|
||||
<pnl:PNLColumn
|
||||
c:DataGridFilterProperties.Filter="{Binding DataContext.total_delta_filter, Source={u:DirectReference _grid}}"
|
||||
@@ -1198,11 +1329,15 @@
|
||||
<c:TextCell c:DIYGrid.ColumnKey="CPP" DataContext="{Binding cpp}" Style="{StaticResource RowTextCell}"/>
|
||||
<!--SOD-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODPNL" DataContext="{Binding sod_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODRealizedPNL" DataContext="{Binding sod_realized_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODUnrealizedPNL" DataContext="{Binding sod_unrealized_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODNetPos" DataContext="{Binding sod_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODLongPos" DataContext="{Binding sod_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODLongAvgPrice" DataContext="{Binding sod_long_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODLongAvgPNL" DataContext="{Binding sod_long_avg_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODShortPos" DataContext="{Binding sod_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODShortAvgPrice" DataContext="{Binding sod_short_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODShortAvgPNL" DataContext="{Binding sod_short_avg_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="SODDelta"
|
||||
DataContext="{Binding sod_delta}"
|
||||
@@ -1222,11 +1357,15 @@
|
||||
|
||||
<!--Day-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayPNL" DataContext="{Binding day_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayRealizedPNL" DataContext="{Binding day_realized_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayUnrealizedPNL" DataContext="{Binding day_unrealized_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding day_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding day_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayLongAvgPrice" DataContext="{Binding day_long_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayLongAvgPNL" DataContext="{Binding day_long_avg_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding day_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayShortAvgPrice" DataContext="{Binding day_short_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayShortAvgPNL" DataContext="{Binding day_short_avg_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="DayDelta"
|
||||
DataContext="{Binding day_delta}"
|
||||
@@ -1257,6 +1396,9 @@
|
||||
<c:TextCell c:DIYGrid.ColumnKey="LastPrice" DataContext="{Binding last_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SettlementPrice" DataContext="{Binding settlement_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="PNLPrice" DataContext="{Binding pnl_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="BidPrice" DataContext="{Binding bid_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="AskPrice" DataContext="{Binding ask_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="MidPrice" DataContext="{Binding mid_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<!--Working-->
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingNetPos" DataContext="{Binding working_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingLongPos" DataContext="{Binding working_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
@@ -1265,11 +1407,15 @@
|
||||
<c:TextCell c:DIYGrid.ColumnKey="WorkingShortAvgPrice" DataContext="{Binding working_short_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<!--Total-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalPNL" DataContext="{Binding total_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalRealizedPNL" DataContext="{Binding total_realized_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalUnrealizedPNL" DataContext="{Binding total_unrealized_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalNetPos" DataContext="{Binding total_net_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalLongPos" DataContext="{Binding total_long_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalLongAvgPrice" DataContext="{Binding total_long_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalLongAvgPNL" DataContext="{Binding total_long_avg_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalShortPos" DataContext="{Binding total_short_pos}" Style="{StaticResource RowHyperlinkCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalShortAvgPrice" DataContext="{Binding total_short_avg_price}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalShortAvgPNL" DataContext="{Binding total_short_avg_pnl}" Style="{StaticResource RowTextCell}"/>
|
||||
<c:TextCell
|
||||
c:DIYGrid.ColumnKey="TotalDelta"
|
||||
DataContext="{Binding total_delta}"
|
||||
@@ -1360,6 +1506,8 @@
|
||||
</Grid>
|
||||
<!--SOD-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODPNL" DataContext="{Binding sod_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODRealizedPNL" DataContext="{Binding sod_realized_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="SODUnrealizedPNL" DataContext="{Binding sod_unrealized_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODNetPos" DataContext="{Binding sod_net_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODLongPos" DataContext="{Binding sod_long_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="SODShortPos" DataContext="{Binding sod_short_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
@@ -1370,6 +1518,8 @@
|
||||
|
||||
<!--Day-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayPNL" DataContext="{Binding day_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayRealizedPNL" DataContext="{Binding day_realized_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="DayUnrealizedPNL" DataContext="{Binding day_unrealized_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayNetPos" DataContext="{Binding day_net_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayLongPos" DataContext="{Binding day_long_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="DayShortPos" DataContext="{Binding day_short_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
@@ -1384,6 +1534,8 @@
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="WorkingShortPos" DataContext="{Binding working_short_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<!--Total-->
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalPNL" DataContext="{Binding total_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalRealizedPNL" DataContext="{Binding total_realized_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:TextCell c:DIYGrid.ColumnKey="TotalUnrealizedPNL" DataContext="{Binding total_unrealized_pnl}" Style="{StaticResource GroupRowTextCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalNetPos" DataContext="{Binding total_net_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalLongPos" DataContext="{Binding total_long_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
<c:HyperlinkCell c:DIYGrid.ColumnKey="TotalShortPos" DataContext="{Binding total_short_pos}" Style="{StaticResource GroupRowHyperlinkCell}"/>
|
||||
|
||||
@@ -1,427 +1,439 @@
|
||||
namespace bts.shell.pnl {
|
||||
using bts.collections;
|
||||
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
enum PNLHeaderRowDisplay {
|
||||
None,
|
||||
Total,
|
||||
SelectedTotal
|
||||
}
|
||||
|
||||
partial class PNLView : UserControl {
|
||||
public static readonly double
|
||||
MinGroupRowHeight = 24.0.RoundLayoutValue(),
|
||||
MinHeaderRowHeight = 24.0.RoundLayoutValue();
|
||||
DIYGridPopupManager _popup_man_backing;
|
||||
|
||||
public enum Columns {
|
||||
Account = 1,
|
||||
// Security
|
||||
Exchange,
|
||||
Symbol,
|
||||
Expiration,
|
||||
SecurityType,
|
||||
Security,
|
||||
SecurityKey,
|
||||
CPP,
|
||||
// SOD
|
||||
SODPNL,
|
||||
SODLongPos,
|
||||
SODLongAvgPrice,
|
||||
SODShortPos,
|
||||
SODShortAvgPrice,
|
||||
SODNetPos,
|
||||
SODDelta,
|
||||
SODGamma,
|
||||
SODVega,
|
||||
SODTheta,
|
||||
// Day
|
||||
DayPNL,
|
||||
DayLongPos,
|
||||
DayLongAvgPrice,
|
||||
DayShortAvgPrice,
|
||||
DayShortPos,
|
||||
DayDelta,
|
||||
DayGamma,
|
||||
DayVega,
|
||||
DayTheta,
|
||||
DayCalcType,
|
||||
DayNetPos,
|
||||
HasDayTrades,
|
||||
// Raw Day
|
||||
RawDayLongPos,
|
||||
RawDayShortPos,
|
||||
RawDayNetPos,
|
||||
// Working
|
||||
WorkingLongPos,
|
||||
WorkingLongAvgPrice,
|
||||
WorkingShortPos,
|
||||
WorkingShortAvgPrice,
|
||||
WorkingNetPos,
|
||||
// Total
|
||||
TotalPNL,
|
||||
TotalLongPos,
|
||||
TotalLongAvgPrice,
|
||||
TotalShortPos,
|
||||
TotalShortAvgPrice,
|
||||
TotalNetPos,
|
||||
TotalDelta,
|
||||
TotalGamma,
|
||||
TotalVega,
|
||||
TotalTheta,
|
||||
// Market
|
||||
BidPrice,
|
||||
MidPrice,
|
||||
AskPrice,
|
||||
LastPrice,
|
||||
SettlementPrice,
|
||||
PNLPrice
|
||||
}
|
||||
|
||||
public static readonly IValueConverter
|
||||
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
|
||||
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
|
||||
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
|
||||
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
TotalHeaderRowDisplayTextConverter = ValueConverter.create<int, int, string>((filtered, total) => $"Total ({(filtered == total ? total.ToString() : $"{filtered}/{total}")})"),
|
||||
CellParamsConverter = ValueConverter.create<object, Columns, PNLCell>((item, col) => new PNLCell(item, col));
|
||||
|
||||
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
|
||||
|
||||
static readonly string _node_path_separator = $" {SpecialCharacters.Dot} ";
|
||||
|
||||
IDisposable _anchors_hook, _animations_hook;
|
||||
int _anchor_version;
|
||||
|
||||
public PNLView() {
|
||||
SetCurrentValue(AnchorIndexProperty, new AnchorIndex());
|
||||
InitializeComponent();
|
||||
SetBinding(AnchorsProperty, new Binding(nameof(PNLVM.anchors)));
|
||||
_grid.ConnectOnLoaded(() => {
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter)) {
|
||||
presenter.GroupItemSelectionPivot += on_group_item_selection_pivot;
|
||||
return Disposable.Create(() => presenter.GroupItemSelectionPivot -= on_group_item_selection_pivot);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
void on_group_item_selection_pivot(object sender, GroupItemSelectionPivotEventArgs e) =>
|
||||
e.Handled = e.group_item is PNLRowCollectionViewGroup pnl_group && pnl_group.is_expanded;
|
||||
}
|
||||
|
||||
public new PNLVM DataContext {
|
||||
get => base.DataContext as PNLVM;
|
||||
set => base.DataContext = value;
|
||||
}
|
||||
|
||||
public bool EnableAnimations {
|
||||
get => _animations_hook != null;
|
||||
set {
|
||||
if (EnableAnimations != value) {
|
||||
if (_animations_hook == null) {
|
||||
_animations_hook = AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
} else {
|
||||
_animations_hook.Dispose();
|
||||
_animations_hook = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region AnchorIndex
|
||||
public static readonly DependencyProperty AnchorIndexProperty =
|
||||
WPFHelper.create_dp<AnchorIndex, PNLView>(
|
||||
nameof(AnchorIndex),
|
||||
(o, v) => v?.reset(o?.Anchors));
|
||||
|
||||
public AnchorIndex AnchorIndex {
|
||||
get => GetValue(AnchorIndexProperty) as AnchorIndex;
|
||||
set => SetValue(AnchorIndexProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GroupLevel
|
||||
public static readonly DependencyProperty GroupLevelProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"GroupLevel",
|
||||
typeof(int),
|
||||
typeof(PNLView),
|
||||
new PropertyMetadata(0, (d, e) => {
|
||||
if (d is GroupItem group && e.NewValue is int level)
|
||||
group.SetBinding(DIYGrid.ColumnKeyProperty, new Binding($"{nameof(Anchors)}[{level}]") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(PNLView), 1) });
|
||||
}));
|
||||
|
||||
public static int GetGroupLevel(DependencyObject d) => (int)d.GetValue(GroupLevelProperty);
|
||||
public static void SetGroupLevel(DependencyObject d, int value) => d.SetValue(GroupLevelProperty, value);
|
||||
|
||||
void _grid_loaded(object sender, RoutedEventArgs e) {
|
||||
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
|
||||
rp.SetBinding(MarginProperty,
|
||||
BindingHelper.create_multi(
|
||||
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
|
||||
double top = 0;
|
||||
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
|
||||
top += row_height;
|
||||
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
|
||||
top += row_height;
|
||||
return new Thickness(0, top, 0, 0);
|
||||
}),
|
||||
new Binding(nameof(PNLVM.header_row_display)),
|
||||
new Binding {
|
||||
Path = new PropertyPath(DIYGrid.RowHeightProperty),
|
||||
Converter = HeaderRowHeightConverter,
|
||||
Source = _grid
|
||||
}));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Anchors
|
||||
public static readonly DependencyProperty AnchorsProperty =
|
||||
WPFHelper.create_dp<IEnumerable<Columns>, PNLView>(
|
||||
nameof(Anchors),
|
||||
(o, v) => {
|
||||
if (o?._grid?.Columns is not ObservableCollection<DIYGrid.ColumnBase> cols ||
|
||||
o?._grid?.Rows is not ICollectionView rows_view ||
|
||||
v is not INotifyCollectionChanged anchor_notifier ||
|
||||
v is not IReadOnlyList<Columns> anchor_keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
int version = ++o._anchor_version;
|
||||
bool busy = false;
|
||||
var pnl_cols = cols.CastObservable<DIYGrid.ColumnBase, PNLColumn>();
|
||||
o._anchors_hook?.Dispose();
|
||||
o._anchors_hook = Disposable.Create(
|
||||
anchor_notifier.Subscribe((_, __) => {
|
||||
if (busy)
|
||||
return;
|
||||
// invoke async to avoid noisy consecutive resets due to batch changes
|
||||
busy = true;
|
||||
o.Dispatcher.InvokeAsync(() => {
|
||||
sync_anchor_cols();
|
||||
busy = false;
|
||||
});
|
||||
}));
|
||||
o.AnchorIndex?.reset(anchor_keys);
|
||||
sync_anchor_cols();
|
||||
void sync_anchor_cols() {
|
||||
// since reset is invoked asynchronously, need to check we are still the latest version.
|
||||
if (o._anchor_version != version)
|
||||
return;
|
||||
o.AnchorIndex?.reset(anchor_keys);
|
||||
// move all the anchor columns to the left, ordered by anchor position (grouping hierarchy).
|
||||
for (int i = 0; i < anchor_keys.Count; i++) {
|
||||
if (pnl_cols.TryGetFirst(c => c.key == anchor_keys[i], out var match, out int curr_pos)) {
|
||||
match.is_visible = true;
|
||||
if (curr_pos != i) {
|
||||
cols.Move(curr_pos, i);
|
||||
}
|
||||
match.anchor_index = i + 1;
|
||||
}
|
||||
}
|
||||
for (int i = anchor_keys.Count; i < pnl_cols.Count; i++) {
|
||||
pnl_cols[i].anchor_index = null;
|
||||
}
|
||||
using (rows_view.DeferRefresh()) {
|
||||
rows_view.GroupDescriptions.Clear();
|
||||
var anchor_cols = from key in anchor_keys
|
||||
join col in pnl_cols on key equals col.key
|
||||
select new PNLGroupDescription(col);
|
||||
rows_view.GroupDescriptions.AddRange(anchor_cols);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public IEnumerable<Columns> Anchors {
|
||||
get => GetValue(AnchorsProperty) as IEnumerable<Columns>;
|
||||
set => SetValue(AnchorsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
DIYGridPopupManager _popup_man {
|
||||
get {
|
||||
if (_popup_man_backing == null) {
|
||||
_popup_man_backing = DIYGridPopupManager.create(this);
|
||||
}
|
||||
return _popup_man_backing;
|
||||
}
|
||||
}
|
||||
|
||||
void _cell_click(PNLCell cell) {
|
||||
Log.Ui.info($"PNLCell Click: {cell}.");
|
||||
try {
|
||||
switch (cell.column) {
|
||||
case Columns.SODNetPos:
|
||||
case Columns.SODLongPos:
|
||||
case Columns.SODShortPos:
|
||||
case Columns.DayNetPos:
|
||||
case Columns.DayLongPos:
|
||||
case Columns.DayShortPos:
|
||||
case Columns.RawDayNetPos:
|
||||
case Columns.RawDayLongPos:
|
||||
case Columns.RawDayShortPos:
|
||||
case Columns.TotalNetPos:
|
||||
case Columns.TotalLongPos:
|
||||
case Columns.TotalShortPos: {
|
||||
var oneof = cell.oneof();
|
||||
if (oneof == null)
|
||||
return;
|
||||
var (vm, context, title, fg) = cell.oneof().Match(_row_fills, _group_fills, _header_fills);
|
||||
_show_auxiliary_popup(vm, context, title, fg);
|
||||
}
|
||||
break;
|
||||
case Columns.WorkingNetPos:
|
||||
case Columns.WorkingLongPos:
|
||||
case Columns.WorkingShortPos: {
|
||||
var (vm, context, title, fg) = cell.oneof().Match(_orderbook_params, _orderbook_params, _orderbook_params);
|
||||
_show_auxiliary_popup(vm, context, title, fg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"{nameof(PNLView)} error on cell click: {ex}.");
|
||||
}
|
||||
}
|
||||
|
||||
#region Fills
|
||||
(FillsVM vm, string context, string title, Color fg) _row_fills(PNLRowCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
|
||||
}
|
||||
|
||||
(FillsVM vm, string context, string title, Color fg) _group_fills(PNLGroupCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
|
||||
}
|
||||
|
||||
(FillsVM vm, string context, string title, Color fg) _header_fills(PNLHeaderRowCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _fills_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.SODLongPos: return ("Open Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.SODShortPos: return ("Open Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.SODNetPos: return ("Open Fills", BTSColors.UI.default_text);
|
||||
case Columns.DayLongPos: return ("Day Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.DayShortPos: return ("Day Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.DayNetPos: return ("Day Fills", BTSColors.UI.default_text);
|
||||
case Columns.RawDayLongPos: return ("Raw Day Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.RawDayShortPos: return ("Raw Day Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.RawDayNetPos: return ("Raw Day Fills", BTSColors.UI.default_text);
|
||||
case Columns.TotalLongPos: return ("All Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.TotalShortPos: return ("All Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.TotalNetPos: return ("All Fills", BTSColors.UI.default_text);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OrderBook
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLRowCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
|
||||
}
|
||||
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLGroupCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
|
||||
}
|
||||
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLHeaderRowCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.WorkingLongPos: return ("Working Buy Orders", BTSColors.MarketData.bid);
|
||||
case Columns.WorkingShortPos: return ("Working Sell Orders", BTSColors.MarketData.ask);
|
||||
case Columns.WorkingNetPos: return ("Working Orders", BTSColors.UI.default_text);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
void _cell_click_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (e.Parameter is PNLCell cell) {
|
||||
_cell_click(cell);
|
||||
} else Log.Ui.warn($"{nameof(PNLView)} '{nameof(_cell_click_executed)}' parameter should be a {typeof(PNLCell)} (actually '{e.Parameter}').");
|
||||
}
|
||||
|
||||
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (!e.Handled && sender is FrameworkElement el && el.DataContext is PNLRowCollectionViewGroup group) {
|
||||
if (e.ClickCount == 2) {
|
||||
group.is_expanded = !group.is_expanded;
|
||||
} else if (el.try_find_ancestor_of_type(out DIYGrid grid) && grid.SelectionMode != SelectionMode.Single) {
|
||||
group.is_all_selected = !group.is_all_selected.GetValueOrDefault();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
|
||||
var view = new HeaderedContentControl {
|
||||
Content = content,
|
||||
Header = new AuxiliaryDataHeader {
|
||||
context = context_desc,
|
||||
title = title_desc,
|
||||
title_foreground = fg
|
||||
},
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Resources = new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
_popup_man.show(view);
|
||||
}
|
||||
|
||||
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
|
||||
public PNLGroupDescription(PNLColumn column) : base(column.data_member_path.Path) =>
|
||||
key = column.key;
|
||||
|
||||
public Columns key { get; }
|
||||
|
||||
public bool Equals(PNLGroupDescription other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as PNLGroupDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace bts.shell.pnl {
|
||||
using bts.collections;
|
||||
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.dpi;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
enum PNLHeaderRowDisplay {
|
||||
None,
|
||||
Total,
|
||||
SelectedTotal
|
||||
}
|
||||
|
||||
partial class PNLView : UserControl {
|
||||
public static readonly double
|
||||
MinGroupRowHeight = 24.0.RoundLayoutValue(),
|
||||
MinHeaderRowHeight = 24.0.RoundLayoutValue();
|
||||
DIYGridPopupManager _popup_man_backing;
|
||||
|
||||
public enum Columns {
|
||||
Account = 1,
|
||||
// Security
|
||||
Exchange,
|
||||
Symbol,
|
||||
Expiration,
|
||||
SecurityType,
|
||||
Security,
|
||||
SecurityKey,
|
||||
CPP,
|
||||
// SOD
|
||||
SODPNL,
|
||||
SODRealizedPNL,
|
||||
SODUnrealizedPNL,
|
||||
SODLongPos,
|
||||
SODLongAvgPrice,
|
||||
SODLongAvgPNL,
|
||||
SODShortPos,
|
||||
SODShortAvgPrice,
|
||||
SODShortAvgPNL,
|
||||
SODNetPos,
|
||||
SODDelta,
|
||||
SODGamma,
|
||||
SODVega,
|
||||
SODTheta,
|
||||
// Day
|
||||
DayPNL,
|
||||
DayRealizedPNL,
|
||||
DayUnrealizedPNL,
|
||||
DayLongPos,
|
||||
DayLongAvgPrice,
|
||||
DayLongAvgPNL,
|
||||
DayShortPos,
|
||||
DayShortAvgPrice,
|
||||
DayShortAvgPNL,
|
||||
DayDelta,
|
||||
DayGamma,
|
||||
DayVega,
|
||||
DayTheta,
|
||||
DayCalcType,
|
||||
DayNetPos,
|
||||
HasDayTrades,
|
||||
// Raw Day
|
||||
RawDayLongPos,
|
||||
RawDayShortPos,
|
||||
RawDayNetPos,
|
||||
// Working
|
||||
WorkingLongPos,
|
||||
WorkingLongAvgPrice,
|
||||
WorkingShortPos,
|
||||
WorkingShortAvgPrice,
|
||||
WorkingNetPos,
|
||||
// Total
|
||||
TotalPNL,
|
||||
TotalRealizedPNL,
|
||||
TotalUnrealizedPNL,
|
||||
TotalLongPos,
|
||||
TotalLongAvgPrice,
|
||||
TotalLongAvgPNL,
|
||||
TotalShortPos,
|
||||
TotalShortAvgPrice,
|
||||
TotalShortAvgPNL,
|
||||
TotalNetPos,
|
||||
TotalDelta,
|
||||
TotalGamma,
|
||||
TotalVega,
|
||||
TotalTheta,
|
||||
// Market
|
||||
BidPrice,
|
||||
MidPrice,
|
||||
AskPrice,
|
||||
LastPrice,
|
||||
SettlementPrice,
|
||||
PNLPrice
|
||||
}
|
||||
|
||||
public static readonly IValueConverter
|
||||
IncrementalGroupLevelConverter = ValueConverter.create<int, int>(level => level + 1),
|
||||
GroupLevelMarginConverter = ValueConverter.create<int, Thickness>(level => new Thickness(24 * level, 0, 0, 0)),
|
||||
HeaderRowHeightConverter = new RangeBlockConverter<double> { Minimum = MinHeaderRowHeight },
|
||||
ExpandedVisualStateConverter = ValueConverter.create<bool, string>(is_expanded => is_expanded ? "Expanded" : "Collapsed");
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
TotalHeaderRowDisplayTextConverter = ValueConverter.create<int, int, string>((filtered, total) => $"Total ({(filtered == total ? total.ToString() : $"{filtered}/{total}")})"),
|
||||
CellParamsConverter = ValueConverter.create<object, Columns, PNLCell>((item, col) => new PNLCell(item, col));
|
||||
|
||||
public static readonly RoutedCommand CellClickCommand = new RoutedCommand();
|
||||
|
||||
static readonly string _node_path_separator = $" {SpecialCharacters.Dot} ";
|
||||
|
||||
IDisposable _anchors_hook, _animations_hook;
|
||||
int _anchor_version;
|
||||
|
||||
public PNLView() {
|
||||
SetCurrentValue(AnchorIndexProperty, new AnchorIndex());
|
||||
InitializeComponent();
|
||||
SetBinding(AnchorsProperty, new Binding(nameof(PNLVM.anchors)));
|
||||
_grid.ConnectOnLoaded(() => {
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter presenter)) {
|
||||
presenter.GroupItemSelectionPivot += on_group_item_selection_pivot;
|
||||
return Disposable.Create(() => presenter.GroupItemSelectionPivot -= on_group_item_selection_pivot);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
void on_group_item_selection_pivot(object sender, GroupItemSelectionPivotEventArgs e) =>
|
||||
e.Handled = e.group_item is PNLRowCollectionViewGroup pnl_group && pnl_group.is_expanded;
|
||||
}
|
||||
|
||||
public new PNLVM DataContext {
|
||||
get => base.DataContext as PNLVM;
|
||||
set => base.DataContext = value;
|
||||
}
|
||||
|
||||
public bool EnableAnimations {
|
||||
get => _animations_hook != null;
|
||||
set {
|
||||
if (EnableAnimations != value) {
|
||||
if (_animations_hook == null) {
|
||||
_animations_hook = AdornerHelper.attach_continuously_on_loaded(
|
||||
() => new GridAnimation.DIYGridAnimationAdorner<Columns>(_grid, _grid.descendants_of_type<DIYGridRowsPresenter>().First()),
|
||||
_grid);
|
||||
} else {
|
||||
_animations_hook.Dispose();
|
||||
_animations_hook = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region AnchorIndex
|
||||
public static readonly DependencyProperty AnchorIndexProperty =
|
||||
WPFHelper.create_dp<AnchorIndex, PNLView>(
|
||||
nameof(AnchorIndex),
|
||||
(o, v) => v?.reset(o?.Anchors));
|
||||
|
||||
public AnchorIndex AnchorIndex {
|
||||
get => GetValue(AnchorIndexProperty) as AnchorIndex;
|
||||
set => SetValue(AnchorIndexProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GroupLevel
|
||||
public static readonly DependencyProperty GroupLevelProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"GroupLevel",
|
||||
typeof(int),
|
||||
typeof(PNLView),
|
||||
new PropertyMetadata(0, (d, e) => {
|
||||
if (d is GroupItem group && e.NewValue is int level)
|
||||
group.SetBinding(DIYGrid.ColumnKeyProperty, new Binding($"{nameof(Anchors)}[{level}]") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(PNLView), 1) });
|
||||
}));
|
||||
|
||||
public static int GetGroupLevel(DependencyObject d) => (int)d.GetValue(GroupLevelProperty);
|
||||
public static void SetGroupLevel(DependencyObject d, int value) => d.SetValue(GroupLevelProperty, value);
|
||||
|
||||
void _grid_loaded(object sender, RoutedEventArgs e) {
|
||||
// this is a hack to make room for the 'totals row' between the column headers and the regular rows.
|
||||
if (_grid.try_find_descendent_of_type(out DIYGridRowsPresenter rp)) {
|
||||
rp.SetBinding(MarginProperty,
|
||||
BindingHelper.create_multi(
|
||||
ValueConverter.create<PNLHeaderRowDisplay, double, Thickness>((hrd, row_height) => {
|
||||
double top = 0;
|
||||
if ((hrd & PNLHeaderRowDisplay.Total) != 0)
|
||||
top += row_height;
|
||||
if ((hrd & PNLHeaderRowDisplay.SelectedTotal) != 0)
|
||||
top += row_height;
|
||||
return new Thickness(0, top, 0, 0);
|
||||
}),
|
||||
new Binding(nameof(PNLVM.header_row_display)),
|
||||
new Binding {
|
||||
Path = new PropertyPath(DIYGrid.RowHeightProperty),
|
||||
Converter = HeaderRowHeightConverter,
|
||||
Source = _grid
|
||||
}));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Anchors
|
||||
public static readonly DependencyProperty AnchorsProperty =
|
||||
WPFHelper.create_dp<IEnumerable<Columns>, PNLView>(
|
||||
nameof(Anchors),
|
||||
(o, v) => {
|
||||
if (o?._grid?.Columns is not ObservableCollection<DIYGrid.ColumnBase> cols ||
|
||||
o?._grid?.Rows is not ICollectionView rows_view ||
|
||||
v is not INotifyCollectionChanged anchor_notifier ||
|
||||
v is not IReadOnlyList<Columns> anchor_keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
int version = ++o._anchor_version;
|
||||
bool busy = false;
|
||||
var pnl_cols = cols.CastObservable<DIYGrid.ColumnBase, PNLColumn>();
|
||||
o._anchors_hook?.Dispose();
|
||||
o._anchors_hook = Disposable.Create(
|
||||
anchor_notifier.Subscribe((_, __) => {
|
||||
if (busy)
|
||||
return;
|
||||
// invoke async to avoid noisy consecutive resets due to batch changes
|
||||
busy = true;
|
||||
o.Dispatcher.InvokeAsync(() => {
|
||||
sync_anchor_cols();
|
||||
busy = false;
|
||||
});
|
||||
}));
|
||||
o.AnchorIndex?.reset(anchor_keys);
|
||||
sync_anchor_cols();
|
||||
void sync_anchor_cols() {
|
||||
// since reset is invoked asynchronously, need to check we are still the latest version.
|
||||
if (o._anchor_version != version)
|
||||
return;
|
||||
o.AnchorIndex?.reset(anchor_keys);
|
||||
// move all the anchor columns to the left, ordered by anchor position (grouping hierarchy).
|
||||
for (int i = 0; i < anchor_keys.Count; i++) {
|
||||
if (pnl_cols.TryGetFirst(c => c.key == anchor_keys[i], out var match, out int curr_pos)) {
|
||||
match.is_visible = true;
|
||||
if (curr_pos != i) {
|
||||
cols.Move(curr_pos, i);
|
||||
}
|
||||
match.anchor_index = i + 1;
|
||||
}
|
||||
}
|
||||
for (int i = anchor_keys.Count; i < pnl_cols.Count; i++) {
|
||||
pnl_cols[i].anchor_index = null;
|
||||
}
|
||||
using (rows_view.DeferRefresh()) {
|
||||
rows_view.GroupDescriptions.Clear();
|
||||
var anchor_cols = from key in anchor_keys
|
||||
join col in pnl_cols on key equals col.key
|
||||
select new PNLGroupDescription(col);
|
||||
rows_view.GroupDescriptions.AddRange(anchor_cols);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public IEnumerable<Columns> Anchors {
|
||||
get => GetValue(AnchorsProperty) as IEnumerable<Columns>;
|
||||
set => SetValue(AnchorsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
DIYGridPopupManager _popup_man {
|
||||
get {
|
||||
if (_popup_man_backing == null) {
|
||||
_popup_man_backing = DIYGridPopupManager.create(this);
|
||||
}
|
||||
return _popup_man_backing;
|
||||
}
|
||||
}
|
||||
|
||||
void _cell_click(PNLCell cell) {
|
||||
Log.Ui.info($"PNLCell Click: {cell}.");
|
||||
try {
|
||||
switch (cell.column) {
|
||||
case Columns.SODNetPos:
|
||||
case Columns.SODLongPos:
|
||||
case Columns.SODShortPos:
|
||||
case Columns.DayNetPos:
|
||||
case Columns.DayLongPos:
|
||||
case Columns.DayShortPos:
|
||||
case Columns.RawDayNetPos:
|
||||
case Columns.RawDayLongPos:
|
||||
case Columns.RawDayShortPos:
|
||||
case Columns.TotalNetPos:
|
||||
case Columns.TotalLongPos:
|
||||
case Columns.TotalShortPos: {
|
||||
var oneof = cell.oneof();
|
||||
if (oneof == null)
|
||||
return;
|
||||
var (vm, context, title, fg) = cell.oneof().Match(_row_fills, _group_fills, _header_fills);
|
||||
_show_auxiliary_popup(vm, context, title, fg);
|
||||
}
|
||||
break;
|
||||
case Columns.WorkingNetPos:
|
||||
case Columns.WorkingLongPos:
|
||||
case Columns.WorkingShortPos: {
|
||||
var (vm, context, title, fg) = cell.oneof().Match(_orderbook_params, _orderbook_params, _orderbook_params);
|
||||
_show_auxiliary_popup(vm, context, title, fg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"{nameof(PNLView)} error on cell click: {ex}.");
|
||||
}
|
||||
}
|
||||
|
||||
#region Fills
|
||||
(FillsVM vm, string context, string title, Color fg) _row_fills(PNLRowCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
|
||||
}
|
||||
|
||||
(FillsVM vm, string context, string title, Color fg) _group_fills(PNLGroupCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
|
||||
}
|
||||
|
||||
(FillsVM vm, string context, string title, Color fg) _header_fills(PNLHeaderRowCell cell) {
|
||||
var (title_desc, fg) = _fills_title(cell.column);
|
||||
return (DataContext.mk_fills_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _fills_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.SODLongPos: return ("Open Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.SODShortPos: return ("Open Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.SODNetPos: return ("Open Fills", BTSColors.UI.default_text);
|
||||
case Columns.DayLongPos: return ("Day Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.DayShortPos: return ("Day Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.DayNetPos: return ("Day Fills", BTSColors.UI.default_text);
|
||||
case Columns.RawDayLongPos: return ("Raw Day Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.RawDayShortPos: return ("Raw Day Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.RawDayNetPos: return ("Raw Day Fills", BTSColors.UI.default_text);
|
||||
case Columns.TotalLongPos: return ("All Buy Fills", BTSColors.MarketData.bid);
|
||||
case Columns.TotalShortPos: return ("All Sell Fills", BTSColors.MarketData.ask);
|
||||
case Columns.TotalNetPos: return ("All Fills", BTSColors.UI.default_text);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OrderBook
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLRowCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.row, cell.column), $"{cell.row.account_name} {cell.row.security}", title_desc, fg);
|
||||
}
|
||||
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLGroupCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.group, cell.column), string.Join(_node_path_separator, cell.group.GroupNamePath), title_desc, fg);
|
||||
}
|
||||
|
||||
(OrderBookVM vm, string context, string title, Color fg) _orderbook_params(PNLHeaderRowCell cell) {
|
||||
var (title_desc, fg) = _orderbook_title(cell.column);
|
||||
return (DataContext.mk_orderbook_vm(cell.header_row, cell.column), EnumHelper.get_display_name(cell.header_row), title_desc, fg);
|
||||
}
|
||||
|
||||
static (string desc, Color fg) _orderbook_title(Columns key) {
|
||||
switch (key) {
|
||||
case Columns.WorkingLongPos: return ("Working Buy Orders", BTSColors.MarketData.bid);
|
||||
case Columns.WorkingShortPos: return ("Working Sell Orders", BTSColors.MarketData.ask);
|
||||
case Columns.WorkingNetPos: return ("Working Orders", BTSColors.UI.default_text);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(key, nameof(key));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
void _cell_click_executed(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (e.Parameter is PNLCell cell) {
|
||||
_cell_click(cell);
|
||||
} else Log.Ui.warn($"{nameof(PNLView)} '{nameof(_cell_click_executed)}' parameter should be a {typeof(PNLCell)} (actually '{e.Parameter}').");
|
||||
}
|
||||
|
||||
void _on_group_item_mouse_down(object sender, MouseButtonEventArgs e) {
|
||||
if (!e.Handled && sender is FrameworkElement el && el.DataContext is PNLRowCollectionViewGroup group) {
|
||||
if (e.ClickCount == 2) {
|
||||
group.is_expanded = !group.is_expanded;
|
||||
} else if (el.try_find_ancestor_of_type(out DIYGrid grid) && grid.SelectionMode != SelectionMode.Single) {
|
||||
group.is_all_selected = !group.is_all_selected.GetValueOrDefault();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _show_auxiliary_popup<T>(T content, string context_desc, string title_desc, Color fg) {
|
||||
var view = new HeaderedContentControl {
|
||||
Content = content,
|
||||
Header = new AuxiliaryDataHeader {
|
||||
context = context_desc,
|
||||
title = title_desc,
|
||||
title_foreground = fg
|
||||
},
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Resources = new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
_popup_man.show(view);
|
||||
}
|
||||
|
||||
class PNLGroupDescription : PropertyGroupDescription, IEquatable<PNLGroupDescription> {
|
||||
public PNLGroupDescription(PNLColumn column) : base(column.data_member_path.Path) =>
|
||||
key = column.key;
|
||||
|
||||
public Columns key { get; }
|
||||
|
||||
public bool Equals(PNLGroupDescription other) =>
|
||||
other != null && key == other.key;
|
||||
|
||||
public override int GetHashCode() =>
|
||||
key.GetHashCode();
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as PNLGroupDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,9 +65,9 @@ namespace BCG {
|
||||
public static NMSheetTheoBBOCross get_cross_state(double bid_price, double ask_price, double theo, double delta, bool is_call, double driver_tick_size, bool is_proximity_alert_enabled) {
|
||||
double abs_delta = Math.Abs(delta);
|
||||
abs_delta = abs_delta == 0 ? Double.Epsilon : abs_delta;
|
||||
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && theo < bid_price)
|
||||
if (!Double.IsNaN(theo) && !Double.IsNaN(bid_price) && (bid_price - theo) > _basic_crosss_eps)
|
||||
return new NMSheetTheoBBOCross(CrossState.bid_cross, 0);
|
||||
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && theo > ask_price)
|
||||
else if (!Double.IsNaN(theo) && !Double.IsNaN(ask_price) && (theo - ask_price) > _basic_crosss_eps)
|
||||
return new NMSheetTheoBBOCross(CrossState.ask_cross, 0);
|
||||
else if (is_proximity_alert_enabled) {
|
||||
/// bool is passed to bypass doing the below
|
||||
@@ -87,6 +87,7 @@ namespace BCG {
|
||||
return NMSheetTheoBBOCross.get_nulled_obj();
|
||||
}
|
||||
|
||||
private static double _basic_crosss_eps = 0.01;
|
||||
private static double _n_max_ticks = 100.0;
|
||||
private static NMSheetTheoBBOCross _nulled_obj = new NMSheetTheoBBOCross(CrossState.none, 0);
|
||||
public static NMSheetTheoBBOCross get_nulled_obj() { return _nulled_obj; }
|
||||
|
||||
@@ -1,243 +1,244 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Core;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using OrderPair = System.ValueTuple<bts.api.SecurityImmutable?, bts.api.orderman.OrderRecordImmutable>;
|
||||
using TickDisplaySettingsImmutable = bts.api.domain.TickDisplaySettingsImmutable;
|
||||
|
||||
namespace bts.spark {
|
||||
internal class Activity {
|
||||
public SecurityImmutable? security;
|
||||
public string message;
|
||||
public ILogViewerCategory category;
|
||||
}
|
||||
|
||||
class OrderActivitySummarizer : IDisposable {
|
||||
readonly IBus _bus;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security = new Dictionary<uint, SecurityMD>();
|
||||
IDisposable _dispose;
|
||||
static readonly IEqualityComparer<OrderRecordImmutable> _id_comparer =
|
||||
bts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
|
||||
|
||||
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<OrderRecordTuple, uint>> orders) {
|
||||
_bus = bus;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec => _security = sec),
|
||||
orders.Subscribe(orders_updated));
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
#region formatting
|
||||
|
||||
SecurityImmutable? try_get_security(uint security_key) {
|
||||
if (_security.TryGetValue(security_key, out SecurityMD md)) {
|
||||
return md.security;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static string format_order_success(
|
||||
uint key, double price, double size, Side side, ContractSpecificationImmutable? spec) =>
|
||||
$"({key}) {side.ToString().ToLower()} {size} @ {spec.format(price)}";
|
||||
|
||||
string format_order_fail(
|
||||
double price,
|
||||
double size,
|
||||
Side side,
|
||||
ContractSpecificationImmutable? spec,
|
||||
string error) =>
|
||||
$"Failed to {side.ToString().ToLower()} {size} @ {spec.format(price)}. {error}";
|
||||
|
||||
string format_cancel_success(OrderRecordImmutable order, ContractSpecificationImmutable? spec) {
|
||||
return $"({order.Key}) Canceled {order.short_description(spec)}, was working {order.working_quantity()}";
|
||||
}
|
||||
string format_cancel_error(OrderRecordImmutable order, ContractSpecificationImmutable? spec, string error) {
|
||||
return $"({order.Key}) Failed to cancel {order.short_description(spec)}: {error}";
|
||||
}
|
||||
public static string format_fill_summary(OrderRecordImmutable before, OrderRecordImmutable after) =>
|
||||
_format_fill_summary(_header(after.Key), before, after);
|
||||
|
||||
public static string format_fill_summary(OrderRecordImmutable before, OrderRecordImmutable after, SecurityImmutable? security) =>
|
||||
_format_fill_summary(_header(after.Key, security), before, after);
|
||||
|
||||
public static string format_status_change(uint key, OrderStatus old_status, OrderStatus new_status) =>
|
||||
_format_status_change(_header(key), key, old_status, new_status);
|
||||
|
||||
public static string format_status_change(uint key, OrderStatus old_status, OrderStatus new_status, SecurityImmutable? security) =>
|
||||
_format_status_change(_header(key, security), key, old_status, new_status);
|
||||
|
||||
// since changes are naggled, transition changes aren't always detected (e.g. a new order changes before observers are notified of its existence), so old_status might be null.
|
||||
public static string format_status_change(uint key, OrderStatus? old_status, OrderStatus new_status, SecurityImmutable? security) {
|
||||
string header = _header(key, security);
|
||||
return old_status.HasValue ?
|
||||
_format_status_change(header, key, old_status.Value, new_status) :
|
||||
_format_status_change(header, key, new_status);
|
||||
}
|
||||
|
||||
public static string order_completion_debrief(
|
||||
OrderRecordImmutable order,
|
||||
SecurityImmutable? security,
|
||||
TickDisplaySettingsImmutable qty_display) =>
|
||||
_format_order_completion(
|
||||
_header(order.Key, security), order, security?.ContractSpec, qty_display);
|
||||
|
||||
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
|
||||
double old_price = old_order.State.Price, new_price = new_order.State.Price;
|
||||
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
|
||||
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
|
||||
return StringBuilderEx.join(prefix, change_descs());
|
||||
//
|
||||
string prefix() => $"({old_order.Key}) Changed";
|
||||
IEnumerable<string> change_descs() {
|
||||
if (old_price != new_price)
|
||||
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
if (old_size != new_size)
|
||||
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
|
||||
if (old_attached_target_key != new_attached_target_key)
|
||||
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
|
||||
}
|
||||
}
|
||||
|
||||
static string _header(uint key) => $"({key})";
|
||||
static string _header(uint key, SecurityImmutable? security) => $"({key}) {security.display_name()}";
|
||||
|
||||
static string _format_fill_summary(string header, OrderRecordImmutable before, OrderRecordImmutable after) {
|
||||
int fb = (int)before.State.FilledQuantity;
|
||||
int fa = (int)after.State.FilledQuantity;
|
||||
return $"{header} {after.Def.Side.past_tense()} {(fa - fb)} ({fa} total), still working {after.working_quantity()}";
|
||||
}
|
||||
|
||||
static string _format_status_change(string header, uint key, OrderStatus new_status) =>
|
||||
$"{header} Status changed to {new_status}";
|
||||
|
||||
static string _format_status_change(string header, uint key, OrderStatus old_status, OrderStatus new_status) =>
|
||||
$"{header} Status changed from {old_status} to {new_status}";
|
||||
|
||||
static string _format_order_completion(
|
||||
string header,
|
||||
OrderRecordImmutable order,
|
||||
ContractSpecificationImmutable? spec,
|
||||
TickDisplaySettingsImmutable qty_display) {
|
||||
var filled_qty_str = qty_display.format(order.filled_quantity());
|
||||
return $"{header} Completed: {order.side().past_tense()} {filled_qty_str}, " +
|
||||
$"@ {spec.spec_or_default().format(order.avg_price())} (avg)";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void orders_updated(TableUpdate<OrderRecordTuple, uint> update) {
|
||||
|
||||
foreach (var o in update.added) {
|
||||
if (!string.IsNullOrEmpty(o.order.State.LastMsg) && o.order.State.Created.ToDateTime().Date == DateTime.Today) {
|
||||
if (o.security.HasValue) {
|
||||
log_status(ActivityLogCategory.warn, o.security.Value, o.order.State.LastMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var order in update.changes) {
|
||||
var neworder = order.New.order;
|
||||
var oldorder = order.Old.order;
|
||||
var security = order.New.security ?? order.Old.security;
|
||||
//
|
||||
// description
|
||||
var mod_desc = modified_description(oldorder, neworder, security?.ContractSpec);
|
||||
if (!string.IsNullOrEmpty(mod_desc)) {
|
||||
log_status(ActivityLogCategory.modify, security, mod_desc);
|
||||
}
|
||||
//
|
||||
// filled quantity
|
||||
if (oldorder.State.FilledQuantity != neworder.State.FilledQuantity && !neworder.has_parent()) {
|
||||
log_status(ActivityLogCategory.fill, security, format_fill_summary(oldorder, neworder));
|
||||
}
|
||||
|
||||
//
|
||||
// status
|
||||
if (oldorder.State.Status != neworder.State.Status && !neworder.has_parent()) {
|
||||
log_status(ActivityLogCategory.modify, security,
|
||||
format_status_change(neworder.Key, oldorder.State.Status, neworder.State.Status));
|
||||
}
|
||||
|
||||
// last msg
|
||||
if (oldorder.State.LastMsg != neworder.State.LastMsg) {
|
||||
if (security.HasValue) {
|
||||
log_status(ActivityLogCategory.warn, security, neworder.State.LastMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void log_status(ILogViewerCategory category, SecurityImmutable? security, string msg) {
|
||||
_bus.post(SparkTags.LogActivity, new Activity { security = security, message = msg, category = category });
|
||||
}
|
||||
|
||||
internal void on_send_order(
|
||||
OrderDefinitionImmutable order, double price, double size, uint? key, string error) {
|
||||
var security = try_get_security(order.SecurityKey);
|
||||
log_status(ActivityLogCategory.order, security, key.HasValue ?
|
||||
format_order_success(key.Value, price, size, order.Side, security?.ContractSpec) :
|
||||
format_order_fail(price, size, order.Side, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void on_cancel_order(OrderRecordImmutable order, bool success, string error) {
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
log_status(ActivityLogCategory.cancel, security, success ?
|
||||
format_cancel_success(order, security?.ContractSpec) :
|
||||
format_cancel_error(order, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void update_order(
|
||||
OrderRecordImmutable order, double price, double size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "change from");
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, double size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "failed to change from");
|
||||
|
||||
void _log_order_update(
|
||||
OrderRecordImmutable order,
|
||||
double price,
|
||||
double size,
|
||||
bool algo_changed,
|
||||
string change_desc_prefix
|
||||
) {
|
||||
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
|
||||
// nothing actually changed
|
||||
return;
|
||||
}
|
||||
const string algo_change_text = "change algo parameters";
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var change_descs = new List<string>(1);
|
||||
|
||||
var price_display = TickUtils.get_tick(security, price).to_api_tick_display_settingsi();
|
||||
var price_qty_change_desc = OrdersExtensions.update_description(
|
||||
order, price, size, price_display);
|
||||
|
||||
if (!price_qty_change_desc.is_null_or_empty()) {
|
||||
change_descs.Add(price_qty_change_desc);
|
||||
}
|
||||
if (algo_changed) {
|
||||
change_descs.Add(algo_change_text);
|
||||
}
|
||||
|
||||
var desc = $"({order.Key}) {change_desc_prefix} {"; ".join(change_descs)}";
|
||||
log_status(ActivityLogCategory.modify, security, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Core;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using OrderPair = System.ValueTuple<bts.api.SecurityImmutable?, bts.api.orderman.OrderRecordImmutable>;
|
||||
using TickDisplaySettingsImmutable = bts.api.domain.TickDisplaySettingsImmutable;
|
||||
|
||||
namespace bts.spark {
|
||||
internal class Activity {
|
||||
public SecurityImmutable? security;
|
||||
public string message;
|
||||
public ILogViewerCategory category;
|
||||
}
|
||||
|
||||
class OrderActivitySummarizer : IDisposable {
|
||||
readonly IBus _bus;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security = new Dictionary<uint, SecurityMD>();
|
||||
IDisposable _dispose;
|
||||
static readonly IEqualityComparer<OrderRecordImmutable> _id_comparer =
|
||||
bts.utils.EqualityComparer.Create<OrderRecordImmutable, uint>(a => a.Key);
|
||||
|
||||
public OrderActivitySummarizer(IBus bus, IObservable<TableUpdate<OrderRecordTuple, uint>> orders) {
|
||||
_bus = bus;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(sec => _security = sec),
|
||||
orders.Subscribe(orders_updated));
|
||||
}
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
#region formatting
|
||||
|
||||
SecurityImmutable? try_get_security(uint security_key) {
|
||||
if (_security.TryGetValue(security_key, out SecurityMD md)) {
|
||||
return md.security;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static string format_order_success(
|
||||
uint key, double price, double size, Side side, ContractSpecificationImmutable? spec) =>
|
||||
$"({key}) {side.ToString().ToLower()} {size} @ {spec.format(price)}";
|
||||
|
||||
string format_order_fail(
|
||||
double price,
|
||||
double size,
|
||||
Side side,
|
||||
ContractSpecificationImmutable? spec,
|
||||
string error) =>
|
||||
$"Failed to {side.ToString().ToLower()} {size} @ {spec.format(price)}. {error}";
|
||||
|
||||
string format_cancel_success(OrderRecordImmutable order, ContractSpecificationImmutable? spec) {
|
||||
return $"({order.Key}) Canceled {order.short_description(spec)}, was working {order.working_quantity()}";
|
||||
}
|
||||
string format_cancel_error(OrderRecordImmutable order, ContractSpecificationImmutable? spec, string error) {
|
||||
return $"({order.Key}) Failed to cancel {order.short_description(spec)}: {error}";
|
||||
}
|
||||
public static string format_fill_summary(OrderRecordImmutable before, OrderRecordImmutable after) =>
|
||||
_format_fill_summary(_header(after.Key), before, after);
|
||||
|
||||
public static string format_fill_summary(OrderRecordImmutable before, OrderRecordImmutable after, SecurityImmutable? security) =>
|
||||
_format_fill_summary(_header(after.Key, security), before, after);
|
||||
|
||||
public static string format_status_change(uint key, OrderStatus old_status, OrderStatus new_status) =>
|
||||
_format_status_change(_header(key), key, old_status, new_status);
|
||||
|
||||
public static string format_status_change(uint key, OrderStatus old_status, OrderStatus new_status, SecurityImmutable? security) =>
|
||||
_format_status_change(_header(key, security), key, old_status, new_status);
|
||||
|
||||
// since changes are naggled, transition changes aren't always detected (e.g. a new order changes before observers are notified of its existence), so old_status might be null.
|
||||
public static string format_status_change(uint key, OrderStatus? old_status, OrderStatus new_status, SecurityImmutable? security) {
|
||||
string header = _header(key, security);
|
||||
return old_status.HasValue ?
|
||||
_format_status_change(header, key, old_status.Value, new_status) :
|
||||
_format_status_change(header, key, new_status);
|
||||
}
|
||||
|
||||
public static string order_completion_debrief(
|
||||
OrderRecordImmutable order,
|
||||
SecurityImmutable? security,
|
||||
TickDisplaySettingsImmutable qty_display) =>
|
||||
_format_order_completion(
|
||||
_header(order.Key, security), order, security?.ContractSpec, qty_display);
|
||||
|
||||
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
|
||||
double old_price = old_order.State.Price, new_price = new_order.State.Price;
|
||||
double old_size = old_order.State.quantity();
|
||||
double new_size = new_order.State.quantity();
|
||||
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
|
||||
return StringBuilderEx.join(prefix, change_descs());
|
||||
//
|
||||
string prefix() => $"({old_order.Key}) Changed";
|
||||
IEnumerable<string> change_descs() {
|
||||
if (old_price != new_price)
|
||||
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
if (old_size != new_size)
|
||||
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
|
||||
if (old_attached_target_key != new_attached_target_key)
|
||||
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
|
||||
}
|
||||
}
|
||||
|
||||
static string _header(uint key) => $"({key})";
|
||||
static string _header(uint key, SecurityImmutable? security) => $"({key}) {security.display_name()}";
|
||||
|
||||
static string _format_fill_summary(string header, OrderRecordImmutable before, OrderRecordImmutable after) {
|
||||
int fb = (int)before.State.FilledQuantity;
|
||||
int fa = (int)after.State.FilledQuantity;
|
||||
return $"{header} {after.Def.Side.past_tense()} {(fa - fb)} ({fa} total), still working {after.working_quantity()}";
|
||||
}
|
||||
|
||||
static string _format_status_change(string header, uint key, OrderStatus new_status) =>
|
||||
$"{header} Status changed to {new_status}";
|
||||
|
||||
static string _format_status_change(string header, uint key, OrderStatus old_status, OrderStatus new_status) =>
|
||||
$"{header} Status changed from {old_status} to {new_status}";
|
||||
|
||||
static string _format_order_completion(
|
||||
string header,
|
||||
OrderRecordImmutable order,
|
||||
ContractSpecificationImmutable? spec,
|
||||
TickDisplaySettingsImmutable qty_display) {
|
||||
var filled_qty_str = qty_display.format(order.filled_quantity());
|
||||
return $"{header} Completed: {order.side().past_tense()} {filled_qty_str}, " +
|
||||
$"@ {spec.spec_or_default().format(order.avg_price())} (avg)";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void orders_updated(TableUpdate<OrderRecordTuple, uint> update) {
|
||||
|
||||
foreach (var o in update.added) {
|
||||
if (!string.IsNullOrEmpty(o.order.State.LastMsg) && o.order.State.Created.ToDateTime().Date == DateTime.Today) {
|
||||
if (o.security.HasValue) {
|
||||
log_status(ActivityLogCategory.warn, o.security.Value, o.order.State.LastMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var order in update.changes) {
|
||||
var neworder = order.New.order;
|
||||
var oldorder = order.Old.order;
|
||||
var security = order.New.security ?? order.Old.security;
|
||||
//
|
||||
// description
|
||||
var mod_desc = modified_description(oldorder, neworder, security?.ContractSpec);
|
||||
if (!string.IsNullOrEmpty(mod_desc)) {
|
||||
log_status(ActivityLogCategory.modify, security, mod_desc);
|
||||
}
|
||||
//
|
||||
// filled quantity
|
||||
if (oldorder.State.FilledQuantity != neworder.State.FilledQuantity && !neworder.has_parent()) {
|
||||
log_status(ActivityLogCategory.fill, security, format_fill_summary(oldorder, neworder));
|
||||
}
|
||||
|
||||
//
|
||||
// status
|
||||
if (oldorder.State.Status != neworder.State.Status && !neworder.has_parent()) {
|
||||
log_status(ActivityLogCategory.modify, security,
|
||||
format_status_change(neworder.Key, oldorder.State.Status, neworder.State.Status));
|
||||
}
|
||||
|
||||
// last msg
|
||||
if (oldorder.State.LastMsg != neworder.State.LastMsg) {
|
||||
if (security.HasValue) {
|
||||
log_status(ActivityLogCategory.warn, security, neworder.State.LastMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void log_status(ILogViewerCategory category, SecurityImmutable? security, string msg) {
|
||||
_bus.post(SparkTags.LogActivity, new Activity { security = security, message = msg, category = category });
|
||||
}
|
||||
|
||||
internal void on_send_order(
|
||||
OrderDefinitionImmutable order, double price, double size, uint? key, string error) {
|
||||
var security = try_get_security(order.SecurityKey);
|
||||
log_status(ActivityLogCategory.order, security, key.HasValue ?
|
||||
format_order_success(key.Value, price, size, order.Side, security?.ContractSpec) :
|
||||
format_order_fail(price, size, order.Side, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void on_cancel_order(OrderRecordImmutable order, bool success, string error) {
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
log_status(ActivityLogCategory.cancel, security, success ?
|
||||
format_cancel_success(order, security?.ContractSpec) :
|
||||
format_cancel_error(order, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void update_order(
|
||||
OrderRecordImmutable order, double price, double size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "change from");
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, double size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "failed to change from");
|
||||
|
||||
void _log_order_update(
|
||||
OrderRecordImmutable order,
|
||||
double price,
|
||||
double size,
|
||||
bool algo_changed,
|
||||
string change_desc_prefix
|
||||
) {
|
||||
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
|
||||
// nothing actually changed
|
||||
return;
|
||||
}
|
||||
const string algo_change_text = "change algo parameters";
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var change_descs = new List<string>(1);
|
||||
|
||||
var price_display = TickUtils.get_tick(security, price).to_api_tick_display_settingsi();
|
||||
var price_qty_change_desc = OrdersExtensions.update_description(
|
||||
order, price, size, price_display);
|
||||
|
||||
if (!price_qty_change_desc.is_null_or_empty()) {
|
||||
change_descs.Add(price_qty_change_desc);
|
||||
}
|
||||
if (algo_changed) {
|
||||
change_descs.Add(algo_change_text);
|
||||
}
|
||||
|
||||
var desc = $"({order.Key}) {change_desc_prefix} {"; ".join(change_descs)}";
|
||||
log_status(ActivityLogCategory.modify, security, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
|
||||
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
|
||||
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DefaultButton}">
|
||||
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
@@ -53,8 +53,6 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="140"/>
|
||||
@@ -77,6 +75,7 @@
|
||||
RegionalTick="{Binding price_tick}"
|
||||
Focusable="false"
|
||||
TickDefaultValue="{Binding current_price, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
|
||||
ToolTip="Price"
|
||||
Value="{Binding price}"
|
||||
/>
|
||||
<c:TickUpDownNullableDouble
|
||||
@@ -88,6 +87,7 @@
|
||||
Increment="{Binding size_calculator.unit_quantity}"
|
||||
Minimum="0"
|
||||
TickDisplaySettings="{Binding size_calculator.display_settings}"
|
||||
ToolTip="Size"
|
||||
Value="{Binding size_calculator.display_value}"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -97,13 +97,24 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Button
|
||||
Command="{Binding set_price_command}"
|
||||
CommandParameter="{Binding bbo.bid}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
FontWeight="Bold"
|
||||
ToolTip="Bid Price">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Command="{Binding set_price_command}"
|
||||
CommandParameter="{Binding bbo.ask}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
FontWeight="Bold"
|
||||
ToolTip="Ask Price">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
@@ -120,7 +131,7 @@
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.bid_size}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
>
|
||||
ToolTip="Bid Size">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding Path="bbo.bid_size" />
|
||||
<Binding Path="qty_display" />
|
||||
@@ -131,20 +142,20 @@
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.ask_size}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
>
|
||||
ToolTip="Ask Size">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding Path="bbo.ask_size" />
|
||||
<Binding Path="qty_display" />
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.RowSpan="3">
|
||||
<Grid Grid.Row="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl Margin="-2,-2,-2,0" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
|
||||
<ItemsControl Margin="0,0,-4,-4" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="3" Focusable="False"/>
|
||||
@@ -153,11 +164,11 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Margin="2,4,2,0"
|
||||
Margin="0,0,4,4"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
>
|
||||
ToolTip="Add to Size">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding />
|
||||
@@ -170,35 +181,35 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button Grid.Row="2" Command="{Binding size_calculator.clear}">Clear</Button>
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding size_calculator.clear}"
|
||||
Content="Clear"
|
||||
ToolTip="Clear Size" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.Column="2" Grid.RowSpan="3">
|
||||
<Grid Grid.Row="4" Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox
|
||||
Grid.ColumnSpan="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding account_dirty, FallbackValue={u:False}}"
|
||||
IsEnabled="{Binding can_change_account, FallbackValue={u:True}}"
|
||||
ItemsSource="{Binding properties_vm.accounts}"
|
||||
Margin="0,2"
|
||||
SelectedItem="{Binding properties_vm.selected_account}"
|
||||
/>
|
||||
ToolTip="Account" />
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding time_in_force_dirty, FallbackValue={u:False}}"
|
||||
IsEnabled="{Binding can_change_time_in_force, FallbackValue={u:True}}"
|
||||
ItemsSource="{Binding properties_vm.time_in_forces}"
|
||||
Margin="0,2"
|
||||
SelectedItem="{Binding properties_vm.selected_time_in_force}"
|
||||
/>
|
||||
ToolTip="Time In Force" />
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="2"
|
||||
Margin="0,2"
|
||||
Grid.Row="4"
|
||||
DataContext="{Binding properties_vm}"
|
||||
usp:OrderModificationControl.IsDirty="{Binding DataContext.order_type_dirty, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:False}}"
|
||||
HasEditableProperties="{Binding can_expand, FallbackValue={u:False}}"
|
||||
@@ -207,7 +218,7 @@
|
||||
IsOpen="{Binding is_expanded, FallbackValue={u:False}}"
|
||||
ItemsSource="{Binding algo_definitions_view}"
|
||||
SelectedItem="{Binding selected_algo_definition}"
|
||||
>
|
||||
ToolTip="Order/Algo Type">
|
||||
<uag:OrderDetailsPopupControl.Style>
|
||||
<Style TargetType="uag:OrderDetailsPopupControl" BasedOn="{StaticResource {x:Type uag:OrderDetailsPopupControl}}">
|
||||
<Style.Triggers>
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace mbts.spark {
|
||||
new PropertyValidation<AccountTuple?>(
|
||||
a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account"))
|
||||
});
|
||||
attach(_accounts, accounts_src.Select(accs => accs.Values.ToList()), inline_account_src);
|
||||
attach(_accounts, accounts_src.Select(accs => accs.Values.ToListSorted()), inline_account_src);
|
||||
|
||||
// time in force
|
||||
_time_in_force = new SelectorProperty<TimeInForce>(
|
||||
|
||||
@@ -1,161 +1,161 @@
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.OrderTicket"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}"
|
||||
>
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
|
||||
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
|
||||
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
|
||||
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Text="Sell" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{u:BTSColor MarketData.ask}"
|
||||
Padding="2"
|
||||
Height="Auto"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Text="Buy" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{u:BTSColor MarketData.bid}"
|
||||
Padding="2"
|
||||
Height="Auto"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
|
||||
</Window.InputBindings>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3" />
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
|
||||
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
|
||||
</Grid>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Style="{StaticResource TextBlock.Dark}">
|
||||
<TextBlock.Inlines>
|
||||
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="bid_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="ask_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
</Window>
|
||||
<Window
|
||||
x:Class="mbts.ui.spark.OrderTicket"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:msys="clr-namespace:bts.api;assembly=spark_api"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
u:WindowStyle.SizeToContent="WidthAndHeight"
|
||||
u:WindowStyle.TitleContent="{Binding bbo, Mode=OneWay}"
|
||||
Style="{StaticResource BTSWindowTitleTemplate}"
|
||||
>
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="HorizontalSpacing">8.0</sys:Double>
|
||||
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
|
||||
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
|
||||
<Style x:Key="SellButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.ask_dark}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Text="Sell" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{u:BTSColor MarketData.ask}"
|
||||
Padding="2"
|
||||
Height="Auto"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99c00000" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.bid_dark}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="MinHeight" Value="40" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Content">
|
||||
<Setter.Value>
|
||||
<TextBlock VerticalAlignment="Center" Text="Buy" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
CornerRadius="0"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{u:BTSColor UI.Button.normal_border}"
|
||||
Background="{u:BTSColor MarketData.bid}"
|
||||
Padding="2"
|
||||
Height="Auto"
|
||||
SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
Name="button_content"
|
||||
/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#99167ed3" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{u:BTSColor UI.Button.mouseover_border}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Esc" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
|
||||
</Window.InputBindings>
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition MinWidth="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:OrderModificationControl Grid.ColumnSpan="3" />
|
||||
<Button Grid.Row="2" Style="{StaticResource BuyButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Buy, Type=msys:Side}" />
|
||||
<Button Grid.Row="2" Grid.Column="2" Style="{StaticResource SellButton}" Command="{Binding send_order}" CommandParameter="{u:Enum Sell, Type=msys:Side}" />
|
||||
</Grid>
|
||||
<u:WindowStyle.TitleContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Style="{StaticResource TextBlock.Dark}">
|
||||
<TextBlock.Inlines>
|
||||
<Run FontWeight="Bold" Text="{Binding security, Mode=OneWay}" />
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="bid_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Run.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
<Run Foreground="{u:BTSColor MarketData.ask}" FontSize="10">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}" StringFormat="({0})">
|
||||
<Binding Path="ask_size" Mode="OneWay"/>
|
||||
<Binding Path="DataContext.qty_display" RelativeSource="{RelativeSource AncestorType=usp:OrderTicket}"/>
|
||||
</MultiBinding>
|
||||
</Run>
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</u:WindowStyle.TitleContentTemplate>
|
||||
</Window>
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace mbts.spark {
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
@@ -398,7 +398,7 @@
|
||||
$"Expected rounded qty to be real, non-negative. " +
|
||||
$"qty: {qty}; rounded_qty: {rounded_qty}; display: {display})");
|
||||
var iqty = Math.Floor(rounded_qty);
|
||||
return ((uint)iqty, new() { Fraction = Math.Round(rounded_qty - iqty, 2) });
|
||||
return ((uint)iqty, new() { Fraction = Math.Round(rounded_qty - iqty, 6) });
|
||||
}
|
||||
|
||||
private CustomOrderDefinitionImmutable? _create_custom_def_for_stop_and_stop_limit(OrderRecordImmutable order, ref double stop_price) {
|
||||
@@ -662,7 +662,7 @@
|
||||
if (o.State.Price == price) return true;
|
||||
var wq = o.working_quantity();
|
||||
if (wq > 0) {
|
||||
return await orders_model.update_order(o, price, o.State.Quantity);
|
||||
return await orders_model.update_order(o, price, o.State.quantity());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -64,9 +64,5 @@ namespace System.Reactive.Linq {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static IObservable<TResult> SelectInline<T, TResult>(
|
||||
this IObservable<T> source, Func<T, TResult> selector) =>
|
||||
Observable.Create<TResult>(o => source.Subscribe(v => o.OnNext(selector(v))));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 680 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user