Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0282ad6df2 |
No files matched your search
@@ -90,12 +90,6 @@ 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; \
|
||||
|
||||
@@ -12,10 +12,17 @@
|
||||
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
|
||||
"runArgs": [
|
||||
"--shm-size",
|
||||
"8gb"
|
||||
],
|
||||
"mounts": [
|
||||
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
|
||||
"8gb",
|
||||
"-p",
|
||||
"8000:8000",
|
||||
"-p",
|
||||
"8001:8001",
|
||||
"-p",
|
||||
"8002:8002",
|
||||
"-p",
|
||||
"8003:8003",
|
||||
"-p",
|
||||
"12345:12345"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# This file defines the global default configuration for docker deployments.
|
||||
# It is tracked in source control. For temporary modifications, please create
|
||||
# a separate file called ".env.local" (it's in .gitignore, so won't be tracked).
|
||||
# If a variable is defined in both files, the value in .env.local will be used.
|
||||
#
|
||||
# About the deployment procedure:
|
||||
# We use Docker Compose to build images and launch containers for various services
|
||||
# that make up our software suite:
|
||||
# - sparkd
|
||||
# - TODO: pubsub
|
||||
# - TODO: md-publisher
|
||||
# - TODO: webapp (DSQA on the web)
|
||||
# - TODO: api-server (websocket based)
|
||||
# From inside a devcontainer, you can launch all services in separate containers
|
||||
# on your machine by running:
|
||||
#
|
||||
# tools/deploy.sh
|
||||
#
|
||||
# This script builds release versions for each service, packaged (generally) in
|
||||
# self-contained directories. Next, it runs a few "docker compose" commands to
|
||||
# build docker images loaded with the self-contained binaries from the previous
|
||||
# step, and start containers with those images to run each service. To view the
|
||||
# commands that deploy.sh runs internally (without actually running them), run:
|
||||
#
|
||||
# tools/deploy.sh --dry-run
|
||||
#
|
||||
# For additional information/help, run:
|
||||
#
|
||||
# tools/deploy.sh --help
|
||||
#
|
||||
|
||||
### Sparkd ###
|
||||
# The internal image for sparkd to use. For more info, view:
|
||||
# src/server/sparkd/Dockerfile
|
||||
SPARKD__IMAGE=sparkd
|
||||
# The config dir for this service to use
|
||||
SPARKD__CONFIG_DIR=src/server/sparkd/configurations/dev
|
||||
# Mount a specific volume or dir to "~/sparkdata/data" inside the docker.
|
||||
# Defaults to a volume named "sparkd_<config_port>" (e.g., sparkd_8000).
|
||||
# SPARKD__DATA_DIR=/home/bts/sparkdata/data
|
||||
SPARKD__SOURCE_DIR=install/sparkd-release-gcc
|
||||
SPARKD__RUNTIME_CONF_DIR=/home/ubuntu/8000/sparkdata/conf/
|
||||
SPARKD__CONFIG_PORT=8000
|
||||
SPARKD__QUICKBUS_PORT=8001
|
||||
SPARKD__SIM_FIX_PORT=8002
|
||||
SPARKD__SHM_SIZE=4gb
|
||||
|
||||
# TODO: add config for other services (pubsub, md-publisher, webapp, api-server, etc.)
|
||||
@@ -59,4 +59,3 @@ trace_data/
|
||||
.devcontainer/
|
||||
*.Generated.cs
|
||||
*.lst
|
||||
.env.local
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[submodule "externals"]
|
||||
path = externals
|
||||
url = https://bitbucket.org/bluetradesys/externals.git
|
||||
url = git@bitbucket.org:bluetradesys/externals.git
|
||||
[submodule "vcpkg"]
|
||||
path = vcpkg
|
||||
url = https://github.com/microsoft/vcpkg.git
|
||||
Vendored
+1
-1
@@ -326,4 +326,4 @@
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
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()
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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()
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef BTS_SPARK_VERSION_H
|
||||
#define BTS_SPARK_VERSION_H
|
||||
|
||||
#define BTS_SPARK_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define BTS_SPARK_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define BTS_SPARK_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
#define BTS_SPARK_VERSION_BUILD_METADATA @PROJECT_VERSION_BUILD_METADATA@
|
||||
#define BTS_SPARK_VERSION "@PROJECT_VERSION@"
|
||||
#define BTS_SPARK_VERSION_FULL "@PROJECT_VERSION_FULL@"
|
||||
|
||||
#endif
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
services:
|
||||
sparkd:
|
||||
volumes:
|
||||
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
|
||||
build:
|
||||
dockerfile: src/server/sparkd/Dockerfile
|
||||
args:
|
||||
- IMAGE=${SPARKD__IMAGE}
|
||||
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
|
||||
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
|
||||
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
|
||||
command: >
|
||||
sh -c "
|
||||
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
|
||||
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
|
||||
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
|
||||
fi
|
||||
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
|
||||
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
|
||||
set -a
|
||||
. /home/bts/sparkdata/conf/.env.local
|
||||
set +a
|
||||
fi
|
||||
./sparkd run"
|
||||
env_file:
|
||||
- .env
|
||||
- ${SPARKD__CONFIG_DIR}/.env
|
||||
ports:
|
||||
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
|
||||
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
|
||||
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: "${SPARKD__MEMORY:-4gb}"
|
||||
sparkd-symbols:
|
||||
volumes:
|
||||
- ${SPARKD__DATA_DIR:-sparkd_data}:/home/bts/sparkdata/data
|
||||
- ${SPARKD__RUNTIME_CONF_DIR:-sparkd_conf}:/home/bts/sparkdata/conf
|
||||
build:
|
||||
dockerfile: src/server/sparkd/Dockerfile
|
||||
args:
|
||||
- IMAGE=${SPARKD__IMAGE}
|
||||
- SOURCE_DIR=${SPARKD__SOURCE_DIR}
|
||||
- CONFIG_DIR=${SPARKD__CONFIG_DIR}
|
||||
shm_size: "${SPARKD__SHM_SIZE:-512mb}"
|
||||
command: >
|
||||
sh -c "
|
||||
echo bts | sudo -S chown -R bts:bts /home/bts/sparkdata/data
|
||||
if [ -e "/home/bts/sparkdata/data/logging.sparkd/log" ]; then
|
||||
mv "/home/bts/sparkdata/data/logging.sparkd/log" "/home/bts/sparkdata/data/logging.sparkd/log_$(date '+%Y%m%d-%H%M')"
|
||||
fi
|
||||
ls -alh /home/bts/sparkdata/conf/
|
||||
cat /home/bts/sparkdata/conf/.env.local
|
||||
if [ -e "/home/bts/sparkdata/conf/.env.local" ]; then
|
||||
echo 'Sourcing /home/bts/sparkdata/conf/.env.local'
|
||||
set -a
|
||||
. /home/bts/sparkdata/conf/.env.local
|
||||
set +a
|
||||
fi
|
||||
./sparkd run"
|
||||
env_file:
|
||||
- .env
|
||||
- ${SPARKD__CONFIG_DIR}/.env
|
||||
ports:
|
||||
- "${SPARKD__CONFIG_PORT}:${SPARKD__CONFIG_PORT}"
|
||||
- "${SPARKD__QUICKBUS_PORT}:${SPARKD__QUICKBUS_PORT}"
|
||||
- "${SPARKD__SIM_FIX_PORT}:${SPARKD__SIM_FIX_PORT}"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: "${SPARKD__MEMORY:-4gb}"
|
||||
|
||||
sparkd-tail:
|
||||
image: bluetradingsystems/linux_dotnet7
|
||||
volumes:
|
||||
- type: volume
|
||||
read_only: true
|
||||
source: sparkd_data
|
||||
target: /home/bts/sparkdata/data/logging.sparkd
|
||||
volume:
|
||||
subpath: logging.sparkd
|
||||
command: ["tail", "-f", "/home/bts/sparkdata/data/logging.sparkd/log"]
|
||||
|
||||
sparkd-ping-test:
|
||||
depends_on:
|
||||
- sparkd
|
||||
profiles:
|
||||
- ping-test
|
||||
build:
|
||||
context: .
|
||||
dockerfile_inline: |
|
||||
FROM bluetradingsystems/linux_dotnet7
|
||||
USER "root"
|
||||
RUN apt-get update && apt-get -y install iputils-ping
|
||||
command: [ "ping", "sparkd" ]
|
||||
|
||||
volumes:
|
||||
sparkd_data:
|
||||
name: "sparkd_${SPARKD__CONFIG_PORT:-8000}"
|
||||
@@ -1,38 +0,0 @@
|
||||
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,20 +1,11 @@
|
||||
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
|
||||
@@ -23,14 +14,9 @@ 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
|
||||
@@ -23,7 +23,6 @@
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
|
||||
<PackageReference Include="Parquet.Net" Version="5.0.1" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
|
||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -4,46 +4,42 @@ namespace bts.SilverPine {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
//using MathNet.Numerics.Statistics;
|
||||
|
||||
using ZstdSharp.Unsafe;
|
||||
|
||||
public enum MultiRowOp {
|
||||
public enum GroupByBundleOp {
|
||||
Mean,
|
||||
Median,
|
||||
Mode,
|
||||
Range,
|
||||
StdDev,
|
||||
Max,
|
||||
Min,
|
||||
First,
|
||||
Last,
|
||||
Sum,
|
||||
EdgeDiff,
|
||||
EdgeDiffPercentage,
|
||||
//IsEdgeDiff,
|
||||
Sum
|
||||
}
|
||||
public class MultiRowOpHelper {
|
||||
public readonly List<Tuple<string, MultiRowOp>> operations;
|
||||
public class GroupByBundle {
|
||||
public readonly List<Tuple<string, GroupByBundleOp>> operations;
|
||||
|
||||
public static MultiRowOpHelper parse(string text) {
|
||||
public static GroupByBundle parse(string text) {
|
||||
// text is inp_col: op, inp_col: op, inp_col:op
|
||||
List<Tuple<string, MultiRowOp>> rv = new List<Tuple<string, MultiRowOp>>();
|
||||
List<Tuple<string, GroupByBundleOp>> rv = new List<Tuple<string, GroupByBundleOp>>();
|
||||
string[] calcs = text.Split(',');
|
||||
foreach (string c in calcs) {
|
||||
string[] toks = c.Split(':');
|
||||
string inp_col = toks[0].Trim();
|
||||
string op_txt = toks[1].Trim();
|
||||
MultiRowOp op = Enum.Parse<MultiRowOp>(op_txt, true);
|
||||
GroupByBundleOp op = Enum.Parse<GroupByBundleOp>(op_txt, true);
|
||||
rv.Add(Tuple.Create(inp_col, op));
|
||||
}
|
||||
return new MultiRowOpHelper(rv);
|
||||
return new GroupByBundle(rv);
|
||||
}
|
||||
|
||||
public MultiRowOpHelper(List<Tuple<string, MultiRowOp>> operations_) {
|
||||
public GroupByBundle(List<Tuple<string, GroupByBundleOp>> operations_) {
|
||||
operations = operations_;
|
||||
}
|
||||
|
||||
public static IBlueRow group_by(List<IBlueRow> rows, MultiRowOpHelper parameters) {
|
||||
public static IBlueRow group_by(List<IBlueRow> rows, GroupByBundle parameters) {
|
||||
if (rows.Count() == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -51,8 +47,8 @@ namespace bts.SilverPine {
|
||||
IBlueRow ir = rows.ElementAt(0);
|
||||
IBlueTableSchema in_schema = ir.schema;
|
||||
IBlueTableSchema out_schema = in_schema;
|
||||
IDictionary<string, Tuple<string, MultiRowOp>> out_col_map =
|
||||
new Dictionary<string, Tuple<string, MultiRowOp>>();
|
||||
IDictionary<string, Tuple<string, GroupByBundleOp>> out_col_map =
|
||||
new Dictionary<string, Tuple<string, GroupByBundleOp>>();
|
||||
foreach (var t in parameters.operations) {
|
||||
string out_col_name = $"{t.Item1}_{t.Item2}".ToLower();
|
||||
out_schema = out_schema.add_column(TypeCode.Double, out_col_name);
|
||||
@@ -62,7 +58,7 @@ namespace bts.SilverPine {
|
||||
foreach (var kv in out_col_map) {
|
||||
string out_col_name = kv.Key;
|
||||
string in_col_name = kv.Value.Item1;
|
||||
MultiRowOp op = kv.Value.Item2;
|
||||
GroupByBundleOp op = kv.Value.Item2;
|
||||
IEnumerable<double> col_vals = BlueDataUtility.get_colvalues<double>(rows, in_col_name);
|
||||
double comp_res = compute_op(col_vals, op);
|
||||
or.setValue(out_col_name, comp_res);
|
||||
@@ -70,51 +66,20 @@ namespace bts.SilverPine {
|
||||
return or;
|
||||
}
|
||||
|
||||
// rolling_rows are the rolling window of rows. the 0th row is the one thats gettin
|
||||
// calculated. rows 1 - n-1 are its window rows. the schema is based on the operations.
|
||||
|
||||
public IBlueRow rolling_calc(List<IBlueRow> rolling_rows) {
|
||||
if (rolling_rows.Count() == 0) {
|
||||
return null;
|
||||
}
|
||||
IBlueRow ir = rolling_rows.ElementAt(0);
|
||||
IBlueTableSchema in_schema = ir.schema;
|
||||
IBlueTableSchema out_schema = in_schema;
|
||||
IDictionary<string, Tuple<string, MultiRowOp>> out_col_map =
|
||||
new Dictionary<string, Tuple<string, MultiRowOp>>();
|
||||
foreach (var t in operations) {
|
||||
string out_col_name = $"{t.Item1}_{t.Item2}".ToLower();
|
||||
out_schema = out_schema.add_column(TypeCode.Double, out_col_name);
|
||||
out_col_map[out_col_name] = t;
|
||||
}
|
||||
MutableBlueRow or = new MutableBlueRow(out_schema, ir);
|
||||
foreach (var kv in out_col_map) {
|
||||
string out_col_name = kv.Key;
|
||||
string in_col_name = kv.Value.Item1;
|
||||
MultiRowOp op = kv.Value.Item2;
|
||||
IEnumerable<double> col_vals = BlueDataUtility.get_colvalues<double>(rolling_rows, in_col_name);
|
||||
double comp_res = compute_op(col_vals, op);
|
||||
or.setValue(out_col_name, comp_res);
|
||||
}
|
||||
return or;
|
||||
}
|
||||
|
||||
public static double compute_op(IEnumerable<double> col_vals, MultiRowOp op) {
|
||||
public static double compute_op(IEnumerable<double> col_vals, GroupByBundleOp op) {
|
||||
switch (op) {
|
||||
case MultiRowOp.Max:
|
||||
case GroupByBundleOp.Max:
|
||||
return col_vals.Max<double>();
|
||||
case MultiRowOp.Min:
|
||||
case GroupByBundleOp.Min:
|
||||
return col_vals.Min<double>();
|
||||
case MultiRowOp.Mean:
|
||||
case GroupByBundleOp.Mean:
|
||||
return col_vals.Sum() / col_vals.Count();
|
||||
case MultiRowOp.Sum:
|
||||
case GroupByBundleOp.Sum:
|
||||
return col_vals.Sum();
|
||||
case MultiRowOp.First:
|
||||
case GroupByBundleOp.First:
|
||||
return col_vals.First();
|
||||
case MultiRowOp.Last:
|
||||
case GroupByBundleOp.Last:
|
||||
return col_vals.Last();
|
||||
case MultiRowOp.StdDev:
|
||||
return MathNet.Numerics.Statistics.Statistics.StandardDeviation(col_vals);
|
||||
default:
|
||||
// TODO
|
||||
Log.assert(false);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
using System;
|
||||
|
||||
[DataStreamUserRespresentation("AggregationStream")]
|
||||
public class AgregationStream : ViewStream {
|
||||
|
||||
public AgregationStream(ViewStream parent_str, string operation_txt) {
|
||||
ViewStream rs = new NonIncGroupByStream(parent_str, String.Empty, operation_txt);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
|
||||
public AgregationStream(ViewStream parent_str, string groupby_col, string operation_txt) {
|
||||
ViewStream rs = new NonIncGroupByStream(parent_str, groupby_col, operation_txt);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+4
-7
@@ -8,7 +8,7 @@ using bts;
|
||||
using bts.SilverPine;
|
||||
|
||||
namespace BCG {
|
||||
[DataStreamUserRespresentation("AggregateStream")]
|
||||
[DataStreamUserRespresentation("aggregate")]
|
||||
public class NonIncGroupByStream : ViewStream {
|
||||
private class DelNonIncGroupByEvaluator : INonIncGroupByEvaluator {
|
||||
Func<string[]> _groupby_cols_func;
|
||||
@@ -53,18 +53,15 @@ namespace BCG {
|
||||
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func)
|
||||
: this(main_stream, new DelNonIncGroupByEvaluator(groupby_cols_func, evaluate_func)) { }
|
||||
|
||||
public NonIncGroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_text)
|
||||
: this(parent_str, groupby_col, standard_group_by_text, String.Empty) { }
|
||||
|
||||
public NonIncGroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_text, string optional_sort_col) {
|
||||
MultiRowOpHelper std_group_by = MultiRowOpHelper.parse(standard_group_by_text);
|
||||
public NonIncGroupByStream(ViewStream parent_str, string groupby_col, string standard_group_by_text, string optional_sort_col = null) {
|
||||
GroupByBundle std_group_by = GroupByBundle.parse(standard_group_by_text);
|
||||
Func<string[]> groupby_cols_func = () => {
|
||||
return String.IsNullOrEmpty(groupby_col) ? null : new string[] { groupby_col };
|
||||
};
|
||||
Func<List<IBlueRow>, List<IBlueRow>> evaluate_func = (group_rows) => {
|
||||
if (!String.IsNullOrEmpty(optional_sort_col))
|
||||
group_rows.Sort((r1, r2) => BlueDataUtility.compare_rows(r2, r1, optional_sort_col));
|
||||
IBlueRow rv = MultiRowOpHelper.group_by(group_rows, std_group_by);
|
||||
IBlueRow rv = GroupByBundle.group_by(group_rows, std_group_by);
|
||||
return new IBlueRow[] { rv }.ToList();
|
||||
};
|
||||
ViewStream rs = new NonIncGroupByStream(parent_str, groupby_cols_func, evaluate_func);
|
||||
|
||||
@@ -69,6 +69,4 @@ namespace bts {
|
||||
myFireEvent(dp_out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -13,8 +13,9 @@ namespace BCG {
|
||||
/// desired row ie newRow = f(row), where f is the mapping fucntion. Note that the id
|
||||
/// is preserved across the rows, ie row["id"] = f(row)["id"]
|
||||
/// </summary>
|
||||
[DataStreamUserRespresentation(false)]
|
||||
public class MapStream : MethodDrivenStream {
|
||||
[DataStreamUserRespresentation (false)]
|
||||
public class MapStream : MethodDrivenStream
|
||||
{
|
||||
protected IStreamMapper myMapper;
|
||||
protected IChangeableStreamMapper myChangeableMapper = null;
|
||||
private bool myUseNewMap = Configuration.get<DataStreamConfig>().UseNewMap;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace bts.SilverPine
|
||||
{
|
||||
using BCG;
|
||||
|
||||
[DataStreamUserRespresentation("RollingComputationStream")]
|
||||
public class RollingComputationStream : ViewStream
|
||||
{
|
||||
private ViewStream _parent_str;
|
||||
public RollingComputationStream(ViewStream parent_str, string operation_txt, string roll_column, uint window_sz)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
|
||||
[DataStreamUserRespresentation("RollingComputationStream")]
|
||||
public class RollingComputationStream : ViewStream {
|
||||
public RollingComputationStream(ViewStream parent_str, string operation_txt, string roll_column, uint window_sz) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,6 @@ namespace bts {
|
||||
|
||||
}
|
||||
|
||||
[DataStreamUserRespresentation("Project")]
|
||||
public class NewProjectStream : ViewStream, IStreamMapper {
|
||||
|
||||
private ViewStream _stream;
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using mbts;
|
||||
|
||||
namespace BCG {
|
||||
internal class MutableAppendTable : IBlueRowCollection {
|
||||
private class MyRow : IBlueRow {
|
||||
namespace BCG
|
||||
{
|
||||
internal class MutableAppendTable : IBlueRowCollection
|
||||
{
|
||||
private class MyRow : IBlueRow
|
||||
{
|
||||
private IBlueRow myRow;
|
||||
private int myID;
|
||||
|
||||
public MyRow(IBlueRow r, int id) {
|
||||
public MyRow(IBlueRow r, int id)
|
||||
{
|
||||
myRow = r;
|
||||
myID = id;
|
||||
}
|
||||
@@ -18,8 +21,10 @@ namespace BCG {
|
||||
|
||||
public IBlueTableSchema schema { get { return myRow.schema; } }
|
||||
|
||||
public object this[int index] {
|
||||
get {
|
||||
public object this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (myRow.schema[index].colName == "id")
|
||||
return myID;
|
||||
else
|
||||
@@ -47,17 +52,20 @@ namespace BCG {
|
||||
|
||||
public System.Collections.ICollection Keys { get { return myRow.Keys; } }
|
||||
|
||||
public void Remove(object key) { throw new Exception("The method or operation is not implemented."); }
|
||||
public void Remove(object key) {throw new Exception("The method or operation is not implemented.");}
|
||||
|
||||
public System.Collections.ICollection Values {
|
||||
get {
|
||||
public System.Collections.ICollection Values
|
||||
{
|
||||
get
|
||||
{
|
||||
object[] vals = new object[myRow.Count];
|
||||
CopyTo(vals, 0);
|
||||
return vals;
|
||||
}
|
||||
}
|
||||
|
||||
public object this[object key] {
|
||||
public object this[object key]
|
||||
{
|
||||
get { return key is string ? this[key as string] : this[(int)key]; }
|
||||
set { throw new Exception("The method or operation is not implemented."); }
|
||||
}
|
||||
@@ -66,7 +74,8 @@ namespace BCG {
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
public void CopyTo(Array array, int index) {
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
myRow.CopyTo(array, index);
|
||||
array.SetValue(myID, index + myRow.schema["id"].colNo);
|
||||
}
|
||||
@@ -81,7 +90,8 @@ namespace BCG {
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new BlueRowEnumerator(this); }
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{ return new BlueRowEnumerator(this); }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -96,7 +106,8 @@ namespace BCG {
|
||||
|
||||
public MutableAppendTable(IBlueTableSchema schema) { myRows = new BlueTable(schema); }
|
||||
|
||||
public IBlueRow addRow(IBlueRow r, int tableNo) {
|
||||
public IBlueRow addRow(IBlueRow r, int tableNo)
|
||||
{
|
||||
if (!myIDMap.ContainsKey(tableNo))
|
||||
myIDMap.Add(tableNo, new Dictionary<int, IBlueRow>());
|
||||
IBlueRow nr = new MyRow(r, myID++); // BlueDataUtility.getNewReplacedRow(r, myID++, "id");
|
||||
@@ -105,17 +116,20 @@ namespace BCG {
|
||||
return nr;
|
||||
}
|
||||
|
||||
public IBlueRow removeRow(IBlueRow r, int tableNo) {
|
||||
public IBlueRow removeRow(IBlueRow r, int tableNo)
|
||||
{
|
||||
IBlueRow dr = myIDMap[tableNo][(int)r["id"]];
|
||||
myRows.deleteRow(dr);
|
||||
myIDMap[tableNo].Remove((int)r["id"]);
|
||||
return dr;
|
||||
}
|
||||
|
||||
public IBlueRowCollection removeTable(int tableNo) {
|
||||
public IBlueRowCollection removeTable(int tableNo)
|
||||
{
|
||||
if (!myIDMap.ContainsKey(tableNo))
|
||||
return null;
|
||||
else {
|
||||
else
|
||||
{
|
||||
BlueTable rv = new BlueTable(myRows.schema);
|
||||
foreach (IBlueRow r in myIDMap[tableNo].Values)
|
||||
rv.addRow(r);
|
||||
@@ -125,7 +139,8 @@ namespace BCG {
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
myRows.clear();
|
||||
myIDMap.Clear();
|
||||
}
|
||||
@@ -136,7 +151,7 @@ namespace BCG {
|
||||
|
||||
public IBlueTableSchema schema { get { return myRows.schema; } }
|
||||
|
||||
public bool containsID(int id) { return myRows.containsID(id); }
|
||||
public bool containsID(int id) {return myRows.containsID(id);}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -154,7 +169,7 @@ namespace BCG {
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
public System.Collections.IEnumerator GetEnumerator() { return myRows.GetEnumerator(); }
|
||||
public System.Collections.IEnumerator GetEnumerator() {return myRows.GetEnumerator();}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -164,8 +179,8 @@ namespace BCG {
|
||||
/// to append (or remove) more streams at
|
||||
/// runtime
|
||||
/// </summary>
|
||||
[DataStreamUserRespresentation("UnionStream")]
|
||||
public class MutableAppendStream : ViewStream {
|
||||
public class MutableAppendStream : ViewStream
|
||||
{
|
||||
private bool mySubscriptionActive = false;
|
||||
private MutableAppendTable myTable;
|
||||
private int myMovingIndex;
|
||||
@@ -173,17 +188,22 @@ namespace BCG {
|
||||
private bool _allign_col_orders = true;
|
||||
|
||||
public MutableAppendStream(params ViewStream[] streams)
|
||||
: this(new List<ViewStream>(streams)) { }
|
||||
: this(new List<ViewStream>(streams))
|
||||
{ }
|
||||
|
||||
public MutableAppendStream(bool allign_col_order, params ViewStream[] streams)
|
||||
: this(allign_col_order, new List<ViewStream>(streams)) { }
|
||||
: this(allign_col_order, new List<ViewStream>(streams))
|
||||
{ }
|
||||
|
||||
public MutableAppendStream(ICollection<ViewStream> streams)
|
||||
: this(true, streams) { }
|
||||
: this(true, streams)
|
||||
{ }
|
||||
|
||||
public MutableAppendStream(bool allign_col_orders, ICollection<ViewStream> streams) {
|
||||
public MutableAppendStream(bool allign_col_orders, ICollection<ViewStream> streams)
|
||||
{
|
||||
_allign_col_orders = allign_col_orders;
|
||||
foreach (ViewStream vs in streams) {
|
||||
foreach (ViewStream vs in streams)
|
||||
{
|
||||
myMovingIndex++;
|
||||
ViewStream pj = new ChangeTableNameStream(vs, myMovingIndex.ToString());
|
||||
if (_allign_col_orders)
|
||||
@@ -192,7 +212,8 @@ namespace BCG {
|
||||
}
|
||||
}
|
||||
|
||||
public void addStream(ViewStream vs) {
|
||||
public void addStream(ViewStream vs)
|
||||
{
|
||||
myMovingIndex++;
|
||||
///the change name is done to identify the callbacks, and the project to
|
||||
///make all the schemas col orders the same
|
||||
@@ -204,14 +225,19 @@ namespace BCG {
|
||||
pj.onChange += new ChangeDelegate(myHandleStreamChanges);
|
||||
}
|
||||
|
||||
public void removeStream(ViewStream vs) {
|
||||
public void removeStream(ViewStream vs)
|
||||
{
|
||||
int index = -1;
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex) {
|
||||
if (p.Value.first == vs) {
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
|
||||
{
|
||||
if (p.Value.first == vs)
|
||||
{
|
||||
index = p.Key;
|
||||
if (mySubscriptionActive) {
|
||||
if (mySubscriptionActive)
|
||||
{
|
||||
p.Value.second.onChange -= new ChangeDelegate(myHandleStreamChanges);
|
||||
if (myTable != null) {
|
||||
if (myTable != null)
|
||||
{
|
||||
IBlueRowCollection delRows = myTable.removeTable(p.Key);
|
||||
if (delRows != null)
|
||||
myFireEvent(new DataPacket(myTable, new BlueTable(myTable.schema), delRows));
|
||||
@@ -224,13 +250,15 @@ namespace BCG {
|
||||
}
|
||||
}
|
||||
|
||||
protected override void myOnFirstSubscriber() {
|
||||
protected override void myOnFirstSubscriber()
|
||||
{
|
||||
mySubscriptionActive = true;
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
|
||||
p.Value.second.onChange += new ChangeDelegate(myHandleStreamChanges);
|
||||
}
|
||||
|
||||
protected override void myOnLastSubscriber() {
|
||||
protected override void myOnLastSubscriber()
|
||||
{
|
||||
mySubscriptionActive = false;
|
||||
foreach (KeyValuePair<int, Pair<ViewStream, ViewStream>> p in myStreamIndex)
|
||||
p.Value.second.onChange -= new ChangeDelegate(myHandleStreamChanges);
|
||||
@@ -238,17 +266,20 @@ namespace BCG {
|
||||
myTable.clear();
|
||||
}
|
||||
|
||||
private void myHandleStreamChanges(object newValue) {
|
||||
private void myHandleStreamChanges(object newValue)
|
||||
{
|
||||
DataPacket dp = newValue as DataPacket;
|
||||
myTable = myTable ?? new MutableAppendTable(new BlueTableSchema("MutableAppend", dp.schema.columnInfo));
|
||||
BlueTable adds = new BlueTable(myTable.schema),
|
||||
dels = new BlueTable(myTable.schema);
|
||||
int index = Int32.Parse(dp.schema.tableName);
|
||||
foreach (IBlueRow r in dp.deletedRows) {
|
||||
foreach (IBlueRow r in dp.deletedRows)
|
||||
{
|
||||
IBlueRow dr = myTable.removeRow(r, index);
|
||||
dels.addRow(dr);
|
||||
}
|
||||
foreach (IBlueRow r in dp.addedRows) {
|
||||
foreach (IBlueRow r in dp.addedRows)
|
||||
{
|
||||
IBlueRow nr = myTable.addRow(r, index);
|
||||
adds.addRow(nr);
|
||||
}
|
||||
|
||||
@@ -43,11 +43,4 @@ namespace bts.SilverPine {
|
||||
mySetRealSource(r);
|
||||
}
|
||||
}
|
||||
|
||||
[DataStreamUserRespresentation("PeriodicVolumeAgg")]
|
||||
public class PeriodicVolumeAggregationStream : ViewStream {
|
||||
public PeriodicVolumeAggregationStream(string future_contract, string contract_type, DateTime range_beg, DateTime range_end,
|
||||
TimeSpan period, ViewStreamDatabase db) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,9 +100,9 @@ namespace bts.SilverPine {
|
||||
this(symbol_or_contract, beg_dt, end_dt, ViewStreamDatabase.instance) { }
|
||||
|
||||
public VWAPStream(string symbol_or_contract, DateTime beg_dt, DateTime end_dt, ViewStreamDatabase db) {
|
||||
//var fc = new ContractUtils.FutureContract(symbol_or_contract);
|
||||
ViewStream r = new FutureHistoricalBarsStream(symbol_or_contract, beg_dt.ToString(), end_dt.ToString());
|
||||
// since this is already filtered, we can groupBy iit without a groupBy column
|
||||
var fc = new ContractUtils.FutureContract(symbol_or_contract);
|
||||
ViewStream r = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(beg_dt, end_dt));
|
||||
|
||||
r = new MathStream(r,
|
||||
"vprice = volume * close");
|
||||
r = new NonIncGroupByStream(r,
|
||||
|
||||
+13
-8
@@ -90,15 +90,20 @@ namespace bts.SilverPine {
|
||||
// the argument is symbol, like "ES" etc and not the contract itself
|
||||
public FuturesContinousContractStream(string symbol) : this(symbol, ViewStreamDatabase.instance) { }
|
||||
public FuturesContinousContractStream(string symbol, ViewStreamDatabase db) {
|
||||
symbol = symbol.EndsWith("*") ? symbol : symbol + "*";
|
||||
ViewStream r = new FuturesHistorical1DayBarsStream(symbol);
|
||||
r = new ContractExpanderStream(r, "symbol");
|
||||
r = new FilterStream(r, "contract_type='Future'");
|
||||
// finding the max volume across symbols, if we need to do this for open_interest, then join here
|
||||
// with the open interest stream on symbol/contract
|
||||
r = new NonIncGroupByStream(r, "ts_event", "volume:Max");
|
||||
r = new NewProjectStream(r, "ts_event symbol !");
|
||||
r = new SortStream(r, "-ts_event");
|
||||
// filtering for futures only
|
||||
r = new FilterStream(r, r => {
|
||||
string contract = r.get<string>("symbol");
|
||||
var fc = new ContractUtils.FutureContract(contract);
|
||||
return fc.future_tradable_contract != null;
|
||||
});
|
||||
r = new NonIncGroupByStream(r,
|
||||
"ts_event",
|
||||
"volume:Max");
|
||||
r = new NewProjectStream(r,
|
||||
"ts_event symbol !");
|
||||
r = new SortStream(r,
|
||||
"-ts_event");
|
||||
mySetRealSource(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@ namespace bts.SilverPine {
|
||||
using bts;
|
||||
using BCG;
|
||||
|
||||
// it uses the volume (better to use open interest perhap) to detect the rollover, ie when a new
|
||||
// future exp becomes the highest volume one
|
||||
// it uses the volume - better to use open interst perhap - to detect the rollover, is when a new
|
||||
// futuere exp becomes the highest volume one
|
||||
[DataStreamUserRespresentation("FuturesRoll")]
|
||||
public class FuturesRollStream : ViewStream {
|
||||
public FuturesRollStream(string future_symbol) {
|
||||
ViewStream rs = new FuturesContinousContractStream(future_symbol);
|
||||
rs = new ColumnEvolutionStream(rs, "ts_event", "symbol");
|
||||
rs = new NewProjectStream(rs, "symbol ts_event:roll_date !");
|
||||
//ViewStream ohlcv_str = new OHLCVStream(future_symbol);
|
||||
ViewStream rs = new FuturesActiveContractStream(future_symbol);
|
||||
rs = new ColumnEvolutionStream(rs, "ts_event", "contract");
|
||||
rs = new NewProjectStream(rs, "ts_event:roll_date !");
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,21 @@ namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
using bts;
|
||||
// used directly in blueorb
|
||||
|
||||
[DataStreamUserRespresentation("Settlement")]
|
||||
public class SettlementPriceStream : ViewStream {
|
||||
|
||||
// public SettlementPriceStream(string future_name, string date_txt)
|
||||
// : this(future_name, date_txt, date_txt) { }
|
||||
public SettlementPriceStream(string future_name, string date_txt)
|
||||
: this(future_name, date_txt, date_txt) { }
|
||||
|
||||
// public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
|
||||
// : this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
|
||||
public SettlementPriceStream(string future_name, string begin_dt, string end_dt)
|
||||
: this(future_name, DateTime.Parse(begin_dt).Date, DateTime.Parse(end_dt).Date) { }
|
||||
|
||||
public SettlementPriceStream(string future_contract, DateTime begin_dt)
|
||||
: this(future_contract, begin_dt, begin_dt) { }
|
||||
public SettlementPriceStream(string symbol_or_contract, DateTime begin_dt)
|
||||
: this(symbol_or_contract, begin_dt, begin_dt) { }
|
||||
|
||||
public SettlementPriceStream(string future_contract, DateTime begin_dt, DateTime end_dt) {
|
||||
ContractUtils.FutureContract fc = new ContractUtils.FutureContract(future_contract);
|
||||
public SettlementPriceStream(string symbol_or_contract, DateTime begin_dt, DateTime end_dt) {
|
||||
ContractUtils.FutureContract fc = new ContractUtils.FutureContract(symbol_or_contract);
|
||||
ViewStream rs = new FutureHistoricalBarsStream(fc, BarSize.ONE_DAY, new DateTimeRange(begin_dt, end_dt));
|
||||
rs = new NewProjectStream(rs, "symbol ts_event:date close:settlement !");
|
||||
mySetRealSource(rs);
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
# **DSL Master Learning Prompt**
|
||||
|
||||
This prompt is designed to teach the foundational concepts of the DSL for financial data streaming and transformation. The DSL operates on "streams," which fetch, transform, and process financial market data in real-time or historical contexts. Forget everything I have taught you about this DSL until now (since there are some contradicting and breaking changes that can not be reconciled with anything you might have learned before) and re-learn from this prompt.
|
||||
|
||||
# **Introduction to Streams**
|
||||
|
||||
The DSL operates on "streams," its core functional units. Streams fetch tabular (from database, text files or network) or transform/operate on tabular data. They either act as **Datasources** (e.g., FutureHistoricalBars), that bootstrap the data or **Operators** (e.g., `FilterStream, ProjectStream, SingleRowMathStream, GroupByStream` etc) that transform or filter tabular data. Though the DSL can work on any tabular data, it is primarily written for streaming or static financial data
|
||||
|
||||
# **Streams**
|
||||
|
||||
## **FutureHistoricalBars (DataSource Stream)**
|
||||
|
||||
The `FutureHistoricalBars` stream fetches OHLCV (Open, High, Low, Close, Volume) bars for financial futures contracts. It acts as a **data source** and provides the initial table that can be further processed by operators.
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. **`Future_contract` (string):** Specifies the contract to fetch data for. This can be one of the following 3 types
|
||||
1. **Continuous contracts:** Use the base symbol (e.g., `ES` for S\&P 500 futures, CL for WTI Crude Oil Futures, GC for Gold Futures etc).
|
||||
2. **Specific contracts:** Use a detailed symbol (e.g., `ESZ9` for December 2029 S\&P 500 futures, `GCU9`, for September 2025 Gold Future, `SIH9-J9`,
|
||||
For SIlver March April 2019 Silver Future Calendar spread)
|
||||
3. **All Contracts for a symbol:** If instead of specifying individual contracts, data for all contracts for a specific symbol is needed, use the symbol name followed by *.* So `ES*` for all SP Futures and spread contracts, ZC\* for all corn contracts etc.
|
||||
2. **`Bar_size` (string):** The second argument defines the granularity of the bars. Accepted values:
|
||||
* `ONE_DAY` – Daily bars.
|
||||
* `ONE_MIN` – Minute bars.
|
||||
3. **Optional:**
|
||||
* **`range_begin` (datetime):** Start of the desired time range.
|
||||
* **`range_end` (datetime):** End of the desired time range.
|
||||
|
||||
#### **Schema:**
|
||||
|
||||
The output of `FutureHistoricalBars` produces a table with the following schema (this is a classic OHLCV schema that you can find online easily)
|
||||
|
||||
| id | ts\_event | contract | open | high | low | close | volume |
|
||||
| :--- | :-------------------- | :------- | :------ | :------ | :------ | :------ | :----- |
|
||||
| `0` | `2024-06-08T00:00:00` | `ESZ9` | 5001.50 | 5052.25 | 4990.75 | 5025.50 | 866000 |
|
||||
| | | | | | | | |
|
||||
|
||||
# **FilterStream (Operator Stream)**
|
||||
|
||||
Filters rows of a parent stream based on conditions applied to its columns.
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. `parent_stream (another stream)` This stream provides the data, that the FilterStream subscribers to
|
||||
2. `filter_condition (string)` This is a text expressing a boolean condition that is applied to each row of the parent stream, if it returns false, the row is filtered out
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
1. **NL prompt:** Show me all the days when the open price for SP 500 Futures was greater than or equal to 5000:
|
||||
**DSL:** `r = FilterStream(FutureHistoricalBars("ES", "ONE_DAY"), "open >= 5000")`
|
||||
2. **NL prompt:** Show me all the days when the close price for SP 500 Futures was less than 3000 and volume was greater than half a million
|
||||
**DSL:** `r = FilterStream(FutureHistoricalBars("ES", "ONE_MIN"), "close < 3000 and volume > 500000)`
|
||||
3. **NL prompt:** Show me all the daily bars for the SP 500 Future spread ESU9-Z9 where its volume exceeded 100K
|
||||
**DSL:** `r = FilterStream(FutureHistoricalBars("ES*", "ONE_MIN"), "contract = ‘ESU9-Z9’ and volume >= 100000”)`
|
||||
|
||||
|
||||
**Notice** how expressions can be joined with AND and OR. These expressions can be nested with parenthesis.Also string needs to be in single quotes.
|
||||
|
||||
# **ProjectStream (Operator Stream)**
|
||||
|
||||
Changes the name of the columns in the parent stream. So for example, if we have to rename the close column to settlement, we can do this with ProjectStream
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
3. `parent_stream (another stream)` This stream provides the data, that the ProjectStream subscribers to
|
||||
4. `renaming (string)` This is a text expressed in the form original\_col\_name:projected\_col\_name, original\_col\_name:projected\_col\_name,
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
The following DSL renames the open column to O, high to H, low to L and close to C
|
||||
**DSL:** `r = ProjectStream(FutureHistoricalBars("ES", "ONE_DAY"), "open:O, high:H, low:L, close:C”)`
|
||||
|
||||
# **GroupByStream (Operator Stream)**
|
||||
|
||||
The `GroupByStream` function is a DSL operator used to perform aggregation operations on rows of a parent stream. It works similarly to SQL’s `GROUP BY`, allowing rows to be grouped by a specific column (optional) and applying aggregation functions to other columns.
|
||||
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. **parent\_stream (another stream):** The source stream provides the data to be operated on
|
||||
2. **Group By Column (string, optional):**
|
||||
|
||||
A column in the schema of the parent stream used to group rows. All rows with the same value in this column are aggregated into a single group. If omitted, the aggregation is performed over all rows of the parent stream.
|
||||
|
||||
3. **Operation Text (string):** A string defining the column(s) to be aggregated and the operation(s) to apply, in the format:
|
||||
`<column_name: operation>, <column_name: operation>, …` Each `<column_name: operation>` pair produces a new column in the output stream. The new column name is formatted as `<column_name>_<operation>` in lowercase.
|
||||
|
||||
---
|
||||
|
||||
#### **Valid Aggregation Operations:**
|
||||
|
||||
* `Mean`: Arithmetic mean of the values.
|
||||
* `Median`: Median of the values.
|
||||
* `Mode`: Most frequently occurring value.
|
||||
* `Range`: Difference between the max and min.
|
||||
* `StdDev`: Standard deviation of the values.
|
||||
* `Max`: Maximum value.
|
||||
* `Min`: Minimum value.
|
||||
* `First`: First value in the group.
|
||||
* `Last`: Last value in the group.
|
||||
* `Sum`: Sum of the values.
|
||||
* `EdgeDiff`: Difference between the edge values, meaning the difference between a certain col belonging to the first and last rows respectively
|
||||
* `EdgeDiffPercentage`: Same as `EdgeDiff` but expressed as percentage instead
|
||||
|
||||
---
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
1. **NL:**
|
||||
Find the total SP 500 volume executed in the first week of 2021\.
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-11T00:00:00”)`
|
||||
`result = GroupByStream(bars,”volume_Sum”)`
|
||||
2. **NL:**
|
||||
**Find the highest price and lowest price of the WTI Crude Oil futures over the first half of 2024**
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-06T00:00:00”)`
|
||||
`result = GroupByStream(bars,”high:Max low:Min”)`
|
||||
`result = ProjectStream(result,”high_max:highest_price min_low: lowest_price”)`
|
||||
3. **NL:**
|
||||
**Find the Average daily volume per contract in Natural Gas in 2023**
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("NG*", “ONE_DAY”, “2023-01-03T00:00:00”,“2023-31-12T23:59:59”)`
|
||||
`result = GroupByStream(bars, “contract”, ”volume:Mean”)`
|
||||
`result = ProjectStream(result,”mean_volume:average_daily_volume”)`
|
||||
|
||||
---
|
||||
|
||||
#### **Schema Impact:**
|
||||
|
||||
The `AggregateStream` modifies the schema of the parent stream by adding new columns for each aggregation operation. Existing columns are unaffected.
|
||||
|
||||
# **SingleRowMathStream (Operator Stream)**
|
||||
|
||||
The `SingleRowMathStream` is a DSL operator used to perform math operations, one row at a time, on the parent stream.
|
||||
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. **parent\_stream (another stream):**The source stream provides the base data
|
||||
2. **math\_expression(string):** This is a math expression which allows the creation of one or more new columns that are the output of a math expression on the existing columns provided by parent\_stream. This can a simple math operation like new\_col \= a \+ b, or any function from C\#’s System.Math namespace, like new\_column \= Math.Log(Math.Pow(volume, 1.1))
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
4. **NL:**
|
||||
Find the daily price movement in the SP 500 Futures through the month of Jan
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2024-01-01T00:00:00”,“2024-01-31T00:00:00”)`
|
||||
`result = SingleRowMathStream(bars,”daily_move=close-open”)`
|
||||
5. **NL:**
|
||||
Find the daily typical price of the WTI Crude Oil futures over the first half of 2024
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-06T00:00:00”)`
|
||||
`result = SingleRowMathStream(bars, “typical_price = (high+low+close)/3.0”)`
|
||||
|
||||
#### **Input Space (Scope):** The scope of this operation is a **single row.** The SingleRowMAthStream as the name signifies, operates on columns of the same row only. **No multi row operations are allowed.** For multi row operations using what follows.
|
||||
|
||||
**Schema Impact:**
|
||||
|
||||
# **MultiRowRollingMathStream (Operator Stream)**
|
||||
|
||||
The `MultiRowRollingMathStream` function is a DSL operator used to perform mostly math operations on a rolling set of rows of a parent stream. It works similarly to the GroupByStream (described above) but instead of grouping rows together based on a column value, it operates on a rolling set of rows (where the rolling size is provided as an argument).
|
||||
|
||||
---
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. **Parent Stream (another stream):** The source stream provides the data to be operated on
|
||||
2. **Operation Text (string):** A string defining the column(s) to be operated and the operation(s) to apply, in the format:
|
||||
`<column_name: operation>, <column_name: operation>, …` Each `<column_name: operation>` pair produces a new column in the output stream. The new column name is formatted as `<column_name>_<operation>` in lowercase. This is like the GroupByStream’s operations string.
|
||||
3. **Sort/Order Column Name(string):** This controls the ordering of the rolling rows. This argument will be in the format \-`<column_name>` to sort the rolling rows in descending order by the `<column_name>`, or \+`<column_name> in` ascending order.
|
||||
4. **Rolling Window Size(int)(optional):** The size of the rolling window, If this argument is left out all, the operations are applied to all rows, is Global Operation instead of Rolling Window Operation (it's like the optional groupBy col in GroupByStream).
|
||||
|
||||
---
|
||||
|
||||
#### **Valid Aggregation Operations:**
|
||||
|
||||
* `Mean`: Arithmetic mean of the values.
|
||||
* `Median`: Median of the values.
|
||||
* `Mode`: Most frequently occurring value.
|
||||
* `Range`: Difference between the max and min.
|
||||
* `StdDev`: Standard deviation of the values.
|
||||
* `Max`: Maximum value.
|
||||
* `Min`: Minimum value.
|
||||
* `First`: First value in the group.
|
||||
* `Last`: Last value in the group.
|
||||
* `Sum`: Sum of the values.
|
||||
* `EdgeDiff`: Difference between the edge values, meaning the difference between a certain col belonging to the first and last rows respectively
|
||||
* `EdgeDiffPercentage`: Same as `EdgeDiff` but expressed as percentage instead
|
||||
|
||||
---
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
1. **NL:**
|
||||
**Find the 20 d MA of SP 500 Futures close prices**
|
||||
`bars = FutureHistoricalBars("ES", “ONE_DAY”, “2021-01-03T00:00:00”,“2021-01-11T00:00:00”)`
|
||||
`result = MultirowRollingMathStream(bars, “close:Mean”, “-ts_event”, 20)`
|
||||
2. **NL:**
|
||||
**Find the 90d Standard Deviation of WTI Crude Oil during 2023**
|
||||
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("CL", “ONE_DAY”, “2023-01-01T00:00:00”,“2023-12-31T00:00:00”)`
|
||||
`// calculating typical_price`
|
||||
|
||||
`result = SingleRowMathStream(bars, “typical_price = (high+low+close)/3.0”)`
|
||||
`result = MultirowRollingMathStream(bars,“typical_price:StdDev”, “-ts_event”, 90)`
|
||||
|
||||
`result=ProjectStream(result, “stddev_typical_price:90dStdDev”)`
|
||||
|
||||
3. **NL:**
|
||||
**Find the Average daily volume of Natural Gas in 2023**
|
||||
**DSL:**
|
||||
`bars = FutureHistoricalBars("NG", “ONE_DAY”, “2023-01-03T00:00:00”,“2023-31-12T23:59:59”)`
|
||||
`result = MultirowRollingMathStream(bars, ”volume:Mean”, “-ts_event”)`
|
||||
`result = ProjectStream(result,”mean_volume:average_daily_volume”)`
|
||||
|
||||
---
|
||||
|
||||
#### **Schema Impact:**
|
||||
|
||||
The `MultirowRollingMathStream` modifies the schema of the parent stream by adding new columns for each operation. Existing columns are unaffected.
|
||||
|
||||
# **UnionStream (Operator Stream)**
|
||||
|
||||
Like the SQL Union appends all the rows from 2 streams together.
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
5. `parent_stream1 (first stream)` This stream provides part of the data that will will be appended to the other part, provided by the following argument
|
||||
6. `parent_stream2 (second stream)` This stream provides the other part of the data that will will be appended to the first part, provided by the first argument
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
4. **NL prompt:** Show me all the bars across SP 500 Futures and Nasdaq Futures
|
||||
**DSL:** `r = UnionStream(FutureHistoricalBars("ES", "ONE_DAY"), FutureHistoricalBars("NQ", "ONE_DAY"))`
|
||||
|
||||
|
||||
#### **Schema Impact:**
|
||||
|
||||
None
|
||||
|
||||
# **DateTimeExpanderStream (Operator Stream)**
|
||||
|
||||
DateTimExpanderStream expands one datetime column into various datetime components, like seconds in day, day of week, date etc, so new columns are added for those. The existing columns remain as is.
|
||||
|
||||
#### **Stream arguments (parameters):**
|
||||
|
||||
1. `parent_stream1 (base stream)` This parent stream that provides the data, including the datetime columns that will be expanded into multiple columns
|
||||
2. `Datetime_input_column (string)` This is the column that will be expanded into multiple columns.
|
||||
|
||||
**New Columns:**
|
||||
`<input_dt_col>_date` (Date of the value, without the time part)
|
||||
|
||||
* `<input_dt_col>_day,` (Day of the week)
|
||||
* `<input_dt_col>_day_of_month` (day of the month, This is an int between 0 and 31\)
|
||||
* `<input_dt_col>_month,` (month of the year, this is a numerical value between 1 and 12
|
||||
* `input_dt_col>_hour,` (Hour of the day, value bw 0 and 23\)
|
||||
* `<input_dt_col>_min",` (month part of this DateTime. Value is integer between 1 and 12).
|
||||
* `<input_dt_col>_seconds_of_day",` (Total seconds of the day)
|
||||
|
||||
#### **Examples:**
|
||||
|
||||
**DSL:** `r = DateTimeExpanderStream(FutureHistoricalBars("ES", "ONE_DAY"), “ts_event”)`
|
||||
**Input Schema:**
|
||||
|
||||
| id | ts\_event | contract | open | high | low | close | volume |
|
||||
| :--- | :-------- | :------- | :--- | :--- | :--- | :---- | :----- |
|
||||
|
||||
**Output Schema:**
|
||||
|
||||
| id | ts\_event | contract | open | high | low | close | volume | | | | | | | |
|
||||
| :--- | :-------- | :------- | :--- | :--- | :--- | :---- | :----- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
|
||||
@@ -2,13 +2,9 @@
|
||||
"prompts": [
|
||||
{
|
||||
"category": "Settlement",
|
||||
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is Settlement (symbol_or_contract, datetime1). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or ESZ9 etc. The second argument is the date at which the settlement is to be fetched. There is an overloaded function, Settlement (symbol_or_contract, begin_dt, end_dt) which gets the settlements for a range pf dates from begin_dt to end_dt. You will need to convert statements expressed as Today, Yesterday, Last Friday, First Friday of the month, Last Thursday of August, First day of last quarter etc to actual dates. The function Settlement's second and third arguments are dates, so never pass a string like FirstDayOfLastQUarter. You will need to know what Today is, since there will be references to last week, yesterday etc.",
|
||||
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is Settlement (symbol_or_contract, datetime1). The first argument is a future (like CL, for crude oil) or a specific future contract like crude oil Dec 24 future (CLZ4). The second argument is the date at which the settlement is to be fetched. There is an overloaded function, Settlement (symbol_or_contract, begin_dt, end_dt) which gets the settlements for all dates from begin_dt to end_dt. This will also have datetimes expressed as Today, Yesterday, Last Friday, First Friday of the month, Last Thursday of August etc. You will need to know what Today is, since there will be referecnes to last week, yesterday etc. These examples are assuming Today is 10/11/2024, however you should use the Today's real date",
|
||||
"prompt": "What was the settlement for SP 500 Future on June 8th?",
|
||||
"examples": [
|
||||
{
|
||||
"natural_language": "What was the settlement for SP 500 Future on June 8th?",
|
||||
"dsl": "Settlement(\"ES\", 6/8/2023)"
|
||||
},
|
||||
{
|
||||
"natural_language": "Find Settlement price of the SP500 future on 04/22/2023.",
|
||||
"dsl": "Settlement(\"ES\", 4/22/2023)"
|
||||
@@ -45,7 +41,7 @@
|
||||
},
|
||||
{
|
||||
"category": "VWAP",
|
||||
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is VWAP (symbol_or_contract, period_start_date, period_end_date). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or SP 500 Dec 2024 (ESZ4). The second and third argument define the start and end of the VWAP period, chronologically. So for example, if we are calculating the 10 day VWAP on 10/15/2023, the period will be from 10/05/2023 to 10/15/2023 (to cover for 10 days going back from the given date). so the second argument will 10/05/2023 and the third argument will be 10/15/2023. The NL prompt will also have datetimes expressed as Today, Yesterday, Last Friday, Last Sunday, First day of quarter, Today etc. You will need to translate these as dates that need to be passed to the VWAP DSL function. ",
|
||||
"description": "This is a few shot learning example for an NL to DSL. The DSL function in question is VWAP (symbol_or_contract, perio_end_date, period_start_date). The first argument is a future (like CL, for crude oil, or ES for SP 500) or a specific future contract like crude oil Dec 24 future (CLZ4) or SP 500 Dec 2024 (ESZ4). The second and third argument define the end and start of the VWAP period, respectively. So for example, if we are calculating the 10 day VWAP on 10/15/2023, the period will be from 10/15/2023 going back to 10/05/2023. so the second argument will 10/05/2023 and the third argument will be 10/15/2023. The NL prompt will also have datetimes expressed as Today, Yesterday, Last Friday, Last Sunday, First day of quarter, Today etc. You will need to translate these as dates that need to be passed to the VWAP DSL function. ",
|
||||
"prompt": "What was the VWAP for SP 500 Future over the past 30 days?",
|
||||
"examples": [
|
||||
{
|
||||
@@ -53,39 +49,31 @@
|
||||
"dsl": "VWAP(\"ES\", 4/02/2023, 4/22/2023)"
|
||||
},
|
||||
{
|
||||
"natural_language": "Find the 10 day VWAP of the Crude Oil Future on 10/10/2023.",
|
||||
"dsl": "VWAP(\"CL\", 10/01/2023, 10/10/2023)"
|
||||
"natural_language": "Find Yesterday's 10 day VWAP of the Crude Oil Future.",
|
||||
"dsl": "VWAP(\"CL\", 10/01/2024, 10/10/2024)"
|
||||
},
|
||||
{
|
||||
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future on 9/11/2023.",
|
||||
"dsl": "VWAP(\"ZN\", 8/11/2023, 9/11/2023)"
|
||||
"natural_language": "Find today's 30-day VWAP of the 10 year treasury future.",
|
||||
"dsl": "VWAP(\"ZN\", 9/11/2024, 10/11/2024)"
|
||||
},
|
||||
{
|
||||
"natural_language": "Find the 10 hour VWAP of the Natural Gas future from yesterday at 5:00 PM.",
|
||||
"dsl": "VWAP(\"NG\", 9/27/2024 7:00:00, 9/27/2024 17:00:00)"
|
||||
},
|
||||
{
|
||||
"natural_language": "Find the 3 month VWAP of the SP 500 futures on 10/10/2023",
|
||||
"dsl": "VWAP(\"ES\", 7/10/2023, 10/10/2023)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "FuturePriceDiff",
|
||||
"description": "This is a few-shot learning example for an NL to DSL. The DSL function in question is FuturePriceDiff(symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil, or ES for SP 500 and so on and so forth) or a specific future contract like crude oil Dec 24 future (CLZ4). The second and third arguments are datetimes (usually dates) that the function finds the difference of prices at. If the dates are given like \"first day of quarter\", \"first week of August\" I expect you to convert them into exact dates since the function only takes specific dates. The second argument should be the smaller datetime and the 3rd argument should be the end of the range, ie the higher one. Any prompt asking for price difference or price move should call FuturePriceDiff. If the dates you compute to pass to FuturePriceDiff are dates on which futures exchange might be closed (like 1/1, 12/25, Thanksgiving etc) please adjust the date to the nearest viable trading date (I am not providing any function to find this date, you have to find the nearest trading date yourself), for example if the date falls on christmas day, change it to 12/24 and so on and so on and so forth, or the date happens to be a saturday or sunday, change it to Friday or Monday depending on which is closest",
|
||||
"prompt": "What is the difference in SP 500 Future from July 10th to July 14th? (or how much did the SP 500 Future price move from July 10th to July 14th?)",
|
||||
"description": "This is a few-shot learning example for an NL to DSL. The DSL function in question is FuturePriceDiff(symbol_or_contract, datetime1, datetime2). The first argument is a future (like CL, for crude oil, or ES for SP 500 and so on and so forth) or a specific future contract like crude oil Dec 24 future (CLZ4). The second and third arguments are datetimes (usually dates) that the function finds the difference of prices at. If the dates are given like \"first day of quarter\", \"first week of August\" I expect you to convert them into exact dates since the function only takes specific dates. The second argument should be the smaller datetime and the 3rd argument should be the end of the range, ie the higher one. Any prompt asking for price difference or price move should call FuturePriceDiff. If the dates you compute to pass to FuturePriceDiff are dates on which futures exchange might be closed (like 1/1, 12/25, Thanksgiving etc) please adjust the date to the nearest viable trading date, for example if the date falls on christmas day, change it to 12/24 and so on and so on and so forth, or the date happens to be a saturday or sunday, change it to Friday or Monday depending on which is closest",
|
||||
"prompt": "What is the difference in SP 500 Future from July 10th to July 14th?",
|
||||
"examples": [
|
||||
{
|
||||
"natural_language": "What is the difference in ZN price from July 10th to July 14th?",
|
||||
"dsl": "FuturePriceDiff(\"ZN\", 7/10/2024, 7/14/2024)"
|
||||
},
|
||||
{
|
||||
"natural_language": "What is the difference in SP 500 Future price from the first day of the first quarter of 2020 to the last day of the first quarter of 2020?",
|
||||
"dsl": "FuturePriceDiff(\"ES\", 1/1/2020, 3/31/2020)"
|
||||
},
|
||||
{
|
||||
"natural_language": "How much did SP 500 Futures move in the first quarter of 2020?",
|
||||
"dsl": "FuturePriceDiff(\"ES\", 1/1/2020, 3/31/2020)"
|
||||
"natural_language": "What is the price difference in SP Future price from Sept 10th and Sept 5th?",
|
||||
"dsl": "FuturePriceDiff(\"ES\", 9/5/2024, 9/10/2024)"
|
||||
},
|
||||
{
|
||||
"natural_language": "What is the price difference in Natural Gas Dec Future price from Aug 30th and Aug 20th?",
|
||||
@@ -96,19 +84,19 @@
|
||||
"dsl": "FuturePriceDiff(\"ES\", 8/30/2023, 10/10/2023)"
|
||||
},
|
||||
{
|
||||
"natural_language": "How much did SP 500 Future move during the first quarter of last year",
|
||||
"natural_language": "What is the price move in SP 500 Future during teh first quarter of last year",
|
||||
"dsl": "FuturePriceDiff(\"ES\", 1/1/2023, 3/31/2023)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "Contract Aggregate Volume",
|
||||
"category": "Aggregate Volume",
|
||||
"description": "this is a few shot learning example for an NL to DSL. The DSL function in question is VolumeAgg(symbol, contract_type, datetime1, datetime2), which computes the highest volume in the date range, specified by datetime1, and datetime2. The first argument is a future symbol (like CL for crude oil, ES for SP 500 etc). The second argument is the ContractType, which is \"Future\" for futures and \"FutureSpread\" for future spreads. the 3rd argument is the begin date and the 4th argument is the end date, between which the max volume is computed. If no future is specified use \"*\" from the first arugment.",
|
||||
"prompt": "Which SP 500 Future traded the most between 7/10/2024 and 7/14/2024",
|
||||
"prompt": "Which SP 500 Future traded the most between 7/14/2024 and 7/10/2024",
|
||||
"examples": [
|
||||
{
|
||||
"natural_language": "Which SP Future traded the most between 7/10/2024 and 7/14/2024",
|
||||
"dsl": "VolumeAgg(\"ES\", \"Future\", 7/10/2024, 7/14/2024)"
|
||||
"dsl": "VolumeAgg(\"ES\", \"Future\", 1/1/2024, 1/7/2024)"
|
||||
},
|
||||
{
|
||||
"natural_language": "Which Gold Spread traded the most in the first month of this year",
|
||||
@@ -130,8 +118,8 @@
|
||||
"prompt": "When does the roll typically occur In SP 500?",
|
||||
"examples": [
|
||||
{
|
||||
"natural_language": "When does the roll typically occur In SP 500?",
|
||||
"dsl": "FuturesRoll(\"ES\")"
|
||||
"natural_language": "When does the roll typically occur In CL?",
|
||||
"dsl": "FuturesRoll(\"CL\")"
|
||||
},
|
||||
{
|
||||
"natural_language": "When does the roll typically occur for 10 year treasury?",
|
||||
@@ -175,8 +163,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "Open Interest",
|
||||
"description": "This is a few shot learning example for an NL to DSL.",
|
||||
"category": "Average Volume",
|
||||
"description": "This is a few shot learning example for an NL to DSL. For the purpose of this few shot prompts, lets assume today is 10/3/2024 ",
|
||||
"prompt": "When does the most open interest typically shift from one contract to another in SP 500 Future?",
|
||||
"examples": [
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ type Tab =
|
||||
| PositionManagementTab
|
||||
| MarginTab
|
||||
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.TWSE; Venue.SIM; |]
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
|
||||
|
||||
let securityTypes = [| SecurityType.SPOT; SecurityType.FUTURE; SecurityType.OPTION; SecurityType.SPREAD; |]
|
||||
|
||||
@@ -74,11 +74,6 @@ type TaifexExchangeField =
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type TwseExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type OkxExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
|
||||
@@ -800,7 +800,6 @@ let venueToString (venue:bts.api.Venue) =
|
||||
| bts.api.Venue.FTX -> "FTX"
|
||||
| bts.api.Venue.OKX -> "OKX"
|
||||
| bts.api.Venue.TAIFEX -> "TAIFEX"
|
||||
| bts.api.Venue.TWSE -> "TWSE"
|
||||
|
||||
let exchange_display expirationNameList =
|
||||
let s = sprintf "%O" expirationNameList.product.exchange
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
namespace mbts.spark {
|
||||
using bts.api;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -25,14 +23,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() == (Security.is_perpetual_exp(exp.expiration)
|
||||
? ExpirationDate.Perpetual
|
||||
: ExpirationDate.FixedDate(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() == 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().Value);
|
||||
.Add(sec.earliest_expiration());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,8 @@ 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;
|
||||
@@ -82,11 +81,6 @@ 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,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -629,16 +629,16 @@ namespace mbts.spark {
|
||||
|
||||
static (string desc, Color fg) _fills_title(ColKey key) =>
|
||||
key switch {
|
||||
ColKey.Position => ("Day Fills", BTSColors.DayFills.foreground),
|
||||
ColKey.RawDayPosition => ("Raw Day Fills", BTSColors.DayFills.foreground),
|
||||
_ => ("Fills", BTSColors.UI.default_text),
|
||||
ColKey.Position => ("Day Fills", Colors.Black),
|
||||
ColKey.RawDayPosition => ("Raw Day Fills", Colors.Black),
|
||||
_ => ("Fills", Colors.Black),
|
||||
};
|
||||
|
||||
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", BTSColors.UI.default_text),
|
||||
_ => ("Working Orders", Colors.Black),
|
||||
};
|
||||
|
||||
static GridSettings _hide_cols(GridSettings grid, IEnumerable<OrderBookGridView.ColumnKeys> columns) =>
|
||||
|
||||
@@ -626,22 +626,6 @@
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_SpreaderType_Label"
|
||||
VerticalAlignment="Center"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
Text="Type"
|
||||
/>
|
||||
<ComboBox
|
||||
x:Name="PART_SpreaderType_DropDown"
|
||||
Grid.Column="7"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="{StaticResource Margin.LHeaderPanelItem}"
|
||||
ItemsSource="{TemplateBinding SpreaderTypeItemsSource}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding SpreaderType, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
/>
|
||||
<!-- <TextBlock
|
||||
Grid.Column="6"
|
||||
x:Name="PART_HedgePricing_Label"
|
||||
VerticalAlignment="Center"
|
||||
@@ -658,7 +642,7 @@
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding HedgePricing, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="Collapsed"
|
||||
/> -->
|
||||
/>
|
||||
</Grid>
|
||||
<c:DataGridEx
|
||||
x:Name="PART_DataGrid"
|
||||
|
||||
@@ -237,29 +237,6 @@ namespace mbts.ui.spark {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SpreaderTypeItemsSource
|
||||
public static readonly DependencyProperty SpreaderTypeItemsSourceProperty =
|
||||
WPFHelper.create_dp<IEnumerable, ExecutionStrategyEditor>(
|
||||
nameof(SpreaderTypeItemsSource));
|
||||
|
||||
public IEnumerable SpreaderTypeItemsSource {
|
||||
get => GetValue(SpreaderTypeItemsSourceProperty) as IEnumerable;
|
||||
set => SetValue(SpreaderTypeItemsSourceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SpreaderType
|
||||
public static readonly DependencyProperty SpreaderTypeProperty =
|
||||
WPFHelper.create_dp<object, ExecutionStrategyEditor>(
|
||||
nameof(SpreaderType),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public object SpreaderType {
|
||||
get => GetValue(SpreaderTypeProperty);
|
||||
set => SetValue(SpreaderTypeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate() =>
|
||||
_connect_leg_tuples();
|
||||
|
||||
|
||||
@@ -117,14 +117,6 @@ namespace bts.shell {
|
||||
public static QuoteInsideMarketDistanceThrottle QuoteInsideMarketDistanceThrottle => new QuoteInsideMarketDistanceThrottle();
|
||||
#endregion
|
||||
|
||||
public static SniperQuoteParams SniperQuoteParams => new() {
|
||||
NTicks = 0
|
||||
};
|
||||
|
||||
public static ReverseHedgeQuoteParams ReverseHedgeQuoteParams => new() {
|
||||
NTicks = 0
|
||||
};
|
||||
|
||||
#region PreHedge
|
||||
public static PreHedgeParams PreHedgeParams => new PreHedgeParams {
|
||||
MaxPriceFromInsideMarket = PreHedgeMaxPriceFromInsideMarket,
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
ContractPickerVM="{Binding contract_picker_vm}"
|
||||
HedgePricing="{Binding hedge_pricing}"
|
||||
HedgePricingItemsSource="{Binding hedge_pricing_options}"
|
||||
SpreaderType="{Binding spreader_type}"
|
||||
SpreaderTypeItemsSource="{Binding spreader_type_options}"
|
||||
UseFractionalTicks="{Binding use_fractional_ticks}"
|
||||
IntegerTickSize="{Binding integer_tick_size}"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class SyntheticEditorVM {
|
||||
class IsQuoteModel : RowModelBase<bool> {
|
||||
public override string display_name { get; } = "Enable Quoting";
|
||||
public override string help_text { get; } = "This parameter is in the form of a check box, where if the box is checked the leg will be quoted versus not checked, it will not be shown in the market. If it is not actively quoted in the market, the leg will still be used for calculation of the entire synthetic.";
|
||||
public override string man_page { get; } = string.Empty;
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.IsActiveQuoting;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
|
||||
leg_tup.leg_params.IsActiveQuoting = value;
|
||||
|
||||
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
|
||||
!leg_tups.Any(lt => lt.leg_params.IsActiveQuoting);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
class QuoteEnabledModel : RowModelBase<bool> {
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Quote };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.IsActiveQuoting;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) =>
|
||||
leg_tup.leg_params.IsActiveQuoting = value;
|
||||
|
||||
public override bool has_errors(IReadOnlyCollection<LegSpreaderParamsTuple> leg_tups) =>
|
||||
!leg_tups.Any(lt => lt.leg_params.IsActiveQuoting);
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
using bts.shell;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
class ReverseHedgeEnabledModel : RowModelBase<bool> {
|
||||
readonly ReverseHedgeTicksModel _ticks_model;
|
||||
|
||||
public ReverseHedgeEnabledModel(ReverseHedgeTicksModel ticks_model) =>
|
||||
_ticks_model = ticks_model;
|
||||
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.ReverseHedge };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams is not null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.ReverseHedgeQuoteParams is null) {
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams = SyntheticDataHelper.Defaults.ReverseHedgeQuoteParams;
|
||||
_ticks_model.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams = null;
|
||||
}
|
||||
_ticks_model.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
using bts.spark;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
internal class ReverseHedgeTicksModel : RowModelBase<int> {
|
||||
readonly Dictionary<int, int> _value_store = new();
|
||||
|
||||
public override string display_name => "Ticks";
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.ReverseHedge };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup).NTicks = _value_from_store(leg_tup);
|
||||
|
||||
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup)?.NTicks ?? _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
_value_store[leg_tup.id] = value;
|
||||
var s = _get_params(leg_tup);
|
||||
if (s is not null) {
|
||||
s.NTicks = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup) != null;
|
||||
|
||||
ReverseHedgeQuoteParams _get_params(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.ReverseHedgeQuoteParams;
|
||||
|
||||
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.get_value(leg_tup.id, _ => 0);
|
||||
}
|
||||
}
|
||||
@@ -94,9 +94,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
private static class Sections {
|
||||
public static ParameterSection Sniper => new("Sniper", true);
|
||||
public static ParameterSection ReverseHedge => new("Reverse Hedge", true);
|
||||
public static ParameterSection Quote => new("Quote", true);
|
||||
public static ParameterSection Quote => new ParameterSection("Quote");
|
||||
public static ParameterSection PreHedge => new ParameterSection("Pre-Hedge");
|
||||
public static ParameterSection PostHedge => new ParameterSection("Post-Hedge");
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using bts.shell;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
class SniperEnabledModel : RowModelBase<bool> {
|
||||
readonly SniperTicksModel _ticks_model;
|
||||
|
||||
public SniperEnabledModel(SniperTicksModel ticks_model) =>
|
||||
_ticks_model = ticks_model;
|
||||
|
||||
public override string display_name => string.Empty;
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Sniper };
|
||||
|
||||
public override bool get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.SniperQuoteParams is not null;
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, bool value) {
|
||||
if (value) {
|
||||
if (leg_tup.leg_params.SniperQuoteParams is null) {
|
||||
leg_tup.leg_params.SniperQuoteParams = SyntheticDataHelper.Defaults.SniperQuoteParams;
|
||||
_ticks_model.apply_value(leg_tup);
|
||||
}
|
||||
} else {
|
||||
leg_tup.leg_params.SniperQuoteParams = null;
|
||||
}
|
||||
_ticks_model.set_enabled(leg_tup, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using bts.spark;
|
||||
|
||||
using mbts.ui.spark;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace mbts.spark;
|
||||
|
||||
partial class SyntheticEditorVM {
|
||||
internal class SniperTicksModel : RowModelBase<int> {
|
||||
readonly Dictionary<int, int> _value_store = new();
|
||||
|
||||
public override string display_name => "Ticks";
|
||||
public override string help_text => string.Empty;
|
||||
public override string man_page => string.Empty;
|
||||
|
||||
public override ParameterSection[] section_path { get; } = new[] { Sections.Sniper };
|
||||
|
||||
public void apply_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup).NTicks = _value_from_store(leg_tup);
|
||||
|
||||
public override int get_value(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup)?.NTicks ?? _value_from_store(leg_tup);
|
||||
|
||||
public override void set_value(LegSpreaderParamsTuple leg_tup, int value) {
|
||||
_value_store[leg_tup.id] = value;
|
||||
var s = _get_params(leg_tup);
|
||||
if (s is not null) {
|
||||
s.NTicks = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool _get_enabled(LegSpreaderParamsTuple leg_tup) =>
|
||||
_get_params(leg_tup) != null;
|
||||
|
||||
SniperQuoteParams _get_params(LegSpreaderParamsTuple leg_tup) =>
|
||||
leg_tup.leg_params.SniperQuoteParams;
|
||||
|
||||
int _value_from_store(LegSpreaderParamsTuple leg_tup) =>
|
||||
_value_store.get_value(leg_tup.id, _ => 0);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
using HedgePricing = bts.spark.ExecutionStrategy.Types.HedgePricing;
|
||||
using SpreaderType = bts.spark.ExecutionStrategy.Types.SpreaderType;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
@@ -48,7 +47,6 @@ namespace mbts.spark {
|
||||
int _integer_tick_size = 1;
|
||||
ExecutionStrategyImmutable _strategy;
|
||||
HedgePricing _hedge_pricing;
|
||||
SpreaderType _spreader_type;
|
||||
|
||||
/// <summary>
|
||||
/// Inititalizes a new instance of SyntheticEditorVM class. This constructor may only be used for already-existing synthetic securities (id must be non-zero).
|
||||
@@ -200,8 +198,6 @@ namespace mbts.spark {
|
||||
} else {
|
||||
_can_add_leg = backing(nameof(can_add_leg), false);
|
||||
}
|
||||
var sniper_ticks_model = new SniperTicksModel();
|
||||
var reverse_hedge_ticks_model = new ReverseHedgeTicksModel();
|
||||
var show_quantity_quantity = new ShowQuantityQuantityModel();
|
||||
|
||||
var quote_throttle_outside_ticks = new QuoteThrottleOutsideTicksModel();
|
||||
@@ -229,18 +225,15 @@ namespace mbts.spark {
|
||||
|
||||
var post_hedge_opposite_market_based_cross_threshold = new PostHedgeOppositeMarketVolumeBasedCrossThresholdModel();
|
||||
var post_hedge_opposite_market_based_cross_cross_ticks = new PostHedgeOppositeMarketVolumeBasedCrossCrossTicksModel();
|
||||
var is_quote_model = new IsQuoteModel();
|
||||
rows = new IRowModel[] {
|
||||
_quantity_model,
|
||||
_price_mult_model,
|
||||
is_quote_model,
|
||||
//new EnableHedgeModel(), //TAHA_SPREADER
|
||||
new HedgeRatioModel(),
|
||||
new SniperEnabledModel(sniper_ticks_model),
|
||||
sniper_ticks_model,
|
||||
new ReverseHedgeEnabledModel(reverse_hedge_ticks_model),
|
||||
reverse_hedge_ticks_model,
|
||||
//new MinLeanQtyModel(), //TAHA_SPREADER
|
||||
//new QuoteHiddenModel(),
|
||||
new QuoteEnabledModel(),
|
||||
new QuoteQueueHolderModel(),
|
||||
new MarketDataTypeModel(),
|
||||
new ShowQuantityEnabledModel(show_quantity_quantity),
|
||||
@@ -344,13 +337,6 @@ namespace mbts.spark {
|
||||
public HedgePricing hedge_pricing { get => _hedge_pricing; set => Set(ref _hedge_pricing, value); }
|
||||
public IEnumerable<HedgePricing> hedge_pricing_options => EnumHelper.get_values<HedgePricing>();
|
||||
|
||||
// spreader type
|
||||
public SpreaderType spreader_type {
|
||||
get => _spreader_type;
|
||||
set => Set(ref _spreader_type, value);
|
||||
}
|
||||
public IEnumerable<SpreaderType> spreader_type_options => EnumHelper.get_values<SpreaderType>();
|
||||
|
||||
public ExecutionStrategyImmutable get_strategy() {
|
||||
var mutable_strategy = _strategy.CloneToMutable();
|
||||
mutable_strategy.LegParamsMap.Clear();
|
||||
@@ -366,7 +352,6 @@ namespace mbts.spark {
|
||||
_synthetic.Exchange = Venue.Bts;
|
||||
_strategy = strategy;
|
||||
hedge_pricing = strategy.HedgePricing;
|
||||
spreader_type = strategy.SpreaderType;
|
||||
leg_tuples.SetItems(_get_legs(_synthetic, strategy));
|
||||
if (synthetic.HasContractSpec) {
|
||||
/// assign the fields <see cref="_use_fractional_ticks"/> and <see cref="_integer_tick_size"/>,
|
||||
|
||||
@@ -8,7 +8,6 @@ using mbts.ui.controls;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Api.Orders;
|
||||
|
||||
using System;
|
||||
@@ -92,7 +91,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 ExpirationDate? exp_date { get; set; }
|
||||
public DateTime? exp_date { get; set; }
|
||||
}
|
||||
|
||||
static class FillsFilterStatesExtensions {
|
||||
@@ -122,7 +121,7 @@ namespace bts.shell.fills {
|
||||
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
|
||||
|
||||
if (fs.exp_date.HasValue) {
|
||||
pb.add_exp_date(fs.exp_date.Value, f => f.security?.earliest_expiration());
|
||||
pb.add_date_time(fs.exp_date.Value, f => f.security?.earliest_expiration());
|
||||
}
|
||||
return pb.to_predicate();
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ namespace mbts.spark {
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
using SpreaderType = bts.spark.ExecutionStrategy.Types.SpreaderType;
|
||||
|
||||
class LadderVM : DisposableViewModelBase, IWillLetYouKnowWhenIDispose, IKeyListener {
|
||||
static readonly Brush _transparent_status_brush = BTSColors.UI.transparent;
|
||||
|
||||
@@ -105,7 +103,6 @@ namespace mbts.spark {
|
||||
ulong _misc_time = 0;
|
||||
ulong _last_trade_time = 0;
|
||||
ulong _last_bbo_time = 0;
|
||||
SpreaderType _spreader_type = SpreaderType.Requote;
|
||||
IReadOnlyDictionary<uint, OrderRecordTuple> _cache = DictionaryHelper.empty<uint, OrderRecordTuple>();
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata> _favorite_algo_metadata_map =
|
||||
ImmutableDictionary<AlgoKey, LadderFavoriteAlgoMetadata>.Empty;
|
||||
@@ -129,9 +126,6 @@ namespace mbts.spark {
|
||||
size_calculator.display_settings = model.security_md.quantity_display;
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
this.order_properties_vm = order_properties_vm;
|
||||
if (model.security.HasSynthetic) {
|
||||
spreader_type_choices.AddRange(EnumHelper.get_values<SpreaderType>());
|
||||
}
|
||||
_session_high = backing(nameof(session_high), double.NaN);
|
||||
_session_low = backing(nameof(session_low), double.NaN);
|
||||
_day_change = backing(nameof(day_change), double.NaN);
|
||||
@@ -283,7 +277,9 @@ 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);
|
||||
return _order_price_source(model.orders, p => spec.round_to_tick(p, RoundingMode.AbsDown));
|
||||
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));
|
||||
});
|
||||
_day_change.Changed += (o, n) => RaisePropertyChanged(
|
||||
nameof(day_change_font_color), nameof(day_change_background_color));
|
||||
@@ -584,12 +580,6 @@ namespace mbts.spark {
|
||||
public ObservableCollection<FairValueLineVM> fair_value_lines { get; } =
|
||||
new ObservableCollection<FairValueLineVM>();
|
||||
|
||||
public SpreaderType spreader_type {
|
||||
get => _spreader_type;
|
||||
set => Set(ref _spreader_type, value);
|
||||
}
|
||||
public ObservableCollection<SpreaderType> spreader_type_choices { get; } = new();
|
||||
|
||||
public uint orders_column_width {
|
||||
get => _orders_column_width.Value;
|
||||
set => _orders_column_width.set(value);
|
||||
@@ -769,21 +759,15 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
var order = order_properties_vm.create(side);
|
||||
var def = order.CustomDef;
|
||||
if (def is not null) {
|
||||
if (def.Stop is not null) {
|
||||
def.Stop.StopPrice = price;
|
||||
} else if (def.StopLimit is not null) {
|
||||
if (order.CustomDef is not null) {
|
||||
if (order.CustomDef.Stop is not null) {
|
||||
order.CustomDef.Stop.StopPrice = price;
|
||||
} else if (order.CustomDef.StopLimit is not null) {
|
||||
// swap the prices here so that the level clicked
|
||||
// in the ladder is the StopPrice (trigger) and the Price
|
||||
// is what was specificed in the extra parameter box
|
||||
(price, def.StopLimit.StopPrice) =
|
||||
(def.StopLimit.StopPrice, price);
|
||||
}
|
||||
if (security.HasSynthetic) {
|
||||
foreach (var d in OrderDataHelper.enumerate_algo_hierarchy(def).Where(d => d.CustomCase == CustomOrderDefinition.CustomOneofCase.Spreader)) {
|
||||
d.Spreader.ExecutionStrategy.SpreaderType = spreader_type;
|
||||
}
|
||||
(price, order.CustomDef.StopLimit.StopPrice) =
|
||||
(order.CustomDef.StopLimit.StopPrice, price);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,8 +806,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
static IObservable<(ImmutableSortedDictionary<double, ImmutableDictionary<uint, OrderRecordTuple>> price_index, IReadOnlyDictionary<uint, OrderRecordTuple> map)> _order_price_source(
|
||||
IObservable<TableUpdate<OrderRecordTuple, uint>> source, Func<double, double> normalize_price) {
|
||||
var price_comp = MathUtils.error_margin_comparer(1E-9);
|
||||
IObservable<TableUpdate<OrderRecordTuple, uint>> source,
|
||||
IComparer<double> price_comp,
|
||||
Func<double, double> normalize_price
|
||||
) {
|
||||
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 => {
|
||||
@@ -867,18 +853,15 @@ 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,30 +626,10 @@ namespace mbts.spark {
|
||||
|
||||
void _send_parameterized_order(Point point, Side side) {
|
||||
Disposable.dispose(ref _parameterized_order_popup);
|
||||
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");
|
||||
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))
|
||||
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) });
|
||||
@@ -721,10 +701,6 @@ 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)
|
||||
@@ -1448,7 +1424,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 ?? double.NaN);
|
||||
.if_nan(_vm.settlement);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
@@ -1456,14 +1432,11 @@ 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 ?? double.NaN);
|
||||
.if_nan(_vm.settlement);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
@@ -1473,15 +1446,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 ?? double.NaN);
|
||||
.if_nan(_vm.settlement);
|
||||
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.NaN;
|
||||
double _get_session_high_raw() => _vm?.md?.session_high ?? double.NaN;
|
||||
double _get_session_low_raw() => _vm.md.session_low;
|
||||
double _get_session_high_raw() => _vm.md.session_high;
|
||||
|
||||
void _render_mkt_break(DrawingVisual vis, double price, uint color) =>
|
||||
LadderRender.render_mkt_break(vis, _row_for_price(price), _settings.row_height, color);
|
||||
@@ -1502,7 +1475,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() ?? s;
|
||||
_settings = _vm.get_save_state();
|
||||
_pending_scroll_to_origin |= _settings == null;
|
||||
_invalidate_spec();
|
||||
if (_price_list != null) {
|
||||
@@ -1578,17 +1551,15 @@ namespace mbts.spark {
|
||||
_visual_host.AddVisualChild(_mid_mkt_break_vis);
|
||||
_visual_host.AddVisualChild(_ask_mkt_break_vis);
|
||||
_visual_host.AddVisualChild(_bid_mkt_break_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));
|
||||
}
|
||||
_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();
|
||||
|
||||
@@ -956,13 +956,6 @@
|
||||
</Style>
|
||||
</uag:OrderDetailsPopupControl.Resources>
|
||||
</uag:OrderDetailsPopupControl>
|
||||
|
||||
<ComboBox
|
||||
Margin="2"
|
||||
ItemsSource="{Binding spreader_type_choices}"
|
||||
SelectedItem="{Binding spreader_type}"
|
||||
Visibility="{Binding HasItems, Mode=OneWay, RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
|
||||
<!-- Position into Qty Button -->
|
||||
<Button
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -150,11 +150,7 @@ namespace bts.shell.orderbook {
|
||||
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.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());
|
||||
|
||||
@@ -1,317 +1,527 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,6 @@ 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;
|
||||
@@ -53,8 +51,6 @@ 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;
|
||||
@@ -68,8 +64,6 @@ 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;
|
||||
@@ -92,8 +86,6 @@ 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 &&
|
||||
@@ -105,8 +97,6 @@ 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 &&
|
||||
@@ -164,8 +154,6 @@ namespace bts.shell.pnl {
|
||||
new() {
|
||||
// sod
|
||||
sod_pnl = op(a.sod_pnl, b.sod_pnl),
|
||||
sod_realized_pnl = op(a.sod_realized_pnl, b.sod_realized_pnl),
|
||||
sod_unrealized_pnl = op(a.sod_unrealized_pnl, b.sod_unrealized_pnl),
|
||||
sod_long_pos = op(a.sod_long_pos, b.sod_long_pos),
|
||||
sod_short_pos = op(a.sod_short_pos, b.sod_short_pos),
|
||||
sod_delta = op(a.sod_delta, b.sod_delta),
|
||||
@@ -177,8 +165,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl = op(a.day_pnl, b.day_pnl),
|
||||
day_realized_pnl = op(a.day_realized_pnl, b.day_realized_pnl),
|
||||
day_unrealized_pnl = op(a.day_unrealized_pnl, b.day_unrealized_pnl),
|
||||
day_long_pos = op(a.day_long_pos, b.day_long_pos),
|
||||
day_short_pos = op(a.day_short_pos, b.day_short_pos),
|
||||
day_delta = op(a.day_delta, b.day_delta),
|
||||
|
||||
@@ -39,9 +39,6 @@ namespace bts.shell {
|
||||
class PNLWindow : SparkWindow<PNLSaveState>, ISharedWindow {
|
||||
public const string WINDOW_TYPE = "PNL";
|
||||
public const string WINDOW_TITLE = "P&L Summary";
|
||||
// Cpp overrides aren't respected in pnl calc. Hide the "Override Cpp"
|
||||
// menu item until we fix it.
|
||||
const bool CAN_OVERRIDE_CPP = false;
|
||||
|
||||
static int _window_id_counter = 0;
|
||||
|
||||
@@ -140,10 +137,6 @@ 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(
|
||||
@@ -159,7 +152,7 @@ namespace bts.shell {
|
||||
},
|
||||
new SparkCommandCommand(
|
||||
"Override CPP...", Icons.CurrencyExchange, new Command(_override_cpp)) {
|
||||
show_in_context_menu = CAN_OVERRIDE_CPP,
|
||||
show_in_context_menu = true,
|
||||
show_in_toolbar = false
|
||||
},
|
||||
SparkCommands.window_settings_command(new DialogCommand(_mk_settings_win)),
|
||||
@@ -218,16 +211,13 @@ 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) &&
|
||||
(!menu_display.HasValue || menu_display.Value.Equals(defaults.menu_display))) {
|
||||
defaults.grid.columns.SequenceEqual(cols) && defaults.filters.safe_equals(filters)) {
|
||||
// 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
|
||||
@@ -244,7 +234,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);
|
||||
menu_display: _menu_display_vm?.create());
|
||||
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, result.ToArray());
|
||||
settings = set_triggers(settings, actual_triggers.Concat(result).ToArray());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
@@ -35,8 +33,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
// expiration
|
||||
[ProtoMember(53)]
|
||||
ExpirationDate[] _expiration;
|
||||
public ExpirationDate[] expiration {
|
||||
DateTime[] _expiration;
|
||||
public DateTime[] expiration {
|
||||
get => _expiration.empty_if_null();
|
||||
set => _expiration = value;
|
||||
}
|
||||
@@ -67,24 +65,16 @@ 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; }
|
||||
@@ -102,24 +92,16 @@ 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; }
|
||||
@@ -167,24 +149,16 @@ 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; }
|
||||
@@ -225,8 +199,6 @@ 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) &&
|
||||
@@ -238,8 +210,6 @@ 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) &&
|
||||
@@ -262,8 +232,6 @@ 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) &&
|
||||
@@ -302,8 +270,6 @@ 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);
|
||||
@@ -315,8 +281,6 @@ 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);
|
||||
@@ -335,8 +299,6 @@ 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,13 +9,9 @@ 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;
|
||||
|
||||
@@ -114,22 +110,7 @@ namespace bts.shell.pnl {
|
||||
GridSettings _grid_backing;
|
||||
PNLCellAppearanceProto _grid_cell_appearance_backing;
|
||||
|
||||
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 =>
|
||||
public static PNLSettings Default =>
|
||||
new PNLSettings {
|
||||
_sorting_criteria = new[] {
|
||||
new SortingCriteria(nameof(Columns.Account), true),
|
||||
@@ -211,10 +192,6 @@ 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));
|
||||
@@ -225,28 +202,16 @@ namespace bts.shell.pnl {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.DayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.TotalNetPos));
|
||||
// working
|
||||
@@ -259,28 +224,16 @@ namespace bts.shell.pnl {
|
||||
get {
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.SODUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.SODNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.sod_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.SODNetPos));
|
||||
// day
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.DayUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.DayNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.day_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.DayNetPos));
|
||||
// total
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_realized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalRealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} > 0", _animation(foreground: BTSColors.PNL.profit, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_unrealized_pnl)} < 0", _animation(foreground: BTSColors.PNL.loss, column: Columns.TotalUnrealizedPNL));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} > 0", _animation(foreground: BTSColors.MarketData.bid, column: Columns.TotalNetPos));
|
||||
yield return _trigger($"{nameof(PNLNormalizedData.total_net_pos)} < 0", _animation(foreground: BTSColors.MarketData.ask, column: Columns.TotalNetPos));
|
||||
// working
|
||||
@@ -439,14 +392,10 @@ 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(
|
||||
@@ -479,14 +428,10 @@ 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(
|
||||
@@ -526,14 +471,10 @@ 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(
|
||||
@@ -587,40 +528,28 @@ namespace bts.shell.pnl {
|
||||
|
||||
// pnl
|
||||
yield return sod_pnl;
|
||||
yield return sod_realized_pnl;
|
||||
yield return sod_unrealized_pnl;
|
||||
yield return day_pnl;
|
||||
yield return day_realized_pnl;
|
||||
yield return day_unrealized_pnl;
|
||||
yield return total_pnl;
|
||||
yield return total_realized_pnl;
|
||||
yield return total_unrealized_pnl;
|
||||
|
||||
// sod pos, price
|
||||
yield return sod_long_pos; // hidden
|
||||
yield return sod_long_avg_price; // hidden
|
||||
yield return sod_long_avg_pnl; // hidden
|
||||
yield return sod_short_pos; // hidden
|
||||
yield return sod_short_avg_price; // hidden
|
||||
yield return sod_short_avg_pnl; // hidden
|
||||
yield return sod_net_pos;
|
||||
|
||||
// day pos, price
|
||||
yield return day_long_pos; // hidden
|
||||
yield return day_long_avg_price; // hidden
|
||||
yield return day_long_avg_pnl; // hidden
|
||||
yield return day_short_pos; // hidden
|
||||
yield return day_short_avg_price; // hidden
|
||||
yield return day_short_avg_pnl; // hidden
|
||||
yield return day_net_pos;
|
||||
|
||||
// total pos, price
|
||||
yield return total_long_pos; // hidden
|
||||
yield return total_long_avg_price; // hidden
|
||||
yield return total_long_avg_pnl; // hidden
|
||||
yield return total_short_pos; // hidden
|
||||
yield return total_short_avg_price; // hidden
|
||||
yield return total_short_avg_pnl; // hidden
|
||||
yield return total_net_pos;
|
||||
|
||||
// sod greeks
|
||||
|
||||
@@ -34,8 +34,6 @@ namespace bts.shell.pnl {
|
||||
_cell_map.set_appearances(default_appearances);
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_realized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODRealizedPNL, d => d.sod_realized_pnl);
|
||||
sod_unrealized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.SODUnrealizedPNL, d => d.sod_unrealized_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.sod_short_pos);
|
||||
@@ -46,8 +44,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_realized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.DayRealizedPNL, d => d.day_realized_pnl);
|
||||
day_unrealized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.DayUnrealizedPNL, d => d.day_unrealized_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.day_short_pos);
|
||||
@@ -62,8 +58,6 @@ namespace bts.shell.pnl {
|
||||
working_short_pos = _cell_map.add_short_pos(ColumnKeys.WorkingShortPos, r => r.working_short_pos);
|
||||
// total
|
||||
total_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalPNL, d => d.total_pnl);
|
||||
total_realized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalRealizedPNL, d => d.total_realized_pnl);
|
||||
total_unrealized_pnl = _cell_map.add_pnl_decimal(ColumnKeys.TotalUnrealizedPNL, d => d.total_unrealized_pnl);
|
||||
total_net_pos = _cell_map.add_net_pos(ColumnKeys.TotalNetPos, d => d.total_net_pos);
|
||||
total_long_pos = _cell_map.add_long_pos(ColumnKeys.TotalLongPos, d => d.total_long_pos);
|
||||
total_short_pos = _cell_map.add_short_pos(ColumnKeys.TotalShortPos, d => d.total_short_pos);
|
||||
@@ -94,8 +88,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
|
||||
@@ -106,8 +98,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// sod
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
|
||||
@@ -118,8 +108,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
|
||||
|
||||
@@ -6,7 +6,6 @@ using mbts.spark;
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Data;
|
||||
|
||||
@@ -50,7 +49,9 @@ namespace bts.shell.pnl {
|
||||
expiration_cell = _cell_map.add(
|
||||
ColumnKeys.Expiration,
|
||||
d => d.expiration,
|
||||
e => e.ToString());
|
||||
e => e == DateTime.MinValue
|
||||
? "N/A"
|
||||
: SecurityFormatter.format_expiration(e));
|
||||
symbol_cell = _cell_map.add(ColumnKeys.Symbol, d => d.symbol);
|
||||
security_cell = _cell_map.add(
|
||||
ColumnKeys.Security,
|
||||
@@ -62,18 +63,14 @@ namespace bts.shell.pnl {
|
||||
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);
|
||||
@@ -81,18 +78,14 @@ namespace bts.shell.pnl {
|
||||
|
||||
// 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);
|
||||
@@ -113,18 +106,14 @@ namespace bts.shell.pnl {
|
||||
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(
|
||||
@@ -203,8 +192,6 @@ namespace bts.shell.pnl {
|
||||
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,
|
||||
@@ -216,8 +203,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// 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,
|
||||
@@ -245,13 +230,13 @@ namespace bts.shell.pnl {
|
||||
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 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, ExpirationDate> expiration_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; }
|
||||
@@ -259,15 +244,11 @@ namespace bts.shell.pnl {
|
||||
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; }
|
||||
@@ -275,15 +256,11 @@ namespace bts.shell.pnl {
|
||||
|
||||
// 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; }
|
||||
@@ -297,15 +274,11 @@ namespace bts.shell.pnl {
|
||||
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; }
|
||||
|
||||
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -120,7 +119,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, ExpirationDate, ExpirationDate>(
|
||||
expiration_filter = DiscreteValuesFilterVM.CreateTableUpdate<PNLRowData, DateTime, DateTime>(
|
||||
r => r.expiration,
|
||||
exp => exp,
|
||||
out var expiration_filter_items_observer,
|
||||
@@ -227,7 +226,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, ExpirationDate[]> expiration_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<PNLRowData, DateTime[]> 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());
|
||||
@@ -238,14 +237,10 @@ 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);
|
||||
@@ -254,14 +249,10 @@ 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);
|
||||
@@ -276,14 +267,10 @@ 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);
|
||||
@@ -323,8 +310,6 @@ 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(),
|
||||
@@ -336,14 +321,10 @@ 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(),
|
||||
@@ -352,8 +333,6 @@ 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(),
|
||||
@@ -396,8 +375,6 @@ 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);
|
||||
@@ -406,8 +383,6 @@ 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);
|
||||
@@ -416,8 +391,6 @@ 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);
|
||||
@@ -593,14 +566,10 @@ 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);
|
||||
@@ -608,14 +577,10 @@ 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);
|
||||
@@ -624,14 +589,10 @@ 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);
|
||||
@@ -666,13 +627,13 @@ namespace bts.shell.pnl {
|
||||
yield return col_hash.Contains(c.id) ? c.with(is_visible: false) : c;
|
||||
}
|
||||
|
||||
static IObservable<TableUpdate<ExpirationDate, ExpirationDate>> _expirations_source(
|
||||
static IObservable<TableUpdate<DateTime, DateTime>> _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<ExpirationDate>.Default)
|
||||
.Filter(e => e != ExpirationDate.None);
|
||||
PNLRowData.key_fn, r => r.expiration, e => e, EqualityComparer<DateTime>.Default)
|
||||
.Filter(e => e != DateTime.MinValue);
|
||||
|
||||
// combines the known system symbols
|
||||
static IObservable<TableUpdate<string, string>> _symbols_source(IObservable<TableUpdate<PNLRowData, PNLKey>> pnl_source) =>
|
||||
@@ -746,7 +707,7 @@ namespace bts.shell.pnl {
|
||||
filter.security = SecurityCriteria.from_securities(sec));
|
||||
break;
|
||||
case ColumnKeys.Expiration:
|
||||
try_set_filter_prop<ExpirationDate>(exp_date => filter.exp_date = exp_date);
|
||||
try_set_filter_prop<DateTime>(exp_date => filter.exp_date = exp_date);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
@@ -813,17 +774,6 @@ namespace bts.shell.pnl {
|
||||
case ColumnKeys.Account: filter.accounts = new[] { g.Name.ToString() }; break;
|
||||
case ColumnKeys.Symbol: filter.symbols = new[] { g.Name.ToString() }; break;
|
||||
case ColumnKeys.Exchange: filter.exchanges = new[] { (Venue)g.Name }; break;
|
||||
case ColumnKeys.Expiration: {
|
||||
if (g.Name is ExpirationDate exp) {
|
||||
if (filter.security_criteria is null) {
|
||||
filter.security_criteria = SecurityCriteria.from_expirations(exp);
|
||||
} else {
|
||||
filter.security_criteria.clauses = ArrayHelper.add(
|
||||
filter.security_criteria.clauses, SecurityCriteriaClause.from_expirations(exp));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ColumnKeys.Security:
|
||||
if (g.Name is SecurityImmutable sec)
|
||||
filter.security_criteria = SecurityCriteria.from_securities(sec);
|
||||
|
||||
@@ -118,8 +118,6 @@ namespace bts.shell.pnl {
|
||||
});
|
||||
// sod
|
||||
sod_pnl = _cell_map.add_pnl(ColumnKeys.SODPNL, d => d.sod_pnl);
|
||||
sod_realized_pnl = _cell_map.add_pnl(ColumnKeys.SODRealizedPNL, d => d.sod_realized_pnl);
|
||||
sod_unrealized_pnl = _cell_map.add_pnl(ColumnKeys.SODUnrealizedPNL, d => d.sod_unrealized_pnl);
|
||||
sod_net_pos = _cell_map.add_net_pos(ColumnKeys.SODNetPos, d => d.display_sod_net_pos);
|
||||
sod_long_pos = _cell_map.add_long_pos(ColumnKeys.SODLongPos, d => d.display_sod_long_pos);
|
||||
sod_short_pos = _cell_map.add_short_pos(ColumnKeys.SODShortPos, d => d.display_sod_short_pos);
|
||||
@@ -130,8 +128,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
day_pnl = _cell_map.add_pnl(ColumnKeys.DayPNL, d => d.day_pnl);
|
||||
day_realized_pnl = _cell_map.add_pnl(ColumnKeys.DayRealizedPNL, d => d.day_realized_pnl);
|
||||
day_unrealized_pnl = _cell_map.add_pnl(ColumnKeys.DayUnrealizedPNL, d => d.day_unrealized_pnl);
|
||||
day_net_pos = _cell_map.add_net_pos(ColumnKeys.DayNetPos, d => d.display_day_net_pos);
|
||||
day_long_pos = _cell_map.add_long_pos(ColumnKeys.DayLongPos, d => d.display_day_long_pos);
|
||||
day_short_pos = _cell_map.add_short_pos(ColumnKeys.DayShortPos, d => d.display_day_short_pos);
|
||||
@@ -142,8 +138,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
total_pnl = _cell_map.add(ColumnKeys.TotalPNL, d => d.total_pnl, FormattingDelegates.Price<decimal>.Value);
|
||||
total_realized_pnl = _cell_map.add_pnl(ColumnKeys.TotalRealizedPNL, d => d.total_realized_pnl);
|
||||
total_unrealized_pnl = _cell_map.add_pnl(ColumnKeys.TotalUnrealizedPNL, d => d.total_unrealized_pnl);
|
||||
total_net_pos = _cell_map.add(ColumnKeys.TotalNetPos, d => d.display_total_net_pos);
|
||||
total_long_pos = _cell_map.add(ColumnKeys.TotalLongPos, d => d.display_total_long_pos);
|
||||
total_short_pos = _cell_map.add(ColumnKeys.TotalShortPos, d => d.display_total_short_pos);
|
||||
@@ -187,8 +181,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// sod
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> sod_short_pos { get; }
|
||||
@@ -199,8 +191,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// day
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> day_short_pos { get; }
|
||||
@@ -211,8 +201,6 @@ namespace bts.shell.pnl {
|
||||
|
||||
// total
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_realized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_unrealized_pnl { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_net_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_long_pos { get; }
|
||||
public PNLCellVM<PNLNormalizedData, decimal> total_short_pos { get; }
|
||||
|
||||
@@ -266,7 +266,6 @@
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Margin" Value="4,0,0,0"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HeaderRowCellText" TargetType="TextBlock">
|
||||
@@ -571,8 +570,6 @@
|
||||
</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}"/>
|
||||
@@ -583,8 +580,6 @@
|
||||
|
||||
<!--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}"/>
|
||||
@@ -603,8 +598,6 @@
|
||||
<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}"/>
|
||||
@@ -639,8 +632,6 @@
|
||||
<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}"/>
|
||||
@@ -651,8 +642,6 @@
|
||||
|
||||
<!--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}"/>
|
||||
@@ -671,8 +660,6 @@
|
||||
<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}"/>
|
||||
@@ -804,26 +791,6 @@
|
||||
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}}"
|
||||
@@ -852,15 +819,6 @@
|
||||
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}}"
|
||||
@@ -879,15 +837,6 @@
|
||||
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}}"
|
||||
@@ -931,26 +880,6 @@
|
||||
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}}"
|
||||
@@ -981,16 +910,6 @@
|
||||
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}}"
|
||||
@@ -1093,16 +1012,6 @@
|
||||
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}}"
|
||||
@@ -1181,26 +1090,6 @@
|
||||
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}}"
|
||||
@@ -1231,16 +1120,6 @@
|
||||
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}}"
|
||||
@@ -1261,16 +1140,6 @@
|
||||
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}}"
|
||||
@@ -1329,15 +1198,11 @@
|
||||
<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}"
|
||||
@@ -1357,15 +1222,11 @@
|
||||
|
||||
<!--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}"
|
||||
@@ -1396,9 +1257,6 @@
|
||||
<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}"/>
|
||||
@@ -1407,15 +1265,11 @@
|
||||
<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}"
|
||||
@@ -1506,8 +1360,6 @@
|
||||
</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}"/>
|
||||
@@ -1518,8 +1370,6 @@
|
||||
|
||||
<!--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}"/>
|
||||
@@ -1534,8 +1384,6 @@
|
||||
<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,439 +1,427 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,244 +1,243 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,10 +143,10 @@ namespace mbts.spark.utilities {
|
||||
bool is_child = false
|
||||
) {
|
||||
var tifs = EnumHelper.get_values_except(TimeInForce.Undefined);
|
||||
return order_type switch {
|
||||
OrderType.Limit or OrderType.Iceberg => tifs,
|
||||
_ => tifs.Except(TimeInForce.Ioc),
|
||||
};
|
||||
if (order_type != OrderType.Limit) {
|
||||
tifs = tifs.Except(new[] { TimeInForce.Ioc });
|
||||
}
|
||||
return tifs;
|
||||
}
|
||||
|
||||
public IEnumerable<string> dynamic_types() =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace mbts.spark {
|
||||
namespace mbts.spark {
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
@@ -662,7 +662,7 @@ namespace mbts.spark {
|
||||
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,5 +64,9 @@ 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))));
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,59 +0,0 @@
|
||||
#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,10 +77,6 @@
|
||||
<EmbeddedResource Include="Sounds/whistle.wav" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="ShellWindowSettings/**" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="diagnostic" AfterTargets="PreBuildEvent" />
|
||||
|
||||
</Project>
|
||||
@@ -254,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 => BTSCoreColors.spring_green;
|
||||
public static BTSColor profit => Colors.Green;
|
||||
public static BTSColor net_zero => BTSCoreColors.gray;
|
||||
public static BTSColor loss => BTSCoreColors.fire_brick;
|
||||
public static BTSColor loss => BTSCoreColors.venetian_red;
|
||||
public static BTSColor header_row_background => 0xffaaaaaa;
|
||||
// group background colors
|
||||
public static BTSColor group_row_background1 => 0x19141420;
|
||||
@@ -276,8 +276,8 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
public static Color anchor_color => Colors.Gold;
|
||||
public static Color profit_color => profit;
|
||||
public static Color loss_color => loss;
|
||||
public static Color profit_color => Colors.Green;
|
||||
public static Color loss_color => BTSCoreColors.venetian_red;
|
||||
public static Color header_row_background_color => 0xffaaaaaa.argb_color();
|
||||
// group background colors
|
||||
public static Color group_row_background_color1 => group_row_background1;
|
||||
@@ -1040,7 +1040,6 @@ 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,7 +83,6 @@
|
||||
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;
|
||||
@@ -101,7 +100,6 @@
|
||||
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;
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,15 +209,11 @@ 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,8 +2,6 @@
|
||||
|
||||
using mbts.ui;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -136,20 +134,6 @@ 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)));
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
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"
|
||||
Foreground="{u:BTSColor UI.input_text}">
|
||||
d:DesignHeight="100" d:DesignWidth="800" x:Name="_me_price">
|
||||
<UserControl.Resources>
|
||||
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
|
||||
<cv:TickDisplayConverter x:Key="TickDisplayConverter" x:Shared="False"/>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#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,13 +22,6 @@ 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,6 +180,38 @@ 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;
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,20 +37,6 @@ 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) =>
|
||||
@@ -269,7 +255,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 ? $@"{order.State.quantity()} @ {spec.format(order.State.Price)}" : "(unknown-price) @ (unknown-quantity)")}";
|
||||
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? ($@"{spec.format(order.State.Price)} @ {order.State.Quantity}") : "(unknown-price) @ (unknown-quantity)")}";
|
||||
|
||||
public static string short_description(
|
||||
SecurityImmutable security, Side side, double price, double quantity) {
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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,7 +5,6 @@ namespace mbts.spark {
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
@@ -28,15 +27,6 @@ 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,7 +4,6 @@ namespace mbts.spark {
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using Spark.Api.Data;
|
||||
using Spark.Core.Collections;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
@@ -18,12 +17,12 @@ namespace mbts.spark {
|
||||
Venue[]? exchanges = null,
|
||||
SecurityType[]? security_types = null,
|
||||
string[]? symbols = null,
|
||||
ExpirationDate[]? expirations = null,
|
||||
DateTime[]? 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;
|
||||
@@ -33,7 +32,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(
|
||||
@@ -59,20 +58,6 @@ 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; }
|
||||
|
||||
@@ -86,7 +71,7 @@ namespace mbts.spark {
|
||||
public string[]? symbols { get; }
|
||||
|
||||
// Not stored in protobuf (yet)
|
||||
public ExpirationDate[]? expirations { get; }
|
||||
public DateTime[]? expirations { get; }
|
||||
|
||||
[ProtoMember(5)]
|
||||
public uint[]? security_keys { get; }
|
||||
|
||||
@@ -3,8 +3,6 @@ namespace mbts.spark {
|
||||
using bts.api;
|
||||
using bts.collections;
|
||||
|
||||
using Spark.Api.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -182,7 +180,7 @@ namespace mbts.spark {
|
||||
Venue[]? exchanges = null,
|
||||
SecurityType[]? security_types = null,
|
||||
string[]? symbols = null,
|
||||
ExpirationDate[]? expirations = null,
|
||||
DateTime[]? expirations = null,
|
||||
uint[]? security_keys = null,
|
||||
string? pretty_name = null,
|
||||
double[]? strikes = null,
|
||||
|
||||
Loaded 100 of 230 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user