Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
No files matched your search
@@ -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,47 @@
|
||||
# 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__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
|
||||
@@ -1,8 +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()
|
||||
|
||||
@@ -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,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
|
||||
@@ -0,0 +1,63 @@
|
||||
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-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: 8069cc893f...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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -12,8 +12,13 @@ namespace bts.shell.market_view {
|
||||
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; }
|
||||
|
||||
@@ -128,6 +133,7 @@ namespace bts.shell.market_view {
|
||||
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
@@ -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);
|
||||
@@ -124,7 +131,7 @@ namespace mbts.spark {
|
||||
|
||||
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
|
||||
@@ -391,7 +398,7 @@ namespace mbts.spark {
|
||||
|
||||
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));
|
||||
@@ -538,6 +545,7 @@ 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(),
|
||||
@@ -584,6 +592,7 @@ 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);
|
||||
@@ -620,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) =>
|
||||
|
||||
@@ -8,6 +8,7 @@ using mbts.ui.controls;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
@@ -91,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 {
|
||||
@@ -121,7 +122,7 @@ namespace bts.shell.fills {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -277,9 +277,7 @@ namespace mbts.spark {
|
||||
var orders_source = settings.Select(s => s.ticks_per_row).DistinctUntilChanged()
|
||||
.SelectSeries(tpr => {
|
||||
var spec = TickUtils.multiply_tick_size(security.regional_ticks(), tpr);
|
||||
var price_comp = Comparer<double>.Create((a, b) =>
|
||||
Comparer<long?>.Default.Compare(spec.count_ticks(a), spec.count_ticks(b)));
|
||||
return _order_price_source(model.orders, price_comp, p => spec.round_to_tick(p, RoundingMode.AbsDown));
|
||||
return _order_price_source(model.orders, p => spec.round_to_tick(p, RoundingMode.AbsDown));
|
||||
});
|
||||
_day_change.Changed += (o, n) => RaisePropertyChanged(
|
||||
nameof(day_change_font_color), nameof(day_change_background_color));
|
||||
@@ -806,10 +804,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
static IObservable<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordTuple>> price_index, IReadOnlyDictionary<uint, OrderRecordTuple> map)> _order_price_source(
|
||||
IObservable<TableUpdate<OrderRecordTuple, uint>> source,
|
||||
IComparer<double> price_comp,
|
||||
Func<double, double> normalize_price
|
||||
) {
|
||||
IObservable<TableUpdate<OrderRecordTuple, uint>> source, Func<double, double> normalize_price) {
|
||||
var price_comp = MathUtils.error_margin_comparer(1E-9);
|
||||
return Observable.Create<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordTuple>>, IReadOnlyDictionary<uint, OrderRecordTuple>)>(o => {
|
||||
var state = ImmutableSortedDictionary.Create<double, ImmutableDictionary<uint, OrderRecordTuple>>(price_comp);
|
||||
var sub = source.Subscribe(table => {
|
||||
@@ -853,15 +849,18 @@ namespace mbts.spark {
|
||||
return sub;
|
||||
});
|
||||
|
||||
bool price_changed(Change<OrderRecordTuple> ch) =>
|
||||
price_comp.Compare(ch.New.order.ui_price(), ch.Old.order.ui_price()) != 0;
|
||||
|
||||
double get_price(OrderRecordTuple order) {
|
||||
var p = order.order.ui_price();
|
||||
if (!(normalize_price is null))
|
||||
p = normalize_price(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool price_changed(Change<OrderRecordTuple> ch) {
|
||||
var oldp = get_price(ch.Old);
|
||||
var newp = get_price(ch.New);
|
||||
return price_comp.Compare(oldp, newp) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
double _normalize_price(double price) =>
|
||||
|
||||
@@ -626,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) });
|
||||
@@ -701,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)
|
||||
@@ -1424,7 +1448,7 @@ namespace mbts.spark {
|
||||
double best_ask = _get_top_price(_asks);
|
||||
var v = best_bid
|
||||
.if_nan(_spec.tickdown(best_ask))
|
||||
.if_nan(_vm.settlement);
|
||||
.if_nan(_vm?.settlement ?? double.NaN);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
@@ -1432,11 +1456,14 @@ namespace mbts.spark {
|
||||
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);
|
||||
var v = _spec.tickdown(best_ask)
|
||||
.if_nan(best_bid)
|
||||
.if_nan(_vm.settlement);
|
||||
.if_nan(_vm?.settlement ?? double.NaN);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
@@ -1446,15 +1473,15 @@ namespace mbts.spark {
|
||||
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);
|
||||
.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);
|
||||
@@ -1475,7 +1502,7 @@ 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) {
|
||||
@@ -1551,15 +1578,17 @@ 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);
|
||||
_render_grid_background();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace mbts.spark {
|
||||
// notional value
|
||||
Add(notional_value_column = new NotionalValueColumn(
|
||||
new DoubleMinMax<OrderRecordTupleWithRoot>(
|
||||
ort => ort.order.order.notional_value(), nameof(OrderBookRowVM.notional_value))));
|
||||
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
|
||||
@@ -236,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,
|
||||
@@ -371,6 +375,7 @@ namespace mbts.spark {
|
||||
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; }
|
||||
@@ -399,6 +404,7 @@ namespace mbts.spark {
|
||||
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(),
|
||||
@@ -423,6 +429,7 @@ namespace mbts.spark {
|
||||
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);
|
||||
@@ -654,7 +661,14 @@ namespace mbts.spark {
|
||||
|
||||
public class NotionalValueColumn : OrderbookColumnBase<DoubleMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public NotionalValueColumn(IDataGridColumnPersistentFilter<OrderRecordTupleWithRoot, DoubleMinMaxState> filter)
|
||||
: base(Columns.NotionalValue, DefaultColumnHeaders.NotionalValue, r => r.notional_value_text, DefaultColumnWidths.Qty, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
: 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(
|
||||
@@ -690,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;
|
||||
|
||||
@@ -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,7 +109,7 @@ 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.notional_value();
|
||||
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;
|
||||
@@ -130,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;
|
||||
@@ -357,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)
|
||||
@@ -523,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,165 +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(26)]
|
||||
public DoubleMinMaxState notional_value;
|
||||
[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) &&
|
||||
notional_value.safe_equals(other.notional_value) &&
|
||||
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,
|
||||
notional_value?.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_numeric(fs.notional_value, o => o.order.order.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.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,271 +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 notional_value => new GridColumnSettings(Columns.Size.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 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,108 +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,
|
||||
NotionalValue = 1 & (1 << 1),
|
||||
}
|
||||
|
||||
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,527 +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?.normal_expirations()?.MinOrDefault(DateTime.MinValue) ?? 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 &&
|
||||
|
||||
@@ -137,6 +137,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(
|
||||
@@ -211,13 +215,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 +241,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));
|
||||
@@ -392,10 +415,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 +455,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 +502,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 +563,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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -570,6 +570,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 +582,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 +602,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 +638,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 +650,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 +670,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 +803,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 +851,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 +878,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 +930,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 +980,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 +1092,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 +1180,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 +1230,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 +1260,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 +1328,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 +1356,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 +1395,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 +1406,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 +1505,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 +1517,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 +1533,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,7 @@ namespace mbts.analytics {
|
||||
if (tl.tl_row.size > 0) {
|
||||
start_long_pos += tl.tl_row.size;
|
||||
start_long_total_cost += cost;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
start_short_pos += Math.Abs(tl.tl_row.size);
|
||||
start_short_total_cost += cost;
|
||||
}
|
||||
@@ -78,8 +77,7 @@ namespace mbts.analytics {
|
||||
if (tl.tl_row.size > 0) {
|
||||
day_long_pos += tl.tl_row.size;
|
||||
day_long_total_cost += cost;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
day_short_pos += Math.Abs(tl.tl_row.size);
|
||||
day_short_total_cost += cost;
|
||||
}
|
||||
@@ -165,6 +163,13 @@ namespace mbts.analytics {
|
||||
is_day_trade = _is_day_trade(tl)
|
||||
};
|
||||
pnl_trades.Add(pnl_tl);
|
||||
} else if (tl.security_type == SecurityType.Spot) {
|
||||
PNLTradeRow pnl_tl = new PNLTradeRow() {
|
||||
tl_row = tl,
|
||||
md = tl_md,
|
||||
is_day_trade = _is_day_trade(tl)
|
||||
};
|
||||
pnl_trades.Add(pnl_tl);
|
||||
}
|
||||
}
|
||||
return pnl_trades;
|
||||
|
||||
@@ -4,6 +4,9 @@ using bts.api;
|
||||
|
||||
using BCG;
|
||||
using mbts.co2;
|
||||
using System.Xml.Serialization;
|
||||
using ZstdSharp.Unsafe;
|
||||
using bts.proto;
|
||||
|
||||
namespace mbts.analytics {
|
||||
// Pairs ITradeLogRow along with the data (theo, md) that is needed to compute
|
||||
@@ -18,20 +21,88 @@ namespace mbts.analytics {
|
||||
|
||||
public SecurityImmutable security { get { return md.security; } }
|
||||
|
||||
private double _option_pnl(double trade_price, double now_price) {
|
||||
double coin_terms = (now_price - trade_price) * tl_row.size;
|
||||
double usd_terms = coin_terms * driver;
|
||||
return usd_terms;
|
||||
// private double _option_pnl(double trade_price, double now_price) {
|
||||
// double coin_terms = (now_price - trade_price) * tl_row.size;
|
||||
// double usd_terms = coin_terms * driver;
|
||||
// return usd_terms;
|
||||
// }
|
||||
|
||||
private bool _is_notional {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private double _future_pnl(double trade_price, double now_price) {
|
||||
// the qty is in USD, so this is the initial cash flow
|
||||
double init_asset_value = tl_row.size;
|
||||
double asset_size = init_asset_value / tl_row.price;
|
||||
double now_asset_value = asset_size * now_price;
|
||||
return now_asset_value - init_asset_value;
|
||||
private bool _is_inverse_option_contract {
|
||||
get {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private double _cpp {
|
||||
get {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _is_reverse_option_contract {
|
||||
get {
|
||||
// check for Deribit options
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private double _option_pnl {
|
||||
get {
|
||||
if (_is_notional) {
|
||||
var now_price = theo;
|
||||
double coin_terms = (now_price - tl_row.price) * tl_row.size;
|
||||
double usd_terms = coin_terms * driver;
|
||||
return usd_terms;
|
||||
} else {
|
||||
return _cpp * tl_row.size * (theo - tl_row.price);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double _spot_pnl {
|
||||
get {
|
||||
// both are computed the same way
|
||||
return _future_pnl;
|
||||
}
|
||||
}
|
||||
|
||||
private double _future_pnl {
|
||||
get {
|
||||
if (_is_notional) {
|
||||
// TODO:
|
||||
// let it fall through right now
|
||||
// the qty is in USD, so this is the initial cash flow
|
||||
var now_price = theo;
|
||||
double init_asset_value = tl_row.size;
|
||||
double asset_size = init_asset_value / tl_row.price;
|
||||
double now_asset_value = asset_size * now_price;
|
||||
return now_asset_value - init_asset_value;
|
||||
} else {
|
||||
// TODO: is position pnl, compute it wrt settlement price?
|
||||
return tl_row.size * (theo - tl_row.price);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private double _future_pnl(double trade_price, double now_price) {
|
||||
// if (_is_notional) {
|
||||
// // the qty is in USD, so this is the initial cash flow
|
||||
// double init_asset_value = tl_row.size;
|
||||
// double asset_size = init_asset_value / tl_row.price;
|
||||
// double now_asset_value = asset_size * now_price;
|
||||
// return now_asset_value - init_asset_value;
|
||||
// } else {
|
||||
// return tl_row.size * (theo - tl_row.price);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
public double driver {
|
||||
get {
|
||||
if (tl_row.security_type == SecurityType.Option) {
|
||||
@@ -44,35 +115,46 @@ namespace mbts.analytics {
|
||||
}
|
||||
public double theo {
|
||||
get {
|
||||
if (tl_row.security_type == SecurityType.Option) {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
|
||||
} else if (tl_row.security_type == SecurityType.Future) {
|
||||
return fut_theo.theo;
|
||||
switch (tl_row.security_type) {
|
||||
// TODO:
|
||||
case SecurityType.Option:
|
||||
if (_is_inverse_option_contract) {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.fwd_normalized_call : opt_theo.fwd_normalized_put;
|
||||
} else {
|
||||
return tl_row.call_put == PutOrCall.Call ?
|
||||
opt_theo.call : opt_theo.put;
|
||||
}
|
||||
// future and options, just get mid market
|
||||
case SecurityType.Future:
|
||||
//return fut_theo.theo;
|
||||
case SecurityType.Spot:
|
||||
var bbo = top_of_book;
|
||||
return (bbo.Item1 + bbo.Item2) / 2;
|
||||
default:
|
||||
return Double.NaN;
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
}
|
||||
public double pnl {
|
||||
get {
|
||||
double rv = Double.NaN;
|
||||
if (is_day_trade) {
|
||||
if (tl_row.security_type == SecurityType.Future) {
|
||||
rv = _future_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Option) {
|
||||
rv = _option_pnl(tl_row.price, theo);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
// use settle price ?
|
||||
if (tl_row.security_type == SecurityType.Future) {
|
||||
rv = _future_pnl(tl_row.price, theo);
|
||||
} else if (tl_row.security_type == SecurityType.Option) {
|
||||
rv = _option_pnl(tl_row.price, theo);
|
||||
switch (tl_row.security_type) {
|
||||
case SecurityType.Option:
|
||||
rv = _option_pnl;
|
||||
break;
|
||||
case SecurityType.Future:
|
||||
rv = _future_pnl;
|
||||
break;
|
||||
case SecurityType.Spot:
|
||||
// = tl_row.size * (theo - tl_row.price);
|
||||
rv = _spot_pnl;
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
public double settle_price {
|
||||
get {
|
||||
return md.md == null ? Double.NaN : md.md.settlement_price;
|
||||
|
||||
@@ -36,6 +36,10 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static bool is_unexpired(bts.api.SecurityTuple st, DateTime exp_date) {
|
||||
if (st.Venue == Venue.Cboe || st.Venue == Venue.Glbx) {
|
||||
// server is already getting teh righ contracts
|
||||
return true;
|
||||
}
|
||||
TimeSpan exp_time = get_exp_time(st);
|
||||
DateTime full_exp_utc = exp_date.ToUniversalTime().Date.Add(exp_time);
|
||||
DateTime now_utc = DateTime.UtcNow;
|
||||
@@ -43,6 +47,8 @@ namespace mbts {
|
||||
}
|
||||
|
||||
public static bool is_unexpired(bts.api.SecurityImmutable sec) {
|
||||
if (sec.security_tuple().Venue == Venue.Cboe || sec.security_tuple().Venue == Venue.Glbx)
|
||||
return true;
|
||||
var sec_type = sec.security_type();
|
||||
switch (sec_type) {
|
||||
case bts.api.SecurityType.Future:
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
#nullable enable
|
||||
using bts.collections;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.shell;
|
||||
|
||||
static class ShellWindowSettingsDirectoryStore {
|
||||
public static ShellWindowSettingsDirectoryStore<T> Create<T>(string path, string searchPattern) =>
|
||||
new(path, searchPattern);
|
||||
}
|
||||
|
||||
class ShellWindowSettingsDirectoryStore<T> {
|
||||
readonly LazyOnce<Dictionary<string, SingleFileStore>> profiles;
|
||||
|
||||
public ShellWindowSettingsDirectoryStore(string path, string searchPattern) =>
|
||||
profiles = new(() => {
|
||||
try {
|
||||
return Directory.EnumerateFiles(path, searchPattern).Select(SingleFileStore.FromPath).ToDictionary(sfs => sfs.Name);
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Failed to enumerate {typeof(T)} settings files in '{path}'. {ex}");
|
||||
}
|
||||
return new(0);
|
||||
});
|
||||
|
||||
public IReadOnlyDictionary<string, T?> Profiles =>
|
||||
MappedDictionary.create(profiles.Value, sfs => sfs.Value);
|
||||
|
||||
class SingleFileStore {
|
||||
LazyOnce<T?> value;
|
||||
|
||||
public SingleFileStore(string path) {
|
||||
Name = Path.GetFileNameWithoutExtension(path);
|
||||
value = new(LoadValue);
|
||||
|
||||
T? LoadValue() {
|
||||
try {
|
||||
using var f = File.OpenRead(path);
|
||||
return ProtobufSerializationHelper.deserialize<T>(f);
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.error($"Failed to load {typeof(T)} file '{path}'. {ex}");
|
||||
}
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static SingleFileStore FromPath(string path) =>
|
||||
new(path);
|
||||
|
||||
public string Name { get; }
|
||||
public T? Value => value.Value;
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,10 @@
|
||||
<EmbeddedResource Include="Sounds/whistle.wav" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="ShellWindowSettings/**" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent" />
|
||||
|
||||
</Project>
|
||||
@@ -143,9 +143,21 @@ namespace BCG {
|
||||
return tp ?? mbts.spark.TheoPoint.getNulledObject();
|
||||
}
|
||||
|
||||
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price(DateTime option_expiration, double strike, bool is_call, OptionTheoInterpolationData sparse_theo_map, double driver_price) {
|
||||
mbts.spark.TheoPoint tp = sparse_theo_map == null ? mbts.spark.TheoPoint.getNulledObject() : sparse_theo_map.interpolate_theos(option_expiration, strike, driver_price);
|
||||
return tp ?? mbts.spark.TheoPoint.getNulledObject();
|
||||
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price(DateTime option_expiration, double strike, bool is_call, OptionTheoInterpolationData sparse_theo_map,
|
||||
ExpStrikeKeyedData<mbts.spark.TheoPoint> live_symbol_theo_map, double driver_price) {
|
||||
// mbts.spark.TheoPoint tp = sparse_theo_map == null ? mbts.spark.TheoPoint.getNulledObject() : sparse_theo_map.interpolate_theos(option_expiration, strike, driver_price);
|
||||
// return tp ?? mbts.spark.TheoPoint.getNulledObject();
|
||||
// TOD: config here
|
||||
return _compute_single_option_leg_at_tied_price_from_live(option_expiration, strike, is_call, live_symbol_theo_map, driver_price);
|
||||
}
|
||||
|
||||
private static mbts.spark.TheoPoint _compute_single_option_leg_at_tied_price_from_live(DateTime option_expiration, double strike, bool is_call,
|
||||
ExpStrikeKeyedData<mbts.spark.TheoPoint> live_symbol_theo_map, double driver_price) {
|
||||
var tp = live_symbol_theo_map.get_data(option_expiration, strike);
|
||||
if (tp == null)
|
||||
return mbts.spark.TheoPoint.getNulledObject();
|
||||
return TheoPoint.extrapolate(tp, driver_price, is_call);
|
||||
|
||||
}
|
||||
|
||||
/// this is the only relatively tricky thing here
|
||||
@@ -175,7 +187,7 @@ namespace BCG {
|
||||
if (leg_price_spec.primaryFrozen) /// tied
|
||||
{
|
||||
effective_driver = leg_price_spec.primary;
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, leg_price_spec.primary);
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, live_symbol_theo_map, leg_price_spec.primary);
|
||||
} else {
|
||||
mbts.spark.TheoPoint tp = _compute_single_option_leg_at_live_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, live_symbol_theo_map);
|
||||
effective_driver = tp.driver;
|
||||
@@ -189,7 +201,7 @@ namespace BCG {
|
||||
future_theo_map.TryGetValue(driver_price_info.first, out symbol_future_theo_map);
|
||||
double driver_price = StatelessFutureTheoCalculator.transform_price(symbol_future_theo_map, leg_price_spec.driverExpiration, leg_price_spec.primary, driver_price_info.second);
|
||||
effective_driver = leg_price_spec.primary;
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, driver_price);
|
||||
return _compute_single_option_leg_at_tied_price(spread_option_leg.contract.expiration, spread_option_leg.contract.strike, spread_option_leg.contract.isCall, symbol_sparse_theo_map, live_symbol_theo_map, driver_price);
|
||||
} else {
|
||||
bts.Log.Ui.error(String.Format("This spread is live but the driver expiration {0} is different from the system driver expiration {1} for {2}",
|
||||
spread_option_leg.legPriceSpec.driverExpiration, driver_price_info.second, spread_option_leg.contract.product));
|
||||
|
||||
@@ -216,8 +216,10 @@ namespace mbts.ui {
|
||||
public static BTSColor ask_text => BTSCoreColors.red_dark;
|
||||
public static BTSColor bid_text => BTSCoreColors.blue;
|
||||
public static BTSColor bid => 0xff76b7fa;
|
||||
public static BTSColor bid_dark => 0xff26239a;
|
||||
public static BTSColor bid_alt => BTSCoreColors.blue_light.with(0x70);
|
||||
public static BTSColor ask => 0xffff6666;
|
||||
public static BTSColor ask_dark => 0xff7a2331;
|
||||
public static BTSColor ask_alt => 0x70ffb4b4;
|
||||
public static BTSColor last => BTSCoreColors.black;
|
||||
public static BTSColor bid_ask_alt_blend => BTSCoreColors.purple_light;
|
||||
@@ -252,9 +254,9 @@ namespace mbts.ui {
|
||||
public static class PNL {
|
||||
public static BTSColor foreground => UI.default_foreground;
|
||||
public static BTSColor anchor => Colors.Gold;
|
||||
public static BTSColor profit => Colors.Green;
|
||||
public static BTSColor profit => BTSCoreColors.spring_green;
|
||||
public static BTSColor net_zero => BTSCoreColors.gray;
|
||||
public static BTSColor loss => BTSCoreColors.venetian_red;
|
||||
public static BTSColor loss => BTSCoreColors.fire_brick;
|
||||
public static BTSColor header_row_background => 0xffaaaaaa;
|
||||
// group background colors
|
||||
public static BTSColor group_row_background1 => 0x19141420;
|
||||
@@ -274,8 +276,8 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
public static Color anchor_color => Colors.Gold;
|
||||
public static Color profit_color => Colors.Green;
|
||||
public static Color loss_color => BTSCoreColors.venetian_red;
|
||||
public static Color profit_color => profit;
|
||||
public static Color loss_color => loss;
|
||||
public static Color header_row_background_color => 0xffaaaaaa.argb_color();
|
||||
// group background colors
|
||||
public static Color group_row_background_color1 => group_row_background1;
|
||||
@@ -1038,6 +1040,7 @@ namespace mbts.ui {
|
||||
}
|
||||
public static class DayFills {
|
||||
public static BTSColor background => UI.common_background;
|
||||
public static BTSColor foreground => UI.default_foreground;
|
||||
}
|
||||
public static class ComboReport {
|
||||
public static BTSColor foreground => UI.default_text;
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
public static BTSColor green => 0xff00c000;
|
||||
public static BTSColor green_p_light => 0xff99ee90;
|
||||
public static BTSColor green_dark_light => 0xff255d25;
|
||||
public static BTSColor spring_green => 0xff00fb76;
|
||||
public static BTSColor light_sea_green => 0xff17b794;
|
||||
public static BTSColor very_light_malachite_green => 0xff52e887;
|
||||
public static BTSColor may_green => 0xff43a23c;
|
||||
@@ -100,6 +101,7 @@
|
||||
public static BTSColor red_light_dark => 0xffd69a9a;
|
||||
public static BTSColor red_light_dark_saturated => 0xffd67e7e;
|
||||
public static BTSColor permanent_geranium_lake => 0xffde3429;
|
||||
public static BTSColor fire_brick => 0xffb1373a;
|
||||
public static BTSColor fire_opal => 0xfff05454;
|
||||
public static BTSColor paradise_pink => 0xffe94560;
|
||||
public static BTSColor tulip => 0xffff878d;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace mbts.ui.controls {
|
||||
public const double
|
||||
Account = 62,
|
||||
CheckMark = 42,
|
||||
Currency = 54,
|
||||
Currency = 68,
|
||||
Exchange = 66,
|
||||
FillKey = 68,
|
||||
OrderKey = 78,
|
||||
@@ -45,6 +45,7 @@ namespace mbts.ui.controls {
|
||||
MergedQty = 68,
|
||||
NotionalValue = 68,
|
||||
Filled = 56,
|
||||
FilledNotionalValue = 81,
|
||||
Symbol = 60,
|
||||
Time = 112,
|
||||
TimeOfDay = 84,
|
||||
@@ -146,6 +147,7 @@ namespace mbts.ui.controls {
|
||||
FillKind = "Fill Kind",
|
||||
FilledPrice = "Fill Price",
|
||||
FilledQuantity = "Fill Qty",
|
||||
FilledNotionalValue = "Fill Notional",
|
||||
FillStatus = "Fill Status",
|
||||
Name = "Name",
|
||||
Description = "Description",
|
||||
@@ -207,11 +209,15 @@ namespace mbts.ui.controls {
|
||||
this.prefix = prefix;
|
||||
|
||||
public string PNL => $"{prefix} PNL";
|
||||
public string RealizedPNL => $"{prefix} R PNL";
|
||||
public string UnrealizedPNL => $"{prefix} U PNL";
|
||||
public string Net => $"{prefix} {PNLHeaders.Net}";
|
||||
public string Long => $"{prefix} {PNLHeaders.Long}";
|
||||
public string LongAvg => $"{Long} {AvgPrice}";
|
||||
public string LongAvgPNL => $"{Long} {AvgPrice} PNL";
|
||||
public string Short => $"{prefix} {PNLHeaders.Short}";
|
||||
public string ShortAvg => $"{Short} {AvgPrice}";
|
||||
public string ShortAvgPNL => $"{Short} {AvgPrice} PNL";
|
||||
public string Delta => $"{prefix} {PNLHeaders.Delta}";
|
||||
public string Gamma => $"{prefix} {PNLHeaders.Gamma}";
|
||||
public string Vega => $"{prefix} {PNLHeaders.Vega}";
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using mbts.ui;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -134,6 +136,20 @@ namespace mbts.spark.filters {
|
||||
}
|
||||
}
|
||||
|
||||
public void add_exp_date(
|
||||
ExpirationDate dt, Func<TRow, ExpirationDate?> selector, bool null_passes_filter = false) {
|
||||
// Use local fns to provide context in stack trace
|
||||
_predicates.Add(null_passes_filter ? null_pass_impl : null_fail_impl);
|
||||
|
||||
bool null_fail_impl(TRow r) =>
|
||||
selector(r) == dt;
|
||||
|
||||
bool null_pass_impl(TRow r) {
|
||||
var rdt = selector(r);
|
||||
return !rdt.HasValue || rdt == dt;
|
||||
}
|
||||
}
|
||||
|
||||
public Func<TRow, bool> to_predicate() =>
|
||||
_predicates.Count == 0 ? null : new Func<TRow, bool>(o => _predicates.All(p => p(o)));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -19,7 +19,7 @@
|
||||
<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="Background" Value="{u:BTSBrush MarketData.ask_dark}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
|
||||
<Setter Property="MinHeight" Value="40"/>
|
||||
@@ -32,7 +32,7 @@
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BuyButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{u:BTSColor MarketData.bid}"/>
|
||||
<Setter Property="Background" Value="{u:BTSBrush MarketData.bid_dark}"/>
|
||||
<Setter Property="BorderBrush" Value="{u:BTSColor UI.Button.normal_border}"/>
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}" />
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
d:DesignHeight="100" d:DesignWidth="800" x:Name="_me_price">
|
||||
d:DesignHeight="100" d:DesignWidth="800" x:Name="_me_price"
|
||||
Foreground="{u:BTSColor UI.input_text}">
|
||||
<UserControl.Resources>
|
||||
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
|
||||
<cv:TickDisplayConverter x:Key="TickDisplayConverter" x:Shared="False"/>
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
}
|
||||
|
||||
void PerformMEFComposition(string config_source) {
|
||||
Log.Ui.info($"this is v2.2.3 Generating MEF dependencies for the shell" +
|
||||
Log.Ui.info($"Generating MEF dependencies for the shell" +
|
||||
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
|
||||
$"{typeof(IShellDaemon)}s, etc.)");
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace mbts.ui;
|
||||
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
|
||||
static class DisplayTranslatorExtensions {
|
||||
public static IDisposable connect_owner<T>(
|
||||
this IDisplayTranslator<T> display_translator,
|
||||
IRaiseNotifyPropertyChanged owner,
|
||||
params string[] properties
|
||||
) {
|
||||
if (display_translator is null)
|
||||
throw new ArgumentNullException(nameof(display_translator));
|
||||
if (owner is null)
|
||||
throw new ArgumentNullException(nameof(owner));
|
||||
display_translator.value_changed += on_value_changed;
|
||||
return Disposable.Create(() => display_translator.value_changed -= on_value_changed);
|
||||
void on_value_changed() {
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
owner.raise_notify_property_changed(properties[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
using bts.utils;
|
||||
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.ui {
|
||||
interface IDisplayTranslator<T> {
|
||||
T display_value { get; set; }
|
||||
T actual_value { get; set; }
|
||||
event Action value_changed;
|
||||
}
|
||||
|
||||
enum QuantityConversionOpType {
|
||||
None,
|
||||
MultiplyActual,
|
||||
DivideActual
|
||||
}
|
||||
|
||||
readonly struct QuantityConversionData<T> : IEquatable<QuantityConversionData<T>>
|
||||
where T : INumber<T> {
|
||||
public QuantityConversionData(T value, QuantityConversionOpType op) {
|
||||
this.value = value;
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public enum OpType {
|
||||
None,
|
||||
MultiplyActual,
|
||||
DivideActual
|
||||
}
|
||||
|
||||
public T value { get; }
|
||||
public QuantityConversionOpType op { get; }
|
||||
|
||||
public QuantityConversionData<TResult> convert_numeric_type<TResult>()
|
||||
where TResult : INumber<TResult> =>
|
||||
new(NumericCastConverter<T, TResult>.convert(value), op);
|
||||
|
||||
public bool Equals(QuantityConversionData<T> other) =>
|
||||
value == other.value &&
|
||||
op == other.op;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is QuantityConversionData<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.auto_phase(value.GetHashCode(), op.GetHashCode());
|
||||
|
||||
public static bool operator ==(QuantityConversionData<T> x, QuantityConversionData<T> y) =>
|
||||
x.Equals(y);
|
||||
|
||||
public static bool operator !=(QuantityConversionData<T> x, QuantityConversionData<T> y) =>
|
||||
!x.Equals(y);
|
||||
}
|
||||
|
||||
static class DisplayQuantityMultiplier {
|
||||
public static DisplayQuantityMultiplier<uint> create_uint() => new(
|
||||
DisplayTranslatorExtensions.to_display,
|
||||
DisplayTranslatorExtensions.from_display);
|
||||
|
||||
public static DisplayQuantityMultiplier<double> create_double() => new(
|
||||
DisplayTranslatorExtensions.to_display,
|
||||
DisplayTranslatorExtensions.from_display);
|
||||
}
|
||||
|
||||
class DisplayQuantityMultiplier<T> : IDisplayTranslator<T> where T : struct, INumber<T> {
|
||||
readonly Func<QuantityConversionData<T>, T, T> _to_display, _from_display;
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
T _actual_value;
|
||||
QuantityConversionData<T> _conversion_data;
|
||||
|
||||
public DisplayQuantityMultiplier(
|
||||
Func<QuantityConversionData<T>, T, T> to_display,
|
||||
Func<QuantityConversionData<T>, T, T> from_display,
|
||||
IEqualityComparer<T> comparer = null,
|
||||
T actual_value = default,
|
||||
QuantityConversionData<T> conversion_data = default) {
|
||||
//
|
||||
_to_display = to_display ?? throw new ArgumentNullException(nameof(to_display));
|
||||
_from_display = from_display ?? throw new ArgumentNullException(nameof(from_display));
|
||||
_comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
_actual_value = actual_value;
|
||||
_conversion_data = conversion_data;
|
||||
}
|
||||
|
||||
public event Action value_changed;
|
||||
|
||||
public T actual_value {
|
||||
get => _actual_value;
|
||||
set {
|
||||
if (!_comparer.Equals(_actual_value, value)) {
|
||||
_actual_value = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T display_value {
|
||||
get => _to_display(_conversion_data, _actual_value);
|
||||
set => actual_value = _from_display(_conversion_data, value);
|
||||
}
|
||||
|
||||
public QuantityConversionData<T> conversion_data {
|
||||
get => _conversion_data;
|
||||
set {
|
||||
if (_conversion_data != value) {
|
||||
_conversion_data = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OptionalDisplayQuantityMultiplier<T> : IDisplayTranslator<T?> where T : struct, INumber<T> {
|
||||
T? _actual_value;
|
||||
QuantityConversionData<T> _conversion_data;
|
||||
|
||||
public event Action value_changed;
|
||||
|
||||
public T? actual_value {
|
||||
get => _actual_value;
|
||||
set {
|
||||
if (_actual_value != value) {
|
||||
_actual_value = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T? display_value {
|
||||
get => _actual_value.HasValue ?
|
||||
_conversion_data.to_display(_actual_value.Value) : _actual_value;
|
||||
set => actual_value = value.HasValue ?
|
||||
_conversion_data.from_display(value.Value) : value;
|
||||
}
|
||||
|
||||
public QuantityConversionData<T> conversion_data {
|
||||
get => _conversion_data;
|
||||
set {
|
||||
if (_conversion_data != value) {
|
||||
_conversion_data = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class DisplayTranslatorExtensions {
|
||||
public static IDisposable connect_owner<T>(
|
||||
this IDisplayTranslator<T> display_translator,
|
||||
IRaiseNotifyPropertyChanged owner,
|
||||
params string[] properties
|
||||
) {
|
||||
if (display_translator is null)
|
||||
throw new ArgumentNullException(nameof(display_translator));
|
||||
if (owner is null)
|
||||
throw new ArgumentNullException(nameof(owner));
|
||||
display_translator.value_changed += on_value_changed;
|
||||
return Disposable.Create(() => display_translator.value_changed -= on_value_changed);
|
||||
void on_value_changed() {
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
owner.raise_notify_property_changed(properties[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<T> subscribe_actual_value<T>(
|
||||
this IDisplayTranslator<T> display_translator) =>
|
||||
subscribe_actual_value(display_translator, EqualityComparer<T>.Default);
|
||||
|
||||
public static IObservable<T> subscribe_actual_value<T>(
|
||||
this IDisplayTranslator<T> display_translator, IEqualityComparer<T> comparer) {
|
||||
if (comparer is null)
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
return Observable.Create<T>(o => {
|
||||
var curr = display_translator.actual_value;
|
||||
o.OnNext(curr);
|
||||
display_translator.value_changed += on_value_changed;
|
||||
return Disposable.Create(() => display_translator.value_changed -= on_value_changed);
|
||||
void on_value_changed() {
|
||||
var next = display_translator.actual_value;
|
||||
if (!comparer.Equals(curr, next))
|
||||
o.OnNext(curr = next);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static T to_display<T>(
|
||||
this QuantityConversionData<T> quantity_conversion_data, T actual_value)
|
||||
where T : INumber<T> =>
|
||||
quantity_conversion_data.op switch {
|
||||
QuantityConversionOpType.DivideActual =>
|
||||
actual_value.safe_divide(quantity_conversion_data.value),
|
||||
QuantityConversionOpType.MultiplyActual =>
|
||||
actual_value * quantity_conversion_data.value,
|
||||
_ => actual_value,
|
||||
};
|
||||
|
||||
public static T from_display<T>(
|
||||
this QuantityConversionData<T> quantity_conversion_data, T display_value)
|
||||
where T : INumber<T> =>
|
||||
quantity_conversion_data.op switch {
|
||||
QuantityConversionOpType.DivideActual => display_value * quantity_conversion_data.value,
|
||||
QuantityConversionOpType.MultiplyActual =>
|
||||
display_value.safe_divide(quantity_conversion_data.value),
|
||||
_ => display_value,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#nullable enable
|
||||
namespace Spark.Api.MarketData;
|
||||
|
||||
using System;
|
||||
|
||||
public readonly struct Bbo : IEquatable<Bbo> {
|
||||
public readonly double BidPrice;
|
||||
public readonly double BidQuantity;
|
||||
public readonly double AskPrice;
|
||||
public readonly double AskQuantity;
|
||||
|
||||
public Bbo(double bidPrice, double bidQuantity, double askPrice, double askQuantity) {
|
||||
BidPrice = bidPrice;
|
||||
BidQuantity = bidQuantity;
|
||||
AskPrice = askPrice;
|
||||
AskQuantity = askQuantity;
|
||||
}
|
||||
|
||||
public static Bbo Empty => new(double.NaN, double.NaN, double.NaN, double.NaN);
|
||||
|
||||
public bool Equals(Bbo other) =>
|
||||
BidPrice.Equals(other.BidPrice) &&
|
||||
BidQuantity.Equals(other.BidQuantity) &&
|
||||
AskPrice.Equals(other.AskPrice) &&
|
||||
AskQuantity.Equals(other.AskQuantity);
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj is Bbo other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(BidPrice, BidQuantity, AskPrice, AskQuantity);
|
||||
|
||||
public override string ToString() =>
|
||||
$"({BidQuantity}) {BidPrice} {AskPrice} ({AskQuantity})";
|
||||
|
||||
public static bool operator ==(Bbo left, Bbo right) =>
|
||||
left.Equals(right);
|
||||
|
||||
public static bool operator !=(Bbo left, Bbo right) =>
|
||||
!left.Equals(right);
|
||||
|
||||
public static implicit operator (double, double, double, double)(Bbo bbo) =>
|
||||
(bbo.BidPrice, bbo.BidQuantity, bbo.AskPrice, bbo.AskQuantity);
|
||||
|
||||
public static implicit operator Bbo((double, double, double, double) bboTup) =>
|
||||
(bboTup.Item1, bboTup.Item2, bboTup.Item3, bboTup.Item4);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#nullable enable
|
||||
namespace Spark.Api.MarketData;
|
||||
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
class BookStore {
|
||||
readonly uint numDepthLevels;
|
||||
readonly Dictionary<uint, PollableBook> books = new(0);
|
||||
IReadOnlyDictionary<uint, SecurityMD> securityMds = new Dictionary<uint, SecurityMD>(0);
|
||||
|
||||
public BookStore(uint numDepthLevels = NativePriceHelpers.COCONUT_MAX_DEPTH) =>
|
||||
this.numDepthLevels = numDepthLevels;
|
||||
|
||||
public BookStore(IReadOnlyDictionary<uint, SecurityMD> securityMds, uint numDepthLevels = NativePriceHelpers.COCONUT_MAX_DEPTH) {
|
||||
this.securityMds = securityMds;
|
||||
this.numDepthLevels = numDepthLevels;
|
||||
}
|
||||
|
||||
public void OnSecurityMds(IReadOnlyDictionary<uint, SecurityMD> securityMds) {
|
||||
this.securityMds = securityMds;
|
||||
books.Clear();
|
||||
}
|
||||
|
||||
public PollableBook? GetPollableBook(uint id) {
|
||||
if (!books.TryGetValue(id, out var book)) {
|
||||
if (securityMds.TryGetValue(id, out var smd)) {
|
||||
book = new(smd, numDepthLevels);
|
||||
books.Add(id, book);
|
||||
}
|
||||
}
|
||||
return book;
|
||||
}
|
||||
|
||||
public BookTuple? GetBook(uint id, bool poll = true) {
|
||||
var pb = GetPollableBook(id);
|
||||
if (poll) {
|
||||
pb?.poll_changes();
|
||||
}
|
||||
return pb?.to_book_tuple();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,13 @@ namespace Spark.Api.MarketData {
|
||||
public SecurityImmutable security { get; }
|
||||
public uint security_id => security.Id;
|
||||
public Book book { get; }
|
||||
public Bbo bbo {
|
||||
get {
|
||||
(double bidp, double bidq) = book.bids.Count > 0 ? (book.bids[0].price, book.bids[0].size) : (double.NaN, double.NaN);
|
||||
(double askp, double askq) = book.asks.Count > 0 ? (book.asks[0].price, book.asks[0].size) : (double.NaN, double.NaN);
|
||||
return new(bidp, bidq, askp, askq);
|
||||
}
|
||||
}
|
||||
|
||||
public TickDisplaySettingsImmutable quantity_display =>
|
||||
_quantity_display ?? Security.Defaults.quantity_display_settingsi;
|
||||
|
||||
@@ -180,38 +180,6 @@ namespace bts.md {
|
||||
}
|
||||
}
|
||||
|
||||
class PollableBook {
|
||||
readonly SecurityMD _security_md;
|
||||
readonly uint _num_depth_levels;
|
||||
ulong _last_update_time = 0;
|
||||
// Reuse the same arrays as an optimization to avoid reallocating memory on each poll.
|
||||
// Note: this will break if the code isn't called in a thread-safe manner.
|
||||
NativePriceSize[] _bids;
|
||||
NativePriceSize[] _asks;
|
||||
|
||||
public PollableBook(SecurityMD security_md, uint num_depth_levels) {
|
||||
_security_md = security_md;
|
||||
_num_depth_levels = num_depth_levels;
|
||||
_bids = new NativePriceSize[_num_depth_levels];
|
||||
_asks = new NativePriceSize[_num_depth_levels];
|
||||
}
|
||||
|
||||
public BookTuple to_book_tuple() {
|
||||
var book = new Book(
|
||||
_security_md.security.Id, _to_book_side(_bids), _to_book_side(_asks));
|
||||
return new(_security_md.security, book, _security_md.quantity_display);
|
||||
}
|
||||
|
||||
public bool poll_changes() =>
|
||||
_security_md.md.get_depth(
|
||||
ref _last_update_time, ref _bids, ref _asks, _num_depth_levels);
|
||||
|
||||
static List<PriceSize> _to_book_side(NativePriceSize[] native_book_side) =>
|
||||
native_book_side
|
||||
.Select(nps => new PriceSize(nps.size, nps.direct_size, nps.implied_size, nps.price))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
class PollablePriceTicker {
|
||||
readonly SecurityMD _security_md;
|
||||
ulong _bbo_poll_time = 0;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#nullable enable
|
||||
namespace Spark.Api.MarketData;
|
||||
|
||||
using bts;
|
||||
using bts.api.core;
|
||||
|
||||
using mbts;
|
||||
|
||||
using Spark.Core.Reactive;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
|
||||
public static class MarketDataExtensions {
|
||||
public static IObservable<TableUpdate<TResult, TKey>>? JoinBbo<T, TResult, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
Func<T, uint> getSecId,
|
||||
Func<T, Bbo, TResult> getResult,
|
||||
Func<TResult, TKey> getResultKey,
|
||||
TimeSpan? pollInterval = null
|
||||
) where TKey : IEquatable<TKey> {
|
||||
return source.JoinBooks(securities, getSecId, GetResult, getResultKey, pollInterval, 1);
|
||||
|
||||
TResult GetResult(T value, BookTuple book) =>
|
||||
getResult(value, book.bbo);
|
||||
}
|
||||
|
||||
public static IObservable<TableUpdate<(T, BookTuple), TKey>> JoinBooks<T, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
Func<T, uint> getSecId,
|
||||
TimeSpan? pollInterval = null,
|
||||
uint? numDepthLevels = null
|
||||
) where TKey : IEquatable<TKey> {
|
||||
// Get key function from the first update. This is for convenience (one
|
||||
// less arg), and guarentees the key is consistent with the source data.
|
||||
bool isKeyInitialized = false;
|
||||
Func<T, TKey> keyFn = _ => throw new NotImplementedException(
|
||||
"Key function expected from first update in source sequence");
|
||||
return source.SelectInline(
|
||||
s => {
|
||||
if (!isKeyInitialized) {
|
||||
isKeyInitialized = true;
|
||||
Interlocked.Exchange(ref keyFn, Log.notnull(s.key_extractor));
|
||||
}
|
||||
return s;
|
||||
})
|
||||
.JoinBooks(securities, getSecId, (v, b) => (v, b), getKey, pollInterval, numDepthLevels);
|
||||
|
||||
TKey getKey((T v, BookTuple) tup) =>
|
||||
keyFn(tup.v);
|
||||
}
|
||||
|
||||
public static IObservable<TableUpdate<TResult, TKey>> JoinBooks<T, TResult, TKey>(
|
||||
this IObservable<TableUpdate<T, TKey>> source,
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
Func<T, uint> getSecId,
|
||||
Func<T, BookTuple, TResult> getResult,
|
||||
Func<TResult, TKey> getResultKey,
|
||||
TimeSpan? pollInterval = null,
|
||||
uint? numDepthLevels = null
|
||||
) where TKey : IEquatable<TKey> {
|
||||
pollInterval ??= TimeSpan.FromSeconds(0.1);
|
||||
numDepthLevels ??= 5;
|
||||
return source.CombineLatest(securities.Select(smds => new BookStore(smds)))
|
||||
.Sample(pollInterval.Value)
|
||||
.Scan(TableUpdate<TResult, TKey>.mk_empty(getResultKey), Accumulate);
|
||||
|
||||
TableUpdate<TResult, TKey> Accumulate(TableUpdate<TResult, TKey> lastOut, (TableUpdate<T, TKey>, BookStore) lastIn) {
|
||||
var (table, bookStore) = lastIn;
|
||||
if (table is null || bookStore is null) {
|
||||
return lastOut;
|
||||
}
|
||||
var bd = lastOut.to_builder();
|
||||
var getKey = Log.notnull(table.key_extractor);
|
||||
var dirtyKeys = table.added.Concat(table.changes.New()).ToHashSet(getKey);
|
||||
lock (bookStore) {
|
||||
foreach (var secGroup in table.state.Values.GroupBy(getSecId)) {
|
||||
var pbook = bookStore.GetPollableBook(secGroup.Key);
|
||||
if (pbook is null) {
|
||||
continue;
|
||||
}
|
||||
bool isBookDirty = pbook.poll_changes();
|
||||
var book = pbook.to_book_tuple();
|
||||
var dirtyVals = secGroup.AsEnumerable();
|
||||
if (!isBookDirty) {
|
||||
dirtyVals = dirtyVals.Where(v => dirtyKeys.Contains(getKey(v)));
|
||||
}
|
||||
foreach (var v in dirtyVals) {
|
||||
var result = getResult(v, book);
|
||||
bd.add_or_update(result);
|
||||
}
|
||||
}
|
||||
return bd.to_table();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Spark.Api.MarketData;
|
||||
|
||||
using bts.md;
|
||||
|
||||
using mbts;
|
||||
using mbts.co2;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
class PollableBook {
|
||||
readonly SecurityMD _security_md;
|
||||
readonly uint _num_depth_levels;
|
||||
ulong _last_update_time = 0;
|
||||
// Reuse the same arrays as an optimization to avoid reallocating memory on each poll.
|
||||
// Note: this will break if the code isn't called in a thread-safe manner.
|
||||
NativePriceSize[] _bids;
|
||||
NativePriceSize[] _asks;
|
||||
|
||||
public PollableBook(SecurityMD security_md, uint num_depth_levels = NativePriceHelpers.COCONUT_MAX_DEPTH) {
|
||||
_security_md = security_md;
|
||||
_num_depth_levels = num_depth_levels;
|
||||
_bids = new NativePriceSize[_num_depth_levels];
|
||||
_asks = new NativePriceSize[_num_depth_levels];
|
||||
}
|
||||
|
||||
public ulong last_update_time => _last_update_time;
|
||||
|
||||
public BookTuple to_book_tuple() {
|
||||
var book = new Book(
|
||||
_security_md.security.Id, _to_book_side(_bids), _to_book_side(_asks));
|
||||
return new(_security_md.security, book, _security_md.quantity_display);
|
||||
}
|
||||
|
||||
public bool poll_changes() =>
|
||||
_security_md.md.get_depth(
|
||||
ref _last_update_time, ref _bids, ref _asks, _num_depth_levels);
|
||||
|
||||
static List<PriceSize> _to_book_side(NativePriceSize[] native_book_side) =>
|
||||
native_book_side
|
||||
.Select(nps => new PriceSize(nps.size, nps.direct_size, nps.implied_size, nps.price))
|
||||
.ToList();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace bts.api.orderman {
|
||||
|
||||
public static class OrderUtilities {
|
||||
public static NativeOrderStateImmutable? native_order_state(this OrderStateImmutable order) {
|
||||
switch(order.CustomCase) {
|
||||
case OrderState.CustomOneofCase.Market:
|
||||
return order.Market.NativeState;
|
||||
case OrderState.CustomOneofCase.Limit:
|
||||
return order.Limit.NativeState;
|
||||
case OrderState.CustomOneofCase.Stop:
|
||||
return order.Stop.NativeState;
|
||||
case OrderState.CustomOneofCase.StopLimit:
|
||||
return order.StopLimit.NativeState;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,20 @@ namespace Spark.Api.Orders {
|
||||
public uint security_id => order.security_id;
|
||||
public SecurityImmutable? security => order.security;
|
||||
|
||||
// TODO: remove assumption that synthetic quantities are already notional values
|
||||
public double notional_value =>
|
||||
(security?.HasSynthetic ?? false)
|
||||
&& security.Value.Synthetic.PriceCalc == SyntheticContract.Types.PriceCalc.NotionalLeg
|
||||
? order.order.quantity()
|
||||
: order.order.notional_value();
|
||||
|
||||
// TODO: remove assumption that synthetic quantities are already notional values
|
||||
public double filled_notional_value =>
|
||||
(security?.HasSynthetic ?? false)
|
||||
&& security.Value.Synthetic.PriceCalc == SyntheticContract.Types.PriceCalc.NotionalLeg
|
||||
? order.order.filled_quantity()
|
||||
: order.order.filled_notional_value();
|
||||
|
||||
public bool Equals(OrderRecordTupleWithRoot other) =>
|
||||
order.Equals(other.order) &&
|
||||
EqualityComparer.nullable_equals(_root, other._root);
|
||||
|
||||
@@ -37,6 +37,20 @@ namespace Spark.Api.Orders {
|
||||
public static double fill_ratio(this OrderStateImmutable state) =>
|
||||
state.filled_quantity() / state.quantity();
|
||||
|
||||
public static NativeOrderStateImmutable? native_order_state(this OrderStateImmutable order) {
|
||||
switch(order.CustomCase) {
|
||||
case OrderState.CustomOneofCase.Market:
|
||||
return order.Market.NativeState;
|
||||
case OrderState.CustomOneofCase.Limit:
|
||||
return order.Limit.NativeState;
|
||||
case OrderState.CustomOneofCase.Stop:
|
||||
return order.Stop.NativeState;
|
||||
case OrderState.CustomOneofCase.StopLimit:
|
||||
return order.StopLimit.NativeState;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static double filled_quantity(this OrderRecordImmutable order) =>
|
||||
@@ -51,6 +65,9 @@ namespace Spark.Api.Orders {
|
||||
public static double notional_value(this OrderRecordImmutable order) =>
|
||||
order.quantity() * order.ui_price();
|
||||
|
||||
public static double filled_notional_value(this OrderRecordImmutable order) =>
|
||||
order.avg_price() * order.filled_quantity();
|
||||
|
||||
public static double working_quantity(this OrderRecordImmutable order) =>
|
||||
order.HasState ? order.State.working_quantity() : 0;
|
||||
|
||||
@@ -252,7 +269,7 @@ namespace Spark.Api.Orders {
|
||||
}
|
||||
|
||||
public static string short_description(this OrderRecordImmutable order, ContractSpecificationImmutable? spec) =>
|
||||
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? ($@"{spec.format(order.State.Price)} @ {order.State.Quantity}") : "(unknown-price) @ (unknown-quantity)")}";
|
||||
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? $@"{order.State.quantity()} @ {spec.format(order.State.Price)}" : "(unknown-price) @ (unknown-quantity)")}";
|
||||
|
||||
public static string short_description(
|
||||
SecurityImmutable security, Side side, double price, double quantity) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
namespace bts.shell.pnl;
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Api.Position;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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(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(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(long_data, short_data);
|
||||
}
|
||||
return order.side() switch {
|
||||
Side.Buy => new(long_data.Add(order, qty_conversion), short_data),
|
||||
Side.Sell => new(long_data, short_data.Add(order, qty_conversion)),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side"),
|
||||
};
|
||||
}
|
||||
|
||||
public WorkingOrderData Remove(OrderRecordImmutable order) {
|
||||
if (order.working_quantity() <= 0) {
|
||||
return new(long_data, short_data);
|
||||
}
|
||||
return order.side() switch {
|
||||
Side.Buy => new(long_data.Remove(order.Key, order.price()), short_data),
|
||||
Side.Sell => new(long_data, short_data.Remove(order.Key, order.price())),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(order.side(), "order.side"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
namespace bts.shell.pnl;
|
||||
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
|
||||
using mbts.spark;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
public 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) {
|
||||
if (display_working_total == 0) {
|
||||
return string.Empty;
|
||||
}
|
||||
var spec = Security.Defaults.ContractSpecificationImmutable;
|
||||
var best_order = data.Values.First().Values.First();
|
||||
var best_price_volume = best_order.qty_conversion.to_display(best_order.order.working_quantity());
|
||||
var best_price = best_order.order.price();
|
||||
return display_working_total == best_price_volume ?
|
||||
$"{best_price_volume} @ {spec.format(best_price)}" :
|
||||
$"{best_price_volume} ({display_working_total}) @ {spec.format(best_price)}";
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace Spark.Api.Data;
|
||||
|
||||
using mbts.ui;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
public class DisplayQuantityMultiplier<T> : IDisplayTranslator<T> where T : struct, INumber<T> {
|
||||
readonly Func<QuantityConversionData<T>, T, T> _to_display, _from_display;
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
T _actual_value;
|
||||
QuantityConversionData<T> _conversion_data;
|
||||
|
||||
public DisplayQuantityMultiplier(
|
||||
Func<QuantityConversionData<T>, T, T> to_display,
|
||||
Func<QuantityConversionData<T>, T, T> from_display,
|
||||
IEqualityComparer<T> comparer = null,
|
||||
T actual_value = default,
|
||||
QuantityConversionData<T> conversion_data = default) {
|
||||
//
|
||||
_to_display = to_display ?? throw new ArgumentNullException(nameof(to_display));
|
||||
_from_display = from_display ?? throw new ArgumentNullException(nameof(from_display));
|
||||
_comparer = comparer ?? EqualityComparer<T>.Default;
|
||||
_actual_value = actual_value;
|
||||
_conversion_data = conversion_data;
|
||||
}
|
||||
|
||||
public event Action value_changed;
|
||||
|
||||
public T actual_value {
|
||||
get => _actual_value;
|
||||
set {
|
||||
if (!_comparer.Equals(_actual_value, value)) {
|
||||
_actual_value = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T display_value {
|
||||
get => _to_display(_conversion_data, _actual_value);
|
||||
set => actual_value = _from_display(_conversion_data, value);
|
||||
}
|
||||
|
||||
public QuantityConversionData<T> conversion_data {
|
||||
get => _conversion_data;
|
||||
set {
|
||||
if (_conversion_data != value) {
|
||||
_conversion_data = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OptionalDisplayQuantityMultiplier<T> : IDisplayTranslator<T?> where T : struct, INumber<T> {
|
||||
T? _actual_value;
|
||||
QuantityConversionData<T> _conversion_data;
|
||||
|
||||
public event Action value_changed;
|
||||
|
||||
public T? actual_value {
|
||||
get => _actual_value;
|
||||
set {
|
||||
if (_actual_value != value) {
|
||||
_actual_value = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T? display_value {
|
||||
get => _actual_value.HasValue ?
|
||||
_conversion_data.to_display(_actual_value.Value) : _actual_value;
|
||||
set => actual_value = value.HasValue ?
|
||||
_conversion_data.from_display(value.Value) : value;
|
||||
}
|
||||
|
||||
public QuantityConversionData<T> conversion_data {
|
||||
get => _conversion_data;
|
||||
set {
|
||||
if (_conversion_data != value) {
|
||||
_conversion_data = value;
|
||||
value_changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DisplayQuantityMultiplier {
|
||||
public static DisplayQuantityMultiplier<uint> create_uint() => new(
|
||||
DisplayTranslatorExtensions.to_display,
|
||||
DisplayTranslatorExtensions.from_display);
|
||||
|
||||
public static DisplayQuantityMultiplier<double> create_double() => new(
|
||||
DisplayTranslatorExtensions.to_display,
|
||||
DisplayTranslatorExtensions.from_display);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
namespace Spark.Api.Data;
|
||||
|
||||
using bts.api;
|
||||
|
||||
using mbts.utils;
|
||||
|
||||
using System;
|
||||
|
||||
public enum ExpirationDateType {
|
||||
None = 0,
|
||||
FixedDate = 1,
|
||||
Perpetual = 2
|
||||
}
|
||||
|
||||
public readonly struct ExpirationDate : IEquatable<ExpirationDate>, IComparable<ExpirationDate> {
|
||||
public ExpirationDate(DateTime value, ExpirationDateType type) {
|
||||
Value = value;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public static ExpirationDate None => new(default, ExpirationDateType.None);
|
||||
public static ExpirationDate Perpetual => new(default, ExpirationDateType.Perpetual);
|
||||
public static ExpirationDate FixedDate(DateTime dateTime) => new(dateTime, ExpirationDateType.FixedDate);
|
||||
|
||||
public DateTime Value { get; }
|
||||
public ExpirationDateType Type { get; }
|
||||
|
||||
static ComparerBuilder<ExpirationDate> comparer = new ComparerBuilder<ExpirationDate>() {
|
||||
ed => ed.Type,
|
||||
ed => ed.Value
|
||||
};
|
||||
|
||||
public int CompareTo(ExpirationDate other) =>
|
||||
comparer.Compare(this, other);
|
||||
|
||||
public bool Equals(ExpirationDate other) =>
|
||||
Value == other.Value && Type == other.Type;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is ExpirationDate other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(Value.GetHashCode(), (int)Type);
|
||||
|
||||
public override string ToString() =>
|
||||
ToString(DateFormat.DayMonthAbvDiff2000);
|
||||
|
||||
public string ToString(DateFormat dateFormat) =>
|
||||
Type switch {
|
||||
ExpirationDateType.None => "None",
|
||||
ExpirationDateType.FixedDate => DateFormatter.Format(Value, dateFormat),
|
||||
ExpirationDateType.Perpetual => "Perp",
|
||||
_ => "Invalid",
|
||||
};
|
||||
|
||||
public static bool operator ==(ExpirationDate left, ExpirationDate right) =>
|
||||
left.Equals(right);
|
||||
|
||||
public static bool operator !=(ExpirationDate left, ExpirationDate right) =>
|
||||
!left.Equals(right);
|
||||
|
||||
public static bool operator <(ExpirationDate left, ExpirationDate right) =>
|
||||
comparer.Compare(left, right) < 0;
|
||||
|
||||
public static bool operator >(ExpirationDate left, ExpirationDate right) =>
|
||||
comparer.Compare(left, right) > 0;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using bts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace Spark.Api.Data;
|
||||
|
||||
public interface IDisplayTranslator<T> {
|
||||
T display_value { get; set; }
|
||||
T actual_value { get; set; }
|
||||
event Action value_changed;
|
||||
}
|
||||
|
||||
public static class DisplayTranslatorExtensions {
|
||||
public static IObservable<T> subscribe_actual_value<T>(
|
||||
this IDisplayTranslator<T> display_translator) =>
|
||||
subscribe_actual_value(display_translator, EqualityComparer<T>.Default);
|
||||
|
||||
public static IObservable<T> subscribe_actual_value<T>(
|
||||
this IDisplayTranslator<T> display_translator, IEqualityComparer<T> comparer) {
|
||||
if (comparer is null)
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
return Observable.Create<T>(o => {
|
||||
var curr = display_translator.actual_value;
|
||||
o.OnNext(curr);
|
||||
display_translator.value_changed += on_value_changed;
|
||||
return Disposable.Create(() => display_translator.value_changed -= on_value_changed);
|
||||
void on_value_changed() {
|
||||
var next = display_translator.actual_value;
|
||||
if (!comparer.Equals(curr, next))
|
||||
o.OnNext(curr = next);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static T to_display<T>(
|
||||
this QuantityConversionData<T> quantity_conversion_data, T actual_value)
|
||||
where T : INumber<T> =>
|
||||
quantity_conversion_data.op switch {
|
||||
QuantityConversionOpType.DivideActual =>
|
||||
actual_value.safe_divide(quantity_conversion_data.value),
|
||||
QuantityConversionOpType.MultiplyActual =>
|
||||
actual_value * quantity_conversion_data.value,
|
||||
_ => actual_value,
|
||||
};
|
||||
|
||||
public static T from_display<T>(
|
||||
this QuantityConversionData<T> quantity_conversion_data, T display_value)
|
||||
where T : INumber<T> =>
|
||||
quantity_conversion_data.op switch {
|
||||
QuantityConversionOpType.DivideActual => display_value * quantity_conversion_data.value,
|
||||
QuantityConversionOpType.MultiplyActual =>
|
||||
display_value.safe_divide(quantity_conversion_data.value),
|
||||
_ => display_value,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace Spark.Api.Data;
|
||||
|
||||
using Spark.Core;
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
public enum QuantityConversionOpType {
|
||||
None,
|
||||
MultiplyActual,
|
||||
DivideActual
|
||||
}
|
||||
|
||||
public readonly struct QuantityConversionData<T> : IEquatable<QuantityConversionData<T>>
|
||||
where T : INumber<T> {
|
||||
public QuantityConversionData(T value, QuantityConversionOpType op) {
|
||||
this.value = value;
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public T value { get; }
|
||||
public QuantityConversionOpType op { get; }
|
||||
|
||||
public QuantityConversionData<TResult> convert_numeric_type<TResult>()
|
||||
where TResult : INumber<TResult> =>
|
||||
new(NumericCastConverter<T, TResult>.convert(value), op);
|
||||
|
||||
public bool Equals(QuantityConversionData<T> other) =>
|
||||
value == other.value &&
|
||||
op == other.op;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is QuantityConversionData<T> other && Equals(other);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(value, op);
|
||||
|
||||
public static bool operator ==(QuantityConversionData<T> x, QuantityConversionData<T> y) =>
|
||||
x.Equals(y);
|
||||
|
||||
public static bool operator !=(QuantityConversionData<T> x, QuantityConversionData<T> y) =>
|
||||
!x.Equals(y);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace mbts.spark {
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
@@ -27,6 +28,15 @@ namespace mbts.spark {
|
||||
internal static SecurityCriteria from_keys(IEnumerable<uint> keys) =>
|
||||
new(new[] { new SecurityCriteriaClause(security_keys: keys.ToArray()) });
|
||||
|
||||
internal static SecurityCriteria from_expirations(params ExpirationDate[] exp_dates) =>
|
||||
new(new[] { new SecurityCriteriaClause(expirations: exp_dates) });
|
||||
|
||||
internal static SecurityCriteria from_expirations(params DateTime[] exp_dates) =>
|
||||
from_expirations(exp_dates.AsEnumerable());
|
||||
|
||||
internal static SecurityCriteria from_expirations(IEnumerable<DateTime> expirations) =>
|
||||
new(new[] { SecurityCriteriaClause.from_expirations(expirations) });
|
||||
|
||||
internal static SecurityCriteria from_securities(params SecurityImmutable[] securities) =>
|
||||
from_securities(securities.AsEnumerable());
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace mbts.spark {
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
@@ -17,12 +18,12 @@ namespace mbts.spark {
|
||||
Venue[]? exchanges = null,
|
||||
SecurityType[]? security_types = null,
|
||||
string[]? symbols = null,
|
||||
DateTime[]? expirations = null,
|
||||
ExpirationDate[]? expirations = null,
|
||||
uint[]? security_keys = null,
|
||||
string? pretty_name = null,
|
||||
double[]? strikes = null,
|
||||
PutOrCall[]? callputs = null,
|
||||
string[]? names= null
|
||||
string[]? names = null
|
||||
) {
|
||||
this.exchanges = exchanges;
|
||||
this.security_types = security_types;
|
||||
@@ -32,7 +33,7 @@ namespace mbts.spark {
|
||||
this.pretty_name = pretty_name;
|
||||
this.strikes = strikes;
|
||||
this.callputs = callputs;
|
||||
this.names=names;
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public static SecurityCriteriaClause from_securities(
|
||||
@@ -58,6 +59,20 @@ namespace mbts.spark {
|
||||
return new SecurityCriteriaClause(security_keys: keys, pretty_name: pretty_name);
|
||||
}
|
||||
|
||||
public static SecurityCriteriaClause from_expirations(IEnumerable<DateTime> expirations) {
|
||||
return expirations is null
|
||||
? new()
|
||||
: from_expirations(expirations.Select(convert).ToArray());
|
||||
|
||||
static ExpirationDate convert(DateTime e) =>
|
||||
Security.is_perpetual_exp(e)
|
||||
? ExpirationDate.Perpetual
|
||||
: ExpirationDate.FixedDate(e);
|
||||
}
|
||||
|
||||
public static SecurityCriteriaClause from_expirations(params ExpirationDate[] expirations) =>
|
||||
new(expirations: expirations);
|
||||
|
||||
[ProtoMember(1)]
|
||||
public string? pretty_name { get; }
|
||||
|
||||
@@ -71,7 +86,7 @@ namespace mbts.spark {
|
||||
public string[]? symbols { get; }
|
||||
|
||||
// Not stored in protobuf (yet)
|
||||
public DateTime[]? expirations { get; }
|
||||
public ExpirationDate[]? expirations { get; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public uint[]? security_keys { get; }
|
||||
|
||||
@@ -3,6 +3,8 @@ namespace mbts.spark {
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -180,7 +182,7 @@ namespace mbts.spark {
|
||||
Venue[]? exchanges = null,
|
||||
SecurityType[]? security_types = null,
|
||||
string[]? symbols = null,
|
||||
DateTime[]? expirations = null,
|
||||
ExpirationDate[]? expirations = null,
|
||||
uint[]? security_keys = null,
|
||||
string? pretty_name = null,
|
||||
double[]? strikes = null,
|
||||
|
||||
@@ -567,6 +567,9 @@ namespace mbts {
|
||||
public static double round(this bts.api.domain.TickFactorsImmutable tick, double input) =>
|
||||
_round_tick(tick.tick_unit_frac(), input);
|
||||
|
||||
public static void round(this bts.api.domain.TickFactorsImmutable tick, ref double input) =>
|
||||
input = _round_tick(tick.tick_unit_frac(), input);
|
||||
|
||||
public static double round(this RegionalTickFactors regional_tick, double input) {
|
||||
if (DoubleHelper.IsNaNOrInfinity(input)) {
|
||||
return input;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace bts.api {
|
||||
public static security_key key(Security s) { return s.Id; }
|
||||
|
||||
public static bool is_perpetual_exp(DateTime exp) =>
|
||||
exp.Year < 2000 || exp.Year > 2999;
|
||||
exp.Year < 2000 || exp.Year > 2099;
|
||||
|
||||
public static class Defaults {
|
||||
public static ContractSpecification ContractSpecification =>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace bts.api {
|
||||
using mbts.utils;
|
||||
using Spark.Core.Collections;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
class SecurityComparer : IComparer<Security>, IComparer<SecurityImmutable> {
|
||||
static readonly SecurityComparer _instance = new();
|
||||
@@ -25,182 +24,24 @@ namespace bts.api {
|
||||
}
|
||||
|
||||
public static int compare(SecurityImmutable x, SecurityImmutable y) {
|
||||
int c = non_deterministic_impl();
|
||||
if (c == 0) {
|
||||
c = x.ToString().CompareTo(y.ToString());
|
||||
if (c == 0) {
|
||||
c = x.Id.CompareTo(y.Id);
|
||||
var xlegs = new ContractLegEnumerable(x);
|
||||
var ylegs = new ContractLegEnumerable(y);
|
||||
using var xen = xlegs.GetEnumerator();
|
||||
using var yen = ylegs.GetEnumerator();
|
||||
int c = 0;
|
||||
while (xen.MoveNext()) {
|
||||
if (!yen.MoveNext()) {
|
||||
return 1;
|
||||
}
|
||||
c = new ComparisonCriteria(xen.Current).CompareTo(new(yen.Current));
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
|
||||
// this has been observed to be non-determinisitc with certain ICE securities
|
||||
// (it is determenistic with other exchanges..as far as we know).
|
||||
int non_deterministic_impl() {
|
||||
// | Type 1 | Type 2 | Comp Hierarchy |
|
||||
// |------------------------------------------|
|
||||
// | SPOT | SPOT | sym, curr |
|
||||
// | SPOT | FUT | sym, curr |
|
||||
// | SPOT | OPT | SPOT < OPT |
|
||||
// | SPOT | SPREAD | * SPOT < SPREAD |
|
||||
// | SPOT | SYNTH | * SPOT < SYNTH |
|
||||
// | FUT | FUT | sym, exp, curr |
|
||||
// | FUT | OPT | * FUT < OPT |
|
||||
// | FUT | SPREAD | * FUT < SPREAD |
|
||||
// | FUT | SYNTH | * FUT < SYNTH |
|
||||
// | OPT | OPT | sym, exp, curr, k, c/p |
|
||||
// | OPT | SPREAD | OPT <=> first leg |
|
||||
// | OPT | SYNTH | OPT <=> first leg |
|
||||
// | SPREAD | SPREAD | legs <=> legs |
|
||||
// | SPREAD | SYNTH | legs <=> legs |
|
||||
//
|
||||
// * comp result is constant (there is no comp hierarchy)
|
||||
|
||||
return x.ContractCase switch {
|
||||
Security.ContractOneofCase.Future =>
|
||||
y.ContractCase switch {
|
||||
Security.ContractOneofCase.Spot =>
|
||||
new ComparerBuilder<SecurityImmutable> {
|
||||
s => s.Symbol,
|
||||
s => s.Currency
|
||||
}.Compare(x, y),
|
||||
Security.ContractOneofCase.Future =>
|
||||
new ComparerBuilder<SecurityImmutable> {
|
||||
s => s.Symbol,
|
||||
s => s.Future.expiration_date()
|
||||
}.Compare(x, y),
|
||||
Security.ContractOneofCase.Option or
|
||||
Security.ContractOneofCase.Spread or
|
||||
Security.ContractOneofCase.Synthetic => -1,
|
||||
_ => 1,
|
||||
},
|
||||
Security.ContractOneofCase.Spot =>
|
||||
y.ContractCase switch {
|
||||
Security.ContractOneofCase.Spot or
|
||||
Security.ContractOneofCase.Future =>
|
||||
new ComparerBuilder<SecurityImmutable> {
|
||||
s => s.Symbol,
|
||||
s => s.Currency
|
||||
}.Compare(x, y),
|
||||
_ => -1,
|
||||
},
|
||||
Security.ContractOneofCase.Option or
|
||||
Security.ContractOneofCase.Spread or
|
||||
Security.ContractOneofCase.Synthetic =>
|
||||
y.ContractCase switch {
|
||||
Security.ContractOneofCase.Spread or
|
||||
Security.ContractOneofCase.Synthetic or
|
||||
Security.ContractOneofCase.Option => compare_flat_legs(),
|
||||
_ => 1,
|
||||
},
|
||||
Security.ContractOneofCase.None =>
|
||||
y.ContractCase == Security.ContractOneofCase.None ? 0 : -1,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
if (yen.MoveNext()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int compare_flat_legs() {
|
||||
var xlegs = new ContractLegEnumerable(x).ToList();
|
||||
var ylegs = new ContractLegEnumerable(y).ToList();
|
||||
int max = Math.Min(xlegs.Count, ylegs.Count);
|
||||
for (int i = 0; i < max; i++) {
|
||||
var xleg = xlegs[i];
|
||||
var yleg = ylegs[i];
|
||||
int comp = _compare(
|
||||
xleg.Security, xleg.signed_quantity(),
|
||||
yleg.Security, yleg.signed_quantity());
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
return xlegs.Count.CompareTo(ylegs.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public static int compare(string x_sym, FutureContract x, string y_sym, FutureContract y) {
|
||||
int sym_comp = x_sym.CompareTo(y_sym);
|
||||
return sym_comp == 0 ? x.expiration_date().CompareTo(y.expiration_date()) : sym_comp;
|
||||
}
|
||||
|
||||
public static int compare(string x_sym, FutureContractImmutable x, string y_sym, FutureContractImmutable y) {
|
||||
int sym_comp = x_sym.CompareTo(y_sym);
|
||||
return sym_comp == 0 ? x.expiration_date().CompareTo(y.expiration_date()) : sym_comp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares option/future securities
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException"/>
|
||||
static int _compare(SecurityImmutable x, double xqty, SecurityImmutable y, double yqty) {
|
||||
static bool is_valid_sec_type(SecurityImmutable sec) =>
|
||||
sec.ContractCase switch {
|
||||
Security.ContractOneofCase.Spot or
|
||||
Security.ContractOneofCase.Future or
|
||||
Security.ContractOneofCase.Option => true,
|
||||
_ => false
|
||||
};
|
||||
if (!is_valid_sec_type(x)) {
|
||||
throw new ArgumentException("Security not of type Option or Future", nameof(x));
|
||||
}
|
||||
if (!is_valid_sec_type(y)) {
|
||||
throw new ArgumentException("Security not of type Option or Future", nameof(y));
|
||||
}
|
||||
|
||||
// symbol
|
||||
int comp = string.Compare(x.Symbol, y.Symbol);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
// expiration
|
||||
var xexp = x.security_expiration();
|
||||
var yexp = y.security_expiration();
|
||||
if (xexp.HasValue) {
|
||||
if (!yexp.HasValue) {
|
||||
return -1;
|
||||
}
|
||||
comp = xexp.Value.CompareTo(yexp.Value);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
} else if (yexp.HasValue) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// strike
|
||||
var xk = x.strike();
|
||||
var yk = y.strike();
|
||||
if (double.IsNaN(xk) != double.IsNaN(yk)) {
|
||||
return double.IsNaN(xk) ? -1 : 1;
|
||||
}
|
||||
if (!double.IsNaN(xk)) {
|
||||
comp = xk.CompareTo(yk);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
// security type
|
||||
comp = x.ContractCase.CompareTo(y.ContractCase);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
// quantity
|
||||
comp = xqty.CompareTo(yqty);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
// venue
|
||||
comp = x.Exchange.CompareTo(y.Exchange);
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
// id
|
||||
comp = x.Id.CompareTo(y.Id);
|
||||
return comp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IComparer<Security>.Compare(Security x, Security y) =>
|
||||
@@ -208,5 +49,65 @@ namespace bts.api {
|
||||
|
||||
int IComparer<SecurityImmutable>.Compare(SecurityImmutable x, SecurityImmutable y) =>
|
||||
compare(x, y);
|
||||
|
||||
readonly struct ComparisonCriteria : IComparable<ComparisonCriteria> {
|
||||
readonly SecurityImmutable security;
|
||||
readonly double legQty;
|
||||
|
||||
public ComparisonCriteria(SecurityImmutable security) {
|
||||
this.security = security;
|
||||
legQty = 1;
|
||||
}
|
||||
|
||||
public ComparisonCriteria(ContractLegImmutable leg) {
|
||||
security = leg.Security;
|
||||
legQty = leg.signed_quantity();
|
||||
}
|
||||
|
||||
public string Symbol => security.Symbol;
|
||||
public DateTime? Expiration => security.ContractCase switch {
|
||||
Security.ContractOneofCase.Future => security.Future.expiration_date(),
|
||||
Security.ContractOneofCase.Option => security.Option.expiration_date(),
|
||||
_ => null,
|
||||
};
|
||||
public string Currency => security.Currency;
|
||||
public double? Strike => security.HasOption ? security.Option.Strike : null;
|
||||
public PutOrCall? CallPut => security.HasOption ? security.Option.CallPut : null;
|
||||
public Venue Exchange => security.Exchange;
|
||||
public uint Id => security.Id;
|
||||
|
||||
public int CompareTo(ComparisonCriteria other) {
|
||||
var c = string.Compare(Symbol, other.Symbol);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = NullCheckComparer.Compare(Expiration, other.Expiration);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = string.Compare(Currency, other.Currency);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = NullCheckComparer.Compare(Strike, other.Strike);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = NullCheckComparer.Compare(CallPut, other.CallPut);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = NullCheckComparer.Compare(Exchange, other.Exchange);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = legQty.CompareTo(other.legQty);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
c = Id.CompareTo(other.Id);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace bts.api {
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -37,15 +39,20 @@ namespace bts.api {
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<DateTime> normal_expirations(this SecurityImmutable security) =>
|
||||
security.ContractCase switch {
|
||||
Security.ContractOneofCase.Future => new[] { security.Future.expiration_date() },
|
||||
Security.ContractOneofCase.Option => new[] { security.Option.expiration_date() },
|
||||
public static IEnumerable<ExpirationDate> normal_expirations(this SecurityImmutable security) {
|
||||
return security.ContractCase switch {
|
||||
Security.ContractOneofCase.Spot => new[] { ExpirationDate.None },
|
||||
Security.ContractOneofCase.Future => new[] { future_exp(security.Future.expiration_date()) },
|
||||
Security.ContractOneofCase.Option => new[] { ExpirationDate.FixedDate(security.Option.expiration_date()) },
|
||||
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => l.Security.normal_expirations()),
|
||||
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => l.Security.normal_expirations()),
|
||||
_ => Enumerable.Empty<DateTime>()
|
||||
_ => Enumerable.Empty<ExpirationDate>()
|
||||
};
|
||||
|
||||
static ExpirationDate future_exp(DateTime exp_date) =>
|
||||
Security.is_perpetual_exp(exp_date) ? ExpirationDate.Perpetual : ExpirationDate.FixedDate(exp_date);
|
||||
}
|
||||
|
||||
public static ProductImmutable product(this SecurityImmutable security) =>
|
||||
new(new() {
|
||||
Exchange = security.Exchange,
|
||||
@@ -68,7 +75,7 @@ namespace bts.api {
|
||||
public static DateTime expiration_date(this FutureContractImmutable future) =>
|
||||
new((int)future.ExpirationYear, (int)future.ExpirationMonth, (int)future.ExpirationDay);
|
||||
|
||||
internal static DateTime earliest_expiration(this SecurityImmutable security) =>
|
||||
internal static ExpirationDate earliest_expiration(this SecurityImmutable security) =>
|
||||
security.normal_expirations().Min();
|
||||
|
||||
public static double strike(this SecurityImmutable security) =>
|
||||
@@ -135,6 +142,7 @@ namespace bts.api {
|
||||
domain.SecurityType.Future => SecurityType.Future,
|
||||
domain.SecurityType.Option => SecurityType.Option,
|
||||
domain.SecurityType.Spread => SecurityType.Spread,
|
||||
domain.SecurityType.Synthetic => SecurityType.Synthetic,
|
||||
domain.SecurityType.Unspecified => SecurityType.Unset,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
@@ -145,6 +153,7 @@ namespace bts.api {
|
||||
SecurityType.Future => domain.SecurityType.Future,
|
||||
SecurityType.Option => domain.SecurityType.Option,
|
||||
SecurityType.Spread => domain.SecurityType.Spread,
|
||||
SecurityType.Synthetic => domain.SecurityType.Synthetic,
|
||||
SecurityType.Unset => domain.SecurityType.Unspecified,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace bts.api {
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@@ -106,8 +108,10 @@
|
||||
string? symbol = null,
|
||||
Security.ContractOneofCase? type = null,
|
||||
DateFormat date_format = DateFormat.DayMonthAbvDiff2000) {
|
||||
var result = Security.is_perpetual_exp(exp_date) ?
|
||||
"Perp" : DateFormatter.Format(exp_date, date_format);
|
||||
var exp_type = Security.is_perpetual_exp(exp_date)
|
||||
? ExpirationDateType.Perpetual
|
||||
: ExpirationDateType.FixedDate;
|
||||
var result = new ExpirationDate(exp_date, exp_type).ToString(date_format);
|
||||
if (symbol is not null) {
|
||||
result = symbol + " " + result;
|
||||
}
|
||||
|
||||
+149
-149
@@ -1,149 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.collections {
|
||||
class OperableHierarchicalCollectionModel<TRow, T, TKey> where TKey : IEquatable<TKey> where TRow : IHierarchicalRow<T> {
|
||||
readonly Func<T, TRow> _row_factory;
|
||||
readonly Func<T, TKey> _key;
|
||||
SortedTree<TRow, TKey> _tree;
|
||||
IComparer<TRow> _comparer;
|
||||
Func<TRow, bool> _filter;
|
||||
List<INode<TRow>> _cached_filtered = new List<INode<TRow>>();
|
||||
|
||||
public OperableHierarchicalCollectionModel(Func<T, TRow> row_factory, Func<T, TKey> key, Func<T, TKey> parent_key, TKey no_parent_key, IComparer<TRow> comparer) {
|
||||
_row_factory = row_factory;
|
||||
_tree = new SortedTree<TRow, TKey>(_comparer = comparer, el => key(el.value), el => parent_key(el.value), no_parent_key);
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public IComparer<TRow> comparer {
|
||||
get => _comparer;
|
||||
set {
|
||||
_tree = _tree.Resort(_comparer = value);
|
||||
_cached_filtered = null;
|
||||
}
|
||||
}
|
||||
public Func<TRow, bool> filter {
|
||||
get => _filter;
|
||||
set {
|
||||
_filter = value;
|
||||
_cached_filtered = null;
|
||||
_refresh_hierarchical_props();
|
||||
}
|
||||
}
|
||||
public IEnumerable<TRow> rows {
|
||||
get {
|
||||
return _root_nodes.SelectMany(impl);
|
||||
IEnumerable<TRow> impl(INode<TRow> n) {
|
||||
yield return n.value;
|
||||
if (n.value.is_expanded) {
|
||||
foreach (var _n in n.inner.SelectMany(impl))
|
||||
yield return _n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TRow> all_rows {
|
||||
get {
|
||||
return _root_nodes.SelectMany(impl);
|
||||
IEnumerable<TRow> impl(INode<TRow> n) {
|
||||
yield return n.value;
|
||||
foreach (var _n in n.inner.SelectMany(impl))
|
||||
yield return _n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int total_row_count { get; private set; }
|
||||
|
||||
IEnumerable<INode<TRow>> _root_nodes =>
|
||||
_filter == null ? _tree.RootNodes : _cached_filtered ?? (_cached_filtered = _tree.RootNodes.filtered(_filter).sorted_hard(_comparer));
|
||||
|
||||
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
|
||||
excludeFiltered ? all_rows : _tree;
|
||||
// collapse
|
||||
public void collapse_row(TKey key) => _toggle_is_expanded(key, false);
|
||||
public void collapse_all() => _toggle_is_expanded(false);
|
||||
// expand
|
||||
public void expand_row(TKey key) => _toggle_is_expanded(key, true);
|
||||
public void expand_all() => _toggle_is_expanded(true);
|
||||
// lookups
|
||||
public bool try_get_row(TKey key, out TRow row) {
|
||||
if (_tree.TryGetNode(key, out var node)) {
|
||||
row = node.value;
|
||||
return true;
|
||||
}
|
||||
row = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public TRow get_parent(TKey key) {
|
||||
if (try_get_row(key, out TRow r)) {
|
||||
return _tree.GetParent(r).value;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public IEnumerable<TRow> get_descendent_rows(TKey key) {
|
||||
if (_tree.TryGetNode(key, out var n)) {
|
||||
yield return n.value;
|
||||
foreach (var v in n.inner_values())
|
||||
yield return v;
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
public void update(IEnumerable<T> added, IEnumerable<T> changed, IEnumerable<TKey> removed) {
|
||||
foreach (var k in removed) {
|
||||
if (_tree.Remove(k))
|
||||
total_row_count--;
|
||||
}
|
||||
foreach (var v in changed) {
|
||||
_tree.TryReplace(_key(v), r => { r.on_value_changed(v); return r; });
|
||||
}
|
||||
foreach (var v in added) {
|
||||
if (_tree.Insert(_row_factory(v)))
|
||||
total_row_count++;
|
||||
}
|
||||
_cached_filtered = null;
|
||||
_refresh_hierarchical_props();
|
||||
}
|
||||
|
||||
void _refresh_hierarchical_props() {
|
||||
foreach (var n in _root_nodes)
|
||||
impl(n, 0);
|
||||
void impl(INode<TRow> n, int depth_level) {
|
||||
if (n.value.depth_level != depth_level) {
|
||||
n.value.on_depth_level_changed(depth_level);
|
||||
}
|
||||
using (var en = n.inner.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
if (!n.value.is_parent)
|
||||
n.value.on_is_parent_changed(true);
|
||||
depth_level++;
|
||||
do impl(en.Current, depth_level);
|
||||
while (en.MoveNext());
|
||||
} else if (n.value.is_parent)
|
||||
n.value.on_is_parent_changed(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _toggle_is_expanded(TKey key, bool is_expanded) =>
|
||||
_tree.TryReplace(key, r => {
|
||||
if (r.is_expanded != is_expanded)
|
||||
r.on_is_expanded_changed(is_expanded);
|
||||
return r;
|
||||
});
|
||||
|
||||
void _toggle_is_expanded(bool is_expanded) {
|
||||
foreach (var row in _tree)
|
||||
if (row.is_expanded != is_expanded)
|
||||
row.on_is_expanded_changed(is_expanded);
|
||||
}
|
||||
|
||||
T _value_selector(TRow row) => row.value;
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace bts.collections {
|
||||
class OperableHierarchicalCollectionModel<TRow, T, TKey> where TKey : IEquatable<TKey> where TRow : IHierarchicalRow<T> {
|
||||
readonly Func<T, TRow> _row_factory;
|
||||
readonly Func<T, TKey> _key;
|
||||
SortedTree<TRow, TKey> _tree;
|
||||
IComparer<TRow> _comparer;
|
||||
Func<TRow, bool> _filter;
|
||||
IEnumerable<INode<TRow>> _cached_filtered = Enumerable.Empty<INode<TRow>>();
|
||||
|
||||
public OperableHierarchicalCollectionModel(Func<T, TRow> row_factory, Func<T, TKey> key, Func<T, TKey> parent_key, TKey no_parent_key, IComparer<TRow> comparer) {
|
||||
_row_factory = row_factory;
|
||||
_tree = new SortedTree<TRow, TKey>(_comparer = comparer, el => key(el.value), el => parent_key(el.value), no_parent_key);
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public IComparer<TRow> comparer {
|
||||
get => _comparer;
|
||||
set {
|
||||
_tree = _tree.Resort(_comparer = value);
|
||||
_cached_filtered = null;
|
||||
}
|
||||
}
|
||||
public Func<TRow, bool> filter {
|
||||
get => _filter;
|
||||
set {
|
||||
_filter = value;
|
||||
_cached_filtered = null;
|
||||
_refresh_hierarchical_props();
|
||||
}
|
||||
}
|
||||
public IEnumerable<TRow> rows {
|
||||
get {
|
||||
return _root_nodes.SelectMany(impl);
|
||||
IEnumerable<TRow> impl(INode<TRow> n) {
|
||||
yield return n.value;
|
||||
if (n.value.is_expanded) {
|
||||
foreach (var _n in n.inner.SelectMany(impl))
|
||||
yield return _n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TRow> all_rows {
|
||||
get {
|
||||
return _root_nodes.SelectMany(impl);
|
||||
IEnumerable<TRow> impl(INode<TRow> n) {
|
||||
yield return n.value;
|
||||
foreach (var _n in n.inner.SelectMany(impl))
|
||||
yield return _n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int total_row_count { get; private set; }
|
||||
|
||||
IEnumerable<INode<TRow>> _root_nodes =>
|
||||
_filter == null ? _tree.RootNodes : (_cached_filtered ??= _tree.RootNodes.filtered(_filter));
|
||||
|
||||
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
|
||||
excludeFiltered ? all_rows : _tree;
|
||||
// collapse
|
||||
public void collapse_row(TKey key) => _toggle_is_expanded(key, false);
|
||||
public void collapse_all() => _toggle_is_expanded(false);
|
||||
// expand
|
||||
public void expand_row(TKey key) => _toggle_is_expanded(key, true);
|
||||
public void expand_all() => _toggle_is_expanded(true);
|
||||
// lookups
|
||||
public bool try_get_row(TKey key, out TRow row) {
|
||||
if (_tree.TryGetNode(key, out var node)) {
|
||||
row = node.value;
|
||||
return true;
|
||||
}
|
||||
row = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public TRow get_parent(TKey key) {
|
||||
if (try_get_row(key, out TRow r)) {
|
||||
return _tree.GetParent(r).value;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public IEnumerable<TRow> get_descendent_rows(TKey key) {
|
||||
if (_tree.TryGetNode(key, out var n)) {
|
||||
yield return n.value;
|
||||
foreach (var v in n.inner_values())
|
||||
yield return v;
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
public void update(IEnumerable<T> added, IEnumerable<T> changed, IEnumerable<TKey> removed) {
|
||||
foreach (var k in removed) {
|
||||
if (_tree.Remove(k))
|
||||
total_row_count--;
|
||||
}
|
||||
foreach (var v in changed) {
|
||||
_tree.TryReplace(_key(v), r => { r.on_value_changed(v); return r; });
|
||||
}
|
||||
foreach (var v in added) {
|
||||
if (_tree.Insert(_row_factory(v)))
|
||||
total_row_count++;
|
||||
}
|
||||
_cached_filtered = null;
|
||||
_refresh_hierarchical_props();
|
||||
}
|
||||
|
||||
void _refresh_hierarchical_props() {
|
||||
foreach (var n in _root_nodes)
|
||||
impl(n, 0);
|
||||
void impl(INode<TRow> n, int depth_level) {
|
||||
if (n.value.depth_level != depth_level) {
|
||||
n.value.on_depth_level_changed(depth_level);
|
||||
}
|
||||
using (var en = n.inner.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
if (!n.value.is_parent)
|
||||
n.value.on_is_parent_changed(true);
|
||||
depth_level++;
|
||||
do impl(en.Current, depth_level);
|
||||
while (en.MoveNext());
|
||||
} else if (n.value.is_parent)
|
||||
n.value.on_is_parent_changed(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _toggle_is_expanded(TKey key, bool is_expanded) =>
|
||||
_tree.TryReplace(key, r => {
|
||||
if (r.is_expanded != is_expanded)
|
||||
r.on_is_expanded_changed(is_expanded);
|
||||
return r;
|
||||
});
|
||||
|
||||
void _toggle_is_expanded(bool is_expanded) {
|
||||
foreach (var row in _tree)
|
||||
if (row.is_expanded != is_expanded)
|
||||
row.on_is_expanded_changed(is_expanded);
|
||||
}
|
||||
|
||||
T _value_selector(TRow row) => row.value;
|
||||
}
|
||||
}
|
||||
+20
-20
@@ -1,20 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.utils {
|
||||
public class ReverseComparer<T> : IComparer<T> {
|
||||
private readonly IComparer<T> _wrapped;
|
||||
|
||||
public ReverseComparer(IComparer<T> wrapped) {
|
||||
_wrapped = wrapped;
|
||||
}
|
||||
|
||||
public int Compare(T x, T y) {
|
||||
// reverse operands
|
||||
return _wrapped.Compare(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.utils {
|
||||
public class ReverseComparer<T> : IComparer<T> {
|
||||
private readonly IComparer<T> _wrapped;
|
||||
|
||||
public ReverseComparer(IComparer<T> wrapped) {
|
||||
_wrapped = wrapped;
|
||||
}
|
||||
|
||||
public int Compare(T x, T y) {
|
||||
// reverse operands
|
||||
return _wrapped.Compare(y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
this IObservable<T> source, Func<T, TResult> bootstrap_selector, Func<T, TResult> selector) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
object locker = new();
|
||||
var local_bootstrap_selector = bootstrap_selector;
|
||||
return source.Subscribe(v => {
|
||||
// The preliminary null-check for `bootstrapper` is performed outside of the lock.
|
||||
//
|
||||
@@ -45,13 +46,13 @@
|
||||
//
|
||||
// If `bootstrapper is not null`, the use of a lock becomes neccessary to ensure that
|
||||
// the observer is only bootstrapped once.
|
||||
if (bootstrap_selector is null) {
|
||||
if (local_bootstrap_selector is null) {
|
||||
o.OnNext(selector(v));
|
||||
return;
|
||||
}
|
||||
lock (locker) {
|
||||
o.OnNext((bootstrap_selector ?? selector)(v));
|
||||
bootstrap_selector = null;
|
||||
o.OnNext((local_bootstrap_selector ?? selector)(v));
|
||||
local_bootstrap_selector = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -210,6 +211,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
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))));
|
||||
|
||||
/// <summary>
|
||||
/// Uses each element of a source observable to create an inner observable of <typeparamref name="TResult"/>, unlike <see cref="Observable.SelectMany{TSource, TResult}(IObservable{TSource}, Func{TSource, IObservable{TResult}})"/>,
|
||||
/// each inner sequence is terminated when the next one begins.
|
||||
|
||||
@@ -322,7 +322,7 @@ namespace bts.api.core {
|
||||
Log.DataFlow.diag($"Create Filter {typeof(T).Name}");
|
||||
|
||||
return source.CombineLatest(predicate, (upd, pred) => (upd, pred)).Subscribe(tup => {
|
||||
bool pred_changed = tup.pred == pred_cache && pred_cache is not null;
|
||||
bool pred_changed = tup.pred != pred_cache && pred_cache is not null;
|
||||
pred_cache = tup.pred;
|
||||
unfiltered_cache = tup.upd;
|
||||
if (pred_changed && filtered_cache is not null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
@@ -10,8 +10,9 @@ namespace bts
|
||||
static int _needs_init_spark_lib = 0;
|
||||
|
||||
public static void init(string config_source = null) {
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
Console.WriteLine(
|
||||
$"Configuring BTS with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
$"Configuring Spark {version} with {(config_source is null ? "defaults" : $"'{config_source}'")}");
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
utils.BTSNativeMethods.provide_command_line(args, args.Length);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace bts.websocket_core {
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.MarketData;
|
||||
using Spark.Api.Orders;
|
||||
using Spark.Core.Collections;
|
||||
@@ -252,7 +253,9 @@ namespace bts.websocket_core {
|
||||
exchanges,
|
||||
types,
|
||||
symbols?.getuprarr(),
|
||||
expiration,
|
||||
expiration is null
|
||||
? Array.Empty<ExpirationDate>()
|
||||
: Array.ConvertAll(expiration, ExpirationDate.FixedDate),
|
||||
null,
|
||||
null,
|
||||
strikes,
|
||||
|
||||
@@ -7,6 +7,11 @@ include(${CMAKE_SOURCE_DIR}/cmake/Protobuf.cmake)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateDefaultConfig.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/PrintList.cmake)
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/cmake/version.h.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/version.h"
|
||||
)
|
||||
|
||||
if (DEFINED DOTNET_BINARY_DIR OR DEFINED DOTNET_WINDOWS_BINARY_DIR)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCopyBinariesTarget.cmake)
|
||||
endif()
|
||||
|
||||
@@ -15,4 +15,7 @@ message DatabentoMarketDataConfig {
|
||||
string dataset = 70;
|
||||
uint32 filter_securities_upto_n_months= 80;
|
||||
bool load_reference_data = 90;
|
||||
uint32 debug_sec = 100;
|
||||
bool only_securities = 110;
|
||||
string bbo_start_time = 120;
|
||||
}
|
||||
@@ -770,14 +770,14 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(
|
||||
case api::VENUE_BINANCE_USDS_FUTURES: {
|
||||
rv.set_session("binance_usds_futures_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
rv.set_account(user_ptr->name());
|
||||
rv.set_operator_id(user_ptr->name());
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_BINANCE: {
|
||||
rv.set_session("binance_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
rv.set_account(user_ptr->name());
|
||||
rv.set_operator_id(user_ptr->name());
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
#include "OrderService.h"
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
#define PARSE_STRING(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
dest.mutable_##name()->clear(); \
|
||||
it->get_to(*dest.mutable_##name()); \
|
||||
}
|
||||
#define PARSE_VALUE(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
dest.set_##name(it->get<decltype(dest.name())>()); \
|
||||
}
|
||||
#define PARSE_STRUCT(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
it->get_to(*dest.mutable_##name()); \
|
||||
}
|
||||
#define PARSE_ARRAY(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end() && it->is_array()) { \
|
||||
c.mutable_##name()->Clear(); \
|
||||
it->get_to(*c.mutable_##name()); \
|
||||
}
|
||||
#define PARSE_ONEOF(name, dest, it) \
|
||||
if (!parsed_oneof) { \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
it->get_to(*dest.mutable_##name()); \
|
||||
parsed_oneof = true; \
|
||||
} \
|
||||
}
|
||||
#define PARSE_ONEOFS(for_each) \
|
||||
{ \
|
||||
bool parsed_oneof = false; \
|
||||
for_each(PARSE_ONEOF) \
|
||||
}
|
||||
|
||||
#define WRITE_VALUE(src, name) j[#name] = src.name()
|
||||
#define WRITE_STRUCT(src, name) \
|
||||
if (src.has_##name()) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
|
||||
#define WRITE_IF_NOT_ZERO(src, name) \
|
||||
if (src.name() != 0) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
#define WRITE_IF_NOT_NAN(src, name) \
|
||||
if (!std::isnan(src.name())) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
#define WRITE_IF_NOT_EMPTY(src, name) \
|
||||
if (!src.name().empty()) { \
|
||||
j[#name] = src.name(); \
|
||||
}
|
||||
#define WRITE_TIMESTAMP(src, name) \
|
||||
j[#name] = bts::core::TimeStamp::from_nanos( \
|
||||
src.name().seconds() * 1'000'000'000 + src.name().nanos()) \
|
||||
.to_iso_string()
|
||||
|
||||
#define PARSE_TIMESTAMP(name, dest, it) \
|
||||
it = j.find(#name); \
|
||||
if (it != j.end()) { \
|
||||
bts::core::TimeStamp tmp_ts = \
|
||||
bts::core::TimeStamp::from_iso_string(it->get<std::string>()); \
|
||||
tmp_ts.to_protobuf(*dest.mutable_##name()); \
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const Fill &f) {
|
||||
WRITE_IF_NOT_ZERO(f, order_key);
|
||||
WRITE_IF_NOT_ZERO(f, security_key);
|
||||
WRITE_IF_NOT_EMPTY(f, account);
|
||||
WRITE_IF_NOT_NAN(f, last_price);
|
||||
WRITE_IF_NOT_NAN(f, avg_price);
|
||||
WRITE_VALUE(f, side);
|
||||
WRITE_IF_NOT_ZERO(f, last_shares);
|
||||
WRITE_IF_NOT_EMPTY(f, fill_key);
|
||||
WRITE_TIMESTAMP(f, timestamp);
|
||||
WRITE_IF_NOT_ZERO(f, internal_account_id);
|
||||
WRITE_VALUE(f, fill_kind);
|
||||
WRITE_VALUE(f, fill_status);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, Fill &f) {
|
||||
nlohmann::json::const_iterator it;
|
||||
PARSE_VALUE(order_key, f, it);
|
||||
PARSE_VALUE(security_key, f, it);
|
||||
PARSE_STRING(account, f, it);
|
||||
PARSE_VALUE(last_price, f, it);
|
||||
PARSE_VALUE(avg_price, f, it);
|
||||
PARSE_VALUE(side, f, it);
|
||||
PARSE_VALUE(last_shares, f, it);
|
||||
PARSE_STRING(fill_key, f, it);
|
||||
PARSE_TIMESTAMP(timestamp, f, it);
|
||||
PARSE_VALUE(internal_account_id, f, it);
|
||||
PARSE_VALUE(fill_kind, f, it);
|
||||
PARSE_VALUE(fill_status, f, it);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const NativeOrderState &n) {
|
||||
WRITE_VALUE(n, exchange_order_id);
|
||||
WRITE_VALUE(n, last_msg_seq_num);
|
||||
WRITE_VALUE(n, sub_error_code);
|
||||
WRITE_VALUE(n, native_status);
|
||||
WRITE_VALUE(n, routing_record);
|
||||
WRITE_VALUE(n, native_type);
|
||||
WRITE_VALUE(n, last_transact_time);
|
||||
WRITE_VALUE(n, last_transact_time_unix_ns);
|
||||
WRITE_VALUE(n, stop_px);
|
||||
WRITE_VALUE(n, exchange_order_id_u64);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, NativeOrderState &n) {
|
||||
nlohmann::json::const_iterator it;
|
||||
PARSE_STRING(exchange_order_id, n, it);
|
||||
PARSE_VALUE(last_msg_seq_num, n, it);
|
||||
PARSE_VALUE(sub_error_code, n, it);
|
||||
PARSE_VALUE(native_status, n, it);
|
||||
PARSE_STRUCT(routing_record, n, it);
|
||||
PARSE_VALUE(native_type, n, it);
|
||||
PARSE_STRING(last_transact_time, n, it);
|
||||
PARSE_VALUE(last_transact_time_unix_ns, n, it);
|
||||
PARSE_VALUE(stop_px, n, it);
|
||||
PARSE_VALUE(exchange_order_id_u64, n, it);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const RoutingRecord &r) {
|
||||
WRITE_IF_NOT_EMPTY(r, session);
|
||||
WRITE_IF_NOT_EMPTY(r, account);
|
||||
WRITE_IF_NOT_EMPTY(r, operator_id);
|
||||
WRITE_VALUE(r, cti_code);
|
||||
WRITE_VALUE(r, customer_or_firm);
|
||||
WRITE_IF_NOT_EMPTY(r, smp_id);
|
||||
WRITE_VALUE(r, smp_instruction);
|
||||
WRITE_IF_NOT_EMPTY(r, location);
|
||||
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
|
||||
WRITE_IF_NOT_EMPTY(r, sender_compid);
|
||||
WRITE_IF_NOT_EMPTY(r, clearing_firm);
|
||||
WRITE_IF_NOT_EMPTY(r, customer_account_ref_id);
|
||||
WRITE_IF_NOT_EMPTY(r, account_code);
|
||||
WRITE_IF_NOT_EMPTY(r, company_id);
|
||||
WRITE_IF_NOT_EMPTY(r, authorized_trader);
|
||||
WRITE_IF_NOT_EMPTY(r, authorized_group);
|
||||
WRITE_IF_NOT_EMPTY(r, cust_order_handing_inst);
|
||||
WRITE_IF_NOT_EMPTY(r, order_server_id);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, RoutingRecord &r) {
|
||||
nlohmann::json::const_iterator it;
|
||||
PARSE_STRING(session, r, it);
|
||||
PARSE_STRING(account, r, it);
|
||||
PARSE_STRING(operator_id, r, it);
|
||||
PARSE_VALUE(cti_code, r, it);
|
||||
PARSE_VALUE(customer_or_firm, r, it);
|
||||
PARSE_STRING(smp_id, r, it);
|
||||
PARSE_VALUE(smp_instruction, r, it);
|
||||
PARSE_STRING(location, r, it);
|
||||
PARSE_STRING(cust_order_handling_inst, r, it);
|
||||
PARSE_STRING(sender_compid, r, it);
|
||||
PARSE_STRING(clearing_firm, r, it);
|
||||
PARSE_STRING(customer_account_ref_id, r, it);
|
||||
PARSE_STRING(account_code, r, it);
|
||||
PARSE_STRING(company_id, r, it);
|
||||
PARSE_STRING(authorized_trader, r, it);
|
||||
PARSE_STRING(authorized_group, r, it);
|
||||
PARSE_STRING(cust_order_handing_inst, r, it);
|
||||
PARSE_STRING(order_server_id, r, it);
|
||||
}
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/api/services/OrderService.pb.h"
|
||||
#include "lib/api/services/native_order_type.h"
|
||||
#include "lib/api/services/order_status.h"
|
||||
#include "lib/api/services/time_in_force.h"
|
||||
#include "lib/api/services/trigger_price_type.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
@@ -10,22 +14,70 @@
|
||||
|
||||
namespace bts::api::orderman {
|
||||
|
||||
PB_ENUM_FROM_JSON(OrderStatus)
|
||||
PB_ENUM_TO_JSON(OrderStatus)
|
||||
PB_ENUM_FROM_JSON(Fill_FillKind)
|
||||
PB_ENUM_TO_JSON(Fill_FillKind)
|
||||
|
||||
PB_ENUM_FROM_JSON(TriggerPriceType)
|
||||
PB_ENUM_TO_JSON(TriggerPriceType)
|
||||
PB_ENUM_FROM_JSON(Fill_FillStatus)
|
||||
PB_ENUM_TO_JSON(Fill_FillStatus)
|
||||
|
||||
void to_json(nlohmann::json &j, const Fill &f);
|
||||
void from_json(const nlohmann::json &j, Fill &f);
|
||||
|
||||
PB_ENUM_FROM_JSON(OrderSubType)
|
||||
PB_ENUM_TO_JSON(OrderSubType)
|
||||
|
||||
PB_ENUM_FROM_JSON(SelfMatchPreventionInstruction)
|
||||
PB_ENUM_TO_JSON(SelfMatchPreventionInstruction)
|
||||
|
||||
PB_ENUM_FROM_JSON(CustomerOrFirm)
|
||||
PB_ENUM_TO_JSON(CustomerOrFirm)
|
||||
|
||||
void to_json(nlohmann::json &j, const RoutingRecord &r);
|
||||
void from_json(const nlohmann::json &j, RoutingRecord &r);
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderStatus)
|
||||
PB_ENUM_TO_JSON(NativeOrderStatus)
|
||||
|
||||
PB_ENUM_FROM_JSON(NativeOrderState_SubError)
|
||||
PB_ENUM_TO_JSON(NativeOrderState_SubError)
|
||||
|
||||
void to_json(nlohmann::json &j, const NativeOrderState &n);
|
||||
void from_json(const nlohmann::json &j, NativeOrderState &n);
|
||||
|
||||
} // namespace bts::api::orderman
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::OrderStatus,
|
||||
s,
|
||||
bts::api::orderman::Fill_FillKind,
|
||||
k,
|
||||
"{}",
|
||||
bts::api::orderman::OrderStatus_Name(s));
|
||||
bts::api::orderman::Fill_FillKind_Name(k));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::TriggerPriceType,
|
||||
bts::api::orderman::Fill_FillStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::Fill_FillStatus(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::OrderSubType,
|
||||
t,
|
||||
"{}",
|
||||
bts::api::orderman::TriggerPriceType_Name(t));
|
||||
bts::api::orderman::OrderSubType_Name(t));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::SelfMatchPreventionInstruction,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::SelfMatchPreventionInstruction_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderStatus,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderStatus_Name(s));
|
||||
|
||||
FMT_IMPL(
|
||||
bts::api::orderman::NativeOrderState_SubError,
|
||||
s,
|
||||
"{}",
|
||||
bts::api::orderman::NativeOrderState_SubError_Name(s));
|
||||
Loaded 100 of 379 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user