Compare commits
142
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40e44db597 | ||
|
|
4a447e44e1 | ||
|
|
b1f4654b3c | ||
|
|
9d350334c0 | ||
|
|
ac597f505f | ||
|
|
147d9ceb1f | ||
|
|
377804dfb9 | ||
|
|
0d26401961 | ||
|
|
889e226151 | ||
|
|
986d53f4e0 | ||
|
|
d13ba2968a | ||
|
|
1a192c8117 | ||
|
|
5e36d4801c | ||
|
|
2cf6411ca7 | ||
|
|
e99df129d5 | ||
|
|
d07c01bd17 | ||
|
|
d69673c524 | ||
|
|
4ee595cdaf | ||
|
|
6fcaf07df0 | ||
|
|
492d48d886 | ||
|
|
b6a56e768b | ||
|
|
3e2b5976a8 | ||
|
|
edd3f6cbd6 | ||
|
|
2e9347341b | ||
|
|
2061e38349 | ||
|
|
650dd57b6d | ||
|
|
82747f7dc0 | ||
|
|
04b26904a3 | ||
|
|
f123e4c72c | ||
|
|
b887cbebab | ||
|
|
14e975bc7e | ||
|
|
064f557057 | ||
|
|
4d106b5e6c | ||
|
|
9fbeaf0d68 | ||
|
|
4018ac8b78 | ||
|
|
81c9503300 | ||
|
|
477210926d | ||
|
|
0aa48a79a9 | ||
|
|
9e71c7af02 | ||
|
|
4647751409 | ||
|
|
a3087b874b | ||
|
|
5b2f5c5c8f | ||
|
|
479227090e | ||
|
|
2dfcf320f5 | ||
|
|
b4c7213cb2 | ||
|
|
c7dfd69b90 | ||
|
|
4fdc866485 | ||
|
|
f5d5ca9a1c | ||
|
|
cfafcf77b9 | ||
|
|
8ebe783139 | ||
|
|
1851a96a58 | ||
|
|
0b334ef870 | ||
|
|
ee811a729a | ||
|
|
cb15be276d | ||
|
|
7d5af6729b | ||
|
|
e5679cf082 | ||
|
|
98f89b9ad4 | ||
|
|
ba4f8f67e3 | ||
|
|
9c78380490 | ||
|
|
f6166f0674 | ||
|
|
6334a902e7 | ||
|
|
082ca62fff | ||
|
|
9dbd8dd7c6 | ||
|
|
8f7a4d0df6 | ||
|
|
f372dd56b3 | ||
|
|
86eba20d61 | ||
|
|
35a985280d | ||
|
|
f5e1b3319c | ||
|
|
65c3948e4f | ||
|
|
002bdff9a3 | ||
|
|
bb6e5dd548 | ||
|
|
7c1aed3339 | ||
|
|
22ae51fb99 | ||
|
|
a53d347f81 | ||
|
|
838dbcd886 | ||
|
|
5ed2309d22 | ||
|
|
4d41704dd2 | ||
|
|
bd1000e518 | ||
|
|
dd4aa3c2ed | ||
|
|
fee9d3f597 | ||
|
|
416f9aef31 | ||
|
|
48f5e4c201 | ||
|
|
4438b4ca8d | ||
|
|
73356e0d61 | ||
|
|
4a285fa581 | ||
|
|
cfa82c2177 | ||
|
|
0410a0431a | ||
|
|
02843f14ff | ||
|
|
f1f1d4259c | ||
|
|
71fc43af68 | ||
|
|
350144ffd5 | ||
|
|
da227a5550 | ||
|
|
0c92cf3a87 | ||
|
|
1188af391e | ||
|
|
0bb85cf915 | ||
|
|
d77f5996fe | ||
|
|
940c41a274 | ||
|
|
e671960dc3 | ||
|
|
8fa34b6b39 | ||
|
|
f9d5429b5b | ||
|
|
2c342482eb | ||
|
|
3aefac26c2 | ||
|
|
1554594cdd | ||
|
|
727a0f38b5 | ||
|
|
7a0e8cdb26 | ||
|
|
a4e97b04d7 | ||
|
|
463980a6ef | ||
|
|
9b25857042 | ||
|
|
ef19e7d76b | ||
|
|
471faec2cb | ||
|
|
ce96d42db8 | ||
|
|
187894ca84 | ||
|
|
63492c4575 | ||
|
|
e150c611f6 | ||
|
|
bb837d6c26 | ||
|
|
965e541ac8 | ||
|
|
1b232aa610 | ||
|
|
25439b094a | ||
|
|
f927ef2c5e | ||
|
|
d42389b75a | ||
|
|
671cbcf03a | ||
|
|
e589e23f41 | ||
|
|
11fadc0a3e | ||
|
|
b8c2d93866 | ||
|
|
67a23dd196 | ||
|
|
ff22c4eb5f | ||
|
|
5addff4c50 | ||
|
|
142e1bd2da | ||
|
|
d05d91bbe7 | ||
|
|
3a90ca9cb5 | ||
|
|
85a46727be | ||
|
|
624fd2c976 | ||
|
|
16d599d2d6 | ||
|
|
c00e4fcb6b | ||
|
|
03e19afe1d | ||
|
|
058e11eadc | ||
|
|
6ff52e7107 | ||
|
|
ee4c28ed41 | ||
|
|
50d26f0060 | ||
|
|
36378e6074 | ||
|
|
420b46741e | ||
|
|
e8f65f95bd |
No files matched your search
@@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"fable": {
|
||||
"version": "4.0.6",
|
||||
"version": "4.24.0",
|
||||
"commands": [
|
||||
"fable"
|
||||
]
|
||||
@@ -27,4 +27,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+182
-11
@@ -31,7 +31,39 @@
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": ["run"],
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
},
|
||||
{
|
||||
"name": "spreader tests2",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader2]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
},
|
||||
{
|
||||
"name": "spreader tests",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "/home/bts/spark/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"[spreader]"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "/home/bts/spark/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
@@ -91,6 +123,18 @@
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark (Skip Build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"program": "Spark.exe",
|
||||
"cwd": "build/Debug/bin/net7.0-windows",
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Spark (Parameterized)",
|
||||
"type": "coreclr",
|
||||
@@ -109,21 +153,138 @@
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Sparkd",
|
||||
"name": "Spark Test",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Sparkd (Debug | Clang)",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "sparkd",
|
||||
"windows": {
|
||||
"preLaunchTask": "Build Spark (Debug)",
|
||||
"program": "Spark.Console.exe",
|
||||
"cwd": "build/Debug/bin/net7.0",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
]
|
||||
},
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"cwd": "build/sparkd-debug-clang/server",
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Spark Test (Skip Build)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"windows": {
|
||||
"program": "Spark.Console.exe",
|
||||
"cwd": "build/Debug/bin/net7.0",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
]
|
||||
},
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Sparkd",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"preLaunchTask": "Build Sparkd (Debug)",
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sparkd (Skip Build)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sparkd Test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"preLaunchTask": "Build Sparkd (Debug)",
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Sparkd Test (Skip Build)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "${workspaceFolder}/build/sparkd-debug-clang/server/sparkd",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkdTest}",
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/sparkd-debug-clang/server",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
@@ -154,5 +315,15 @@
|
||||
"description": "Config Server URL",
|
||||
"default": "http://localhost:8000/config",
|
||||
},
|
||||
{
|
||||
"id": "sparkTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+41
-1
@@ -641,6 +641,21 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Spark (Skip Build)",
|
||||
"type": "process",
|
||||
"command": "",
|
||||
"windows": {
|
||||
"command": "Spark.exe",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/${input:buildConfiguration}/bin/net7.0-windows"
|
||||
},
|
||||
},
|
||||
"icon": {
|
||||
"id": "run",
|
||||
"color": "terminal.ansiGreen",
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run Spark Config Query",
|
||||
"dependsOn": "Build Spark",
|
||||
@@ -705,6 +720,26 @@
|
||||
"color": "terminal.ansiGreen",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "Test Spark (Debug)",
|
||||
"dependsOn": "Build Spark (Debug)",
|
||||
"type": "process",
|
||||
"icon": {
|
||||
"id": "coverage",
|
||||
"color": "terminal.ansiYellow",
|
||||
},
|
||||
"command": "",
|
||||
"windows": {
|
||||
"command": "Spark.Console.exe",
|
||||
"args": [
|
||||
"test",
|
||||
"${input:sparkTest}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/build/Debug/bin/net7.0",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "View DataStreamQueryAnalyzer Logs (Last Run)",
|
||||
"type": "process",
|
||||
@@ -823,6 +858,11 @@
|
||||
"id": "sparkdConfigQuery",
|
||||
"type": "promptString",
|
||||
"description": "Config Type(s)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sparkTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
+16
-5
@@ -1,5 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
project(spark)
|
||||
include(GNUInstallDirs)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
if(WIN32)
|
||||
@@ -87,9 +88,15 @@ else()
|
||||
add_compile_definitions(BINANCE_US_PRICE_STATS_PATH="/api/v3/ticker/24hr")
|
||||
|
||||
# ** Binance USDS Futures **
|
||||
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://dapi.binance.com")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
|
||||
add_compile_definitions(CCAPI_BINANCE_USDS_FUTURES_URL_REST_BASE="https://fapi.binance.com")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_INSTRUMENTS_PATH="/fapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_PRICE_STATS_PATH="/fapi/v1/ticker/24hr")
|
||||
add_compile_definitions(BINANCE_USDS_FUTURES_USER_DATA_PATH="/api/v3/userDataStream")
|
||||
|
||||
# ** Binance Coin Futures **
|
||||
add_compile_definitions(CCAPI_BINANCE_COIN_FUTURES_URL_REST_BASE="https://dapi.binance.com")
|
||||
add_compile_definitions(BINANCE_COIN_FUTURES_INSTRUMENTS_PATH="/dapi/v1/exchangeInfo")
|
||||
add_compile_definitions(BINANCE_COIN_FUTURES_PRICE_STATS_PATH="/dapi/v1/ticker/24hr")
|
||||
|
||||
# ** Deribit **
|
||||
add_compile_definitions(DERIBIT_INSTRUMENT_PATH="/api/v2/public/get_instrument")
|
||||
@@ -109,6 +116,7 @@ else()
|
||||
|
||||
# subdirs
|
||||
if (BUILD_SPARKD)
|
||||
add_subdirectory(src/lib_binance_execution)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/data_files)
|
||||
endif()
|
||||
@@ -145,11 +153,14 @@ else()
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_BINARY_DIR}/server/plugins"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(
|
||||
FILES
|
||||
"${SPARK_LIB}/libACE-5.7.5.so"
|
||||
"${SPARK_LIB}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0"
|
||||
DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
"${SPARK_LIB}/libonixs-fix-engine.so.4.10.1"
|
||||
"${SPARK_LIB}/libonixs-fix-engine.so.4"
|
||||
"${SPARK_LIB}/libonixs-fix-engine.so"
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
endif()
|
||||
endif()
|
||||
+3
-11
@@ -26,6 +26,7 @@
|
||||
"CMAKE_CXX_FLAGS_DEBUG": "$env{SPARK_CMAKE_CXX_FLAGS} /MTd",
|
||||
"CMAKE_CXX_FLAGS_RELEASE": "$env{SPARK_CMAKE_CXX_FLAGS} /MT",
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "/DEBUG",
|
||||
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "/OPT:REF /OPT:ICF /LTCG",
|
||||
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||
@@ -61,7 +62,7 @@
|
||||
},
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"CMAKE_INSTALL_RPATH": ".",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}/sparkd",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-Wno-undef",
|
||||
"EXTERNALS_ROOT": "${sourceDir}/externals/linux",
|
||||
"BUILD_SDK": {
|
||||
@@ -302,16 +303,7 @@
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"hidden": true,
|
||||
"targets": [
|
||||
"data_files_dir",
|
||||
"admin_web_app_bundle",
|
||||
"install",
|
||||
"libspark",
|
||||
"rapidjson_project",
|
||||
"server_layout",
|
||||
"sparkd"
|
||||
]
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
include_guard(DIRECTORY)
|
||||
|
||||
function(protobuf_generate_fs)
|
||||
set(_options)
|
||||
set(_one_value_args TARGET_NAME OUT_DIR PROTOC)
|
||||
set(_multi_value_args PROTO_INCLUDE_DIRS PROTO_SOURCES)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
|
||||
if (NOT DEFINED _ARGS_TARGET_NAME)
|
||||
set(_ARGS_TARGET_NAME "${PROJECT_NAME}_generate_fs_protos")
|
||||
endif()
|
||||
if (NOT DEFINED _ARGS_PROTOC)
|
||||
set(_ARGS_PROTOC "${SPARK_BIN}/protoc")
|
||||
endif()
|
||||
|
||||
set(_proto_include_cli_args)
|
||||
foreach(_dir ${_ARGS_PROTO_INCLUDE_DIRS})
|
||||
list(APPEND _proto_include_cli_args -I ${_dir})
|
||||
endforeach()
|
||||
|
||||
set(_outputs)
|
||||
foreach(_proto_source ${_ARGS_PROTO_SOURCES})
|
||||
# get_filename_component(_abs_proto_source ${_proto_source} ABSOLUTE)
|
||||
get_filename_component(_proto_source_we ${_proto_source} NAME_WE)
|
||||
# get_filename_component(_proto_source_dir ${_proto_source} DIRECTORY)
|
||||
list(APPEND _outputs ${_ARGS_OUT_DIR}/${_proto_source_we}.fable.fs)
|
||||
endforeach()
|
||||
|
||||
set(_venv_exec ${CMAKE_SOURCE_DIR}/tools/venv_exec.sh)
|
||||
set(_plugin_py ${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/plugin.py)
|
||||
set(_template_fs ${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/templates/fs/fable.fs)
|
||||
add_custom_command(
|
||||
OUTPUT ${_outputs}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND ${_venv_exec}
|
||||
${_ARGS_PROTOC}
|
||||
--plugin=protoc-gen-service=${_plugin_py}
|
||||
--service_out=lang=fs,strip_out_path=1:${_ARGS_OUT_DIR}
|
||||
${_proto_include_cli_args}
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
DEPENDS
|
||||
${_venv_exec}
|
||||
${_plugin_py}
|
||||
${_template_fs}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
)
|
||||
add_custom_target(${_ARGS_TARGET_NAME} ALL DEPENDS "${_outputs}")
|
||||
endfunction()
|
||||
@@ -0,0 +1,33 @@
|
||||
include_guard(DIRECTORY)
|
||||
|
||||
function(protobuf_generate_js)
|
||||
set(_options)
|
||||
set(_one_value_args TARGET_NAME OUTPUT)
|
||||
set(_multi_value_args PROTO_INCLUDE_DIRS PROTO_SOURCES)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
|
||||
if (NOT DEFINED _ARGS_TARGET_NAME)
|
||||
set(_ARGS_TARGET_NAME "${PROJECT_NAME}_generate_js_protos")
|
||||
endif()
|
||||
|
||||
set(_proto_include_cli_args)
|
||||
foreach(_dir ${_ARGS_PROTO_INCLUDE_DIRS})
|
||||
list(APPEND _proto_include_cli_args -p ${_dir})
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_ARGS_OUTPUT}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND npx
|
||||
--no-install pbjs
|
||||
-t json-module
|
||||
-w commonjs
|
||||
-o ${_ARGS_OUTPUT}
|
||||
${_proto_include_cli_args}
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
${_ARGS_PROTO_SOURCES}
|
||||
)
|
||||
add_custom_target(${_ARGS_TARGET_NAME} ALL DEPENDS ${_ARGS_OUTPUT})
|
||||
endfunction()
|
||||
@@ -0,0 +1,49 @@
|
||||
include_guard(DIRECTORY)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/TernaryOperator.cmake)
|
||||
|
||||
function(sym_link_dir)
|
||||
set(_options)
|
||||
set(_one_value_args TARGET_NAME SOURCE DEST COMMENT)
|
||||
set(_multi_value_args)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
add_custom_command(
|
||||
OUTPUT ${_ARGS_DEST}
|
||||
COMMAND ln ARGS -sf ${_ARGS_SOURCE} ${_ARGS_DEST}
|
||||
)
|
||||
set_ternary(_comment _ARGS_COMMENT "${_ARGS_COMMENT}" "linking dir ${_ARGS_SOURCE} -> ${_ARGS_DEST}")
|
||||
add_custom_target(
|
||||
${_ARGS_TARGET_NAME} ALL
|
||||
DEPENDS ${_ARGS_DEST}
|
||||
COMMENT "${_comment}"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(sym_link_file)
|
||||
set(_options)
|
||||
set(_one_value_args SOURCE DEST INSTALL_DEST)
|
||||
set(_multi_value_args)
|
||||
cmake_parse_arguments(_ARGS "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
|
||||
file(
|
||||
CREATE_LINK
|
||||
${_ARGS_SOURCE}
|
||||
${_ARGS_DEST}
|
||||
SYMBOLIC
|
||||
)
|
||||
if (DEFINED _ARGS_INSTALL_DEST)
|
||||
install(
|
||||
FILES
|
||||
${_ARGS_SOURCE}
|
||||
DESTINATION ${_ARGS_INSTALL_DEST}
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(sym_link_local_lib LIB_NAME)
|
||||
set(_lib_file_name "${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_NAME}.so")
|
||||
file(
|
||||
CREATE_LINK
|
||||
"${CMAKE_BINARY_DIR}/src/lib_${LIB_NAME}/${_lib_file_name}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${_lib_file_name}"
|
||||
SYMBOLIC
|
||||
)
|
||||
endfunction()
|
||||
@@ -0,0 +1,9 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
macro(set_ternary RESULT CONDITION TRUE_VALUE FALSE_VALUE)
|
||||
if(${CONDITION})
|
||||
set(${RESULT} ${TRUE_VALUE})
|
||||
else()
|
||||
set(${RESULT} ${FALSE_VALUE})
|
||||
endif()
|
||||
endmacro()
|
||||
+1
-1
Submodule externals updated: ff5f02bbde...8069cc893f.
@@ -4,7 +4,7 @@ namespace bts.SilverPine {
|
||||
using BCG;
|
||||
|
||||
using System;
|
||||
|
||||
[DataStreamUserRespresentation("DateTimeExpanderStream")]
|
||||
public class DateTimeExpanstionStream : ViewStream {
|
||||
public DateTimeExpanstionStream(ViewStream parent_stream, string inp_col) {
|
||||
Func<IBlueTableSchema, IBlueTableSchema> get_schema = (s) => {
|
||||
@@ -24,11 +24,11 @@ namespace bts.SilverPine {
|
||||
IBlueTableSchema s = get_schema(r.schema);
|
||||
MutableBlueRow rv = new MutableBlueRow(s, r);
|
||||
rv.setValue($"{inp_col}_date", dt.Date);
|
||||
rv.setValue($"{inp_col}_day", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day_of_week", dt.DayOfWeek.ToString());
|
||||
rv.setValue($"{inp_col}_day_of_month", dt.Day);
|
||||
rv.setValue($"{inp_col}_month", dt.Date.Month);
|
||||
rv.setValue($"{inp_col}_hour", dt.Hour);
|
||||
rv.setValue($"{inp_col}_min", dt.Minute);
|
||||
rv.setValue($"{inp_col}_minute", dt.Minute);
|
||||
rv.setValue($"{inp_col}_seconds_of_day", (dt - dt.Date).TotalSeconds);
|
||||
return rv;
|
||||
});
|
||||
|
||||
@@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnExpressions", "Bt
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bts.ColumnSortExpressions", "Bts.ColumnSortExpressions\Bts.ColumnSortExpressions.csproj", "{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataStreamQueryAnalyzer", "DataStreamQueryAnalyzer\DataStreamQueryAnalyzer.csproj", "{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -113,5 +115,9 @@ Global
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B038CB72-4E81-4AF1-976F-ED8655C2C3D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFA5BA5C-927A-41A9-A696-1E2F4B5FFD15}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -156,6 +156,14 @@ namespace bts.SilverPine {
|
||||
ViewStream rs = new FutureHistoricalBarsStream(fut_contract, bar_size.ToString(), dt_range_beg, dt_range_end);
|
||||
mySetRealSource(rs);
|
||||
}
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size)
|
||||
: this(fut_contract, bar_size, DateTime.MinValue, DateTime.MaxValue) { }
|
||||
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
|
||||
string dt_range_beg, string dt_range_end)
|
||||
: this(fut_contract, bar_size, DateTime.Parse(dt_range_beg),
|
||||
DateTime.Parse(dt_range_end)) { }
|
||||
|
||||
public FutureHistoricalBarsStream(string fut_contract, string bar_size,
|
||||
DateTime dt_range_beg, DateTime dt_range_end)
|
||||
:
|
||||
|
||||
@@ -1,56 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(Spark.Admin.WebApp)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/safe_dep_glob.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/Protobuf.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/SymLink.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/ProtobufGenerateJs.cmake)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/ProtobufGenerateFs.cmake)
|
||||
|
||||
# dependencies
|
||||
set(PROTO_GLOBS
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_PROTO_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
../lib/api/*.proto
|
||||
../lib/api/services/*.proto
|
||||
../lib/domain_model/*.proto
|
||||
../lib/sparkproto/*.proto
|
||||
../lib/md/protos/*.proto
|
||||
../../externals/linux/include/google/protobuf/descriptor.proto
|
||||
../../externals/linux/include/google/protobuf/timestamp.proto)
|
||||
../../externals/linux/include/google/protobuf/timestamp.proto
|
||||
)
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _proto_rel_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${PROTO_GLOBS})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _proto_abs_SOURCES
|
||||
${PROTO_GLOBS})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _ts_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.ts)
|
||||
|
||||
file(
|
||||
GLOB_RECURSE _fs_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.fs)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
COMMAND npm install --include=dev
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/package.json
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(_tools ${CMAKE_SOURCE_DIR}/tools)
|
||||
set(_quiet_exec ${_tools}/quiet_exec.sh)
|
||||
set(_venv_exec ${_tools}/venv_exec.sh)
|
||||
set(_tools_py ${CMAKE_SOURCE_DIR}/tools/py)
|
||||
|
||||
# The tsconfig file is generated as a function of the cmake build type, and
|
||||
# binary dir. Depending on the build type, it extends either
|
||||
# 'tsconfig.debug.json' or 'tsconfig.release.json'.
|
||||
set(_tsconfig_base "${CMAKE_CURRENT_SOURCE_DIR}/tsconfig.$<IF:$<CONFIG:Debug>,debug,release>.json")
|
||||
set(WEBPACK_MODE $<IF:$<CONFIG:Debug>,"development","production">)
|
||||
set(_tsconfig "${CMAKE_CURRENT_BINARY_DIR}/tsconfig.json")
|
||||
|
||||
set(_venv_python ${CMAKE_SOURCE_DIR}/.venv/bin/python)
|
||||
set(_gen_tsconfig_py ${_tools_py}/gen_tsconfig.py)
|
||||
set(_build_admin_web_app_py ${_tools_py}/build_admin_web_app.py)
|
||||
|
||||
set(_gen_tsconfig_deps ${_venv_exec} ${_venv_python} ${_gen_tsconfig_py} ${_tsconfig_base})
|
||||
|
||||
set(_build_admin_web_app_deps
|
||||
${_quiet_exec}
|
||||
${_venv_exec}
|
||||
@@ -60,49 +46,142 @@ set(_build_admin_web_app_deps
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/webpack.config.js
|
||||
${CMAKE_SOURCE_DIR}/src/lib/api/service_compiler/templates/fs/fable.fs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
${_proto_rel_SOURCES}
|
||||
${${PROJECT_NAME}_PROTO_SOURCES}
|
||||
${_api_ts_SOURCES}
|
||||
${_ui_fs_SOURCES}
|
||||
${_tsconfig})
|
||||
|
||||
update_deps_file("${_build_admin_web_app_deps}")
|
||||
|
||||
function(npm_install path)
|
||||
add_custom_command(
|
||||
OUTPUT ${path}/package-lock.json
|
||||
COMMAND "npm" ARGS "install" "--include=dev"
|
||||
DEPENDS ${path}/package.json
|
||||
WORKING_DIRECTORY ${path}
|
||||
COMMENT "Run 'npm install' in ${path}")
|
||||
endfunction()
|
||||
|
||||
npm_install(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_tsconfig}
|
||||
COMMAND ${_venv_exec}
|
||||
ARGS
|
||||
${_venv_python}
|
||||
${_gen_tsconfig_py}
|
||||
--config-base ${_tsconfig_base}
|
||||
--config-out ${_tsconfig}
|
||||
--build-out ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDS ${_gen_tsconfig_deps})
|
||||
${_venv_python}
|
||||
${_gen_tsconfig_py}
|
||||
--config-base ${_tsconfig_base}
|
||||
--config-out ${_tsconfig}
|
||||
--build-out ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDS
|
||||
${_venv_exec}
|
||||
${_venv_python}
|
||||
${_gen_tsconfig_py}
|
||||
${_tsconfig_base}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json
|
||||
)
|
||||
|
||||
protobuf_generate_js(
|
||||
TARGET_NAME generate_js_protos
|
||||
PROTO_INCLUDE_DIRS
|
||||
..
|
||||
../../externals/linux/include
|
||||
PROTO_SOURCES ${${PROJECT_NAME}_PROTO_SOURCES}
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/proto.js
|
||||
)
|
||||
|
||||
protobuf_generate_fs(
|
||||
TARGET_NAME generate_fs_protos
|
||||
PROTO_INCLUDE_DIRS
|
||||
..
|
||||
../../externals/linux/include
|
||||
PROTO_SOURCES ${${PROJECT_NAME}_PROTO_SOURCES}
|
||||
OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/fable
|
||||
)
|
||||
|
||||
# Each input file maps to multiple output files with different extensions
|
||||
# (js, js.map, d.ts, fs). Transpile ts -> d.ts (and js), then d.ts -> fs.
|
||||
# Who knew front-end development could be so complicated? `\_(=_=)_/`
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_TS_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.ts
|
||||
)
|
||||
set(${PROJECT_NAME}_JS_PROTOS)
|
||||
set(${PROJECT_NAME}_TS_PROTOS)
|
||||
set(${PROJECT_NAME}_FS_PROTOS)
|
||||
foreach(_ts_source ${${PROJECT_NAME}_TS_SOURCES})
|
||||
get_filename_component(_ts_source_we ${_ts_source} NAME_WE)
|
||||
set(_prefix ${CMAKE_CURRENT_BINARY_DIR}/${_ts_source_we})
|
||||
list(APPEND ${PROJECT_NAME}_JS_PROTOS ${_prefix}.js ${_prefix}.js.map)
|
||||
list(APPEND ${PROJECT_NAME}_TS_PROTOS ${_prefix}.d.ts ${_prefix}.d.ts.map)
|
||||
list(APPEND ${PROJECT_NAME}_FS_PROTOS ${_prefix}.fs)
|
||||
add_custom_command(
|
||||
OUTPUT ${_prefix}.fs
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND npx --no-install ts2fable ${_prefix}.d.ts ${_prefix}.fs
|
||||
DEPENDS ${_prefix}.d.ts
|
||||
)
|
||||
endforeach()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bundle.js
|
||||
COMMAND ${_quiet_exec}
|
||||
ARGS
|
||||
${_venv_exec}
|
||||
${_venv_python}
|
||||
${_build_admin_web_app_py}
|
||||
--protos ${_proto_abs_SOURCES}
|
||||
--tsconfig ${_tsconfig}
|
||||
--mode ${WEBPACK_MODE}
|
||||
--output-dir ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Building Spark.Admin.WebApp"
|
||||
DEPENDS ${_build_admin_web_app_deps})
|
||||
OUTPUT
|
||||
${${PROJECT_NAME}_JS_PROTOS}
|
||||
${${PROJECT_NAME}_TS_PROTOS}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMAND npx
|
||||
--no-install tsc
|
||||
-p ${_tsconfig}
|
||||
DEPENDS
|
||||
${_tsconfig}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/proto.js
|
||||
)
|
||||
add_custom_target(
|
||||
transpile_type_script
|
||||
ALL
|
||||
DEPENDS
|
||||
${${PROJECT_NAME}_JS_PROTOS}
|
||||
${${PROJECT_NAME}_TS_PROTOS}
|
||||
)
|
||||
add_custom_target(generate_fsts_stubs ALL DEPENDS ${${PROJECT_NAME}_FS_PROTOS})
|
||||
|
||||
add_custom_target(admin_web_app_bundle ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bundle.js)
|
||||
sym_link_dir(
|
||||
TARGET_NAME link_webapp_public
|
||||
SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/public
|
||||
DEST ${CMAKE_CURRENT_BINARY_DIR}/public
|
||||
)
|
||||
sym_link_dir(
|
||||
TARGET_NAME link_webapp_root
|
||||
SOURCE ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEST ${CMAKE_SOURCE_DIR}/build/Spark.Admin.WebApp
|
||||
)
|
||||
|
||||
file(
|
||||
GLOB_RECURSE ${PROJECT_NAME}_FS_SOURCES
|
||||
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
src/*.fs
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/bundle.js
|
||||
$<$<CONFIG:DEBUG>:${CMAKE_CURRENT_BINARY_DIR}/bundle.js.map>
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
COMMAND dotnet restore
|
||||
COMMAND dotnet tool restore
|
||||
COMMAND export SPARK_ADMIN_WEB_APP_OUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND dotnet fable -o ${CMAKE_CURRENT_BINARY_DIR} --verbose
|
||||
COMMAND npm run build -- --mode $<IF:$<CONFIG:Debug>,"development","production">
|
||||
DEPENDS
|
||||
${${PROJECT_NAME}_FS_SOURCES}
|
||||
${${PROJECT_NAME}_FS_PROTOS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Spark.Admin.WebApp.fsproj
|
||||
)
|
||||
add_custom_target(
|
||||
bundle_spark_admin_webapp ALL
|
||||
DEPENDS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/bundle.js
|
||||
$<$<CONFIG:DEBUG>:${CMAKE_CURRENT_BINARY_DIR}/bundle.js.map>
|
||||
COMMENT "Bundling Spark.Admin.WebApp"
|
||||
)
|
||||
|
||||
# Exclude 'public' dir because it is a symlink
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
PATTERN "public" EXCLUDE
|
||||
)
|
||||
# Install actual (non-symlinked) 'public' dir
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/public"
|
||||
DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}"
|
||||
)
|
||||
|
||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" DESTINATION "${SPARK_INSTALL_PREFIX}/bin")
|
||||
Generated
+2755
-15230
File diff suppressed because it is too large.
Load diff
@@ -42,7 +42,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"tsutils": "^3.21.0",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.11.1",
|
||||
"ws": "^8.12.1"
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type Tab =
|
||||
| PositionManagementTab
|
||||
| MarginTab
|
||||
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
|
||||
let venues = [| Venue.BINANCE; Venue.BinanceUs; Venue.BinanceUsdsFutures; Venue.CBOE; Venue.COINBASE; Venue.DERIBIT; Venue.OKX; Venue.GLBX; Venue.ICE; Venue.TAIFEX; Venue.SIM; |]
|
||||
|
||||
let securityTypes = [| SecurityType.SPOT; SecurityType.FUTURE; SecurityType.OPTION; SecurityType.SPREAD; |]
|
||||
|
||||
@@ -74,6 +74,11 @@ type TaifexExchangeField =
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type OkxExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
| ClearingAccount of string
|
||||
|
||||
type SIMExchangeField =
|
||||
| CTI of CTICode
|
||||
| Origin of CustomerOrFirm
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace mbts.spark {
|
||||
public enum MarketViewColumnType {
|
||||
None,
|
||||
Venue,
|
||||
Security,
|
||||
Volume,
|
||||
Last,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace bts.shell.market_view {
|
||||
using bts.api;
|
||||
|
||||
using mbts.spark;
|
||||
using mbts.spark.filters;
|
||||
|
||||
@@ -8,6 +10,8 @@ namespace bts.shell.market_view {
|
||||
|
||||
[ProtoContract]
|
||||
class MarketViewFilterStates {
|
||||
[ProtoMember(37)]
|
||||
public Venue[] venue { get; set; }
|
||||
[ProtoMember(1)]
|
||||
public string security { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -123,6 +127,7 @@ namespace bts.shell.market_view {
|
||||
public Func<MarketViewVM.Row, bool> build_predicate() {
|
||||
var pb = new ColumnPredicateBuilder<MarketViewVM.Row>();
|
||||
// security
|
||||
pb.add_discrete_values(venue, r => r.venue);
|
||||
pb.add_regex(security, r => r.security.ToString());
|
||||
pb.add_regex(security_key, r => r.security_key.ToString());
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@
|
||||
// security
|
||||
public SecurityMD security_md { get; }
|
||||
public SecurityImmutable security => security_md.security;
|
||||
public bts.api.Venue venue => security_md.security.Exchange;
|
||||
public uint security_key => security_md.security.Id;
|
||||
public string currency => security_md.security.Currency;
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ namespace bts.shell.market_view {
|
||||
}
|
||||
|
||||
static class DefaultColumns {
|
||||
public static GridColumnSettings venue => _mk(Columns.Venue, ColHeader.Exchange, ColWidth.Exchange, h_align: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings security => _mk(Columns.Security, ColHeader.Security, ColWidth.Security, h_align: HorizontalAlignment.Left);
|
||||
public static GridColumnSettings currency => _mk(Columns.Currency, ColHeader.Currency, ColWidth.Currency, h_align: HorizontalAlignment.Left, is_visible: false);
|
||||
public static GridColumnSettings volume => _mk(Columns.Volume, ColHeader.Volume, ColWidth.Volume, h_align: HorizontalAlignment.Left);
|
||||
|
||||
@@ -122,6 +122,7 @@ namespace mbts.spark {
|
||||
const double working_orders_col_width = 105;
|
||||
var price_converter = new TickDisplayConverter();
|
||||
|
||||
add_col(ColKey.Venue, r => r.venue.ToString(), nameof(Row.venue), venue_filter, FilterType.DiscreteValues, ColWidth.Exchange);
|
||||
add_col(ColKey.Security, r => r.security.ToString(), nameof(Row.security), security_filter, FilterType.Regex, ColWidth.Security);
|
||||
add_col(ColKey.Currency, r => r.currency, nameof(Row.currency), currency_filter, FilterType.Regex, ColWidth.Currency);
|
||||
add_col(ColKey.Volume, r => r.volume_text, nameof(Row.volume), volume_filter, FilterType.ULongMinMax, ColWidth.Volume);
|
||||
@@ -388,6 +389,7 @@ namespace mbts.spark {
|
||||
internal IBus bus => _bus;
|
||||
internal OrdersModel orders_model => _orders_model;
|
||||
|
||||
public IDataGridColumnPersistentFilter<Row, Venue[]> venue_filter { get; } = DiscreteValuesFilterVM.Common.exchange<Row>(r => r.venue, nameof(Row.venue));
|
||||
public IDataGridColumnPersistentFilter<Row, string> security_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, string> currency_filter { get; }
|
||||
public IDataGridColumnPersistentFilter<Row, DoubleMinMaxState> bid_price_filter { get; } = new DoubleMinMax<Row>(r => r.bid_price, nameof(Row.bid_price));
|
||||
@@ -535,6 +537,7 @@ namespace mbts.spark {
|
||||
|
||||
public MarketViewFilterStates get_filter_states() =>
|
||||
new() {
|
||||
venue = venue_filter.get_state(),
|
||||
security = security_filter.get_state(),
|
||||
bid_price = bid_price_filter.get_state(),
|
||||
bid_merged_size = bid_qty_filter.get_state(),
|
||||
@@ -580,6 +583,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
using var _ = rows_view.DeferRefresh();
|
||||
venue_filter.load_state(filters.venue);
|
||||
security_filter.load_state(filters.security);
|
||||
bid_price_filter.load_state(filters.bid_price);
|
||||
bid_qty_filter.load_state(filters.bid_merged_size);
|
||||
|
||||
@@ -6,6 +6,7 @@ using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
@@ -127,7 +128,11 @@ namespace mbts.ui.spark {
|
||||
size,
|
||||
Observable.Return(calc_buttons));
|
||||
|
||||
_show_popup(ticket_vm, new OrderTicketHeader { bbo = ticket_vm.bbo }, resources);
|
||||
_show_popup(
|
||||
ticket_vm,
|
||||
new OrderTicketHeader { bbo = ticket_vm.bbo },
|
||||
resources,
|
||||
view => Behavior.SetAutoFocusTargetProvider(view, AutoFocusTargetProvider.TypeProvider(typeof(TextBox), view)));
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header) {
|
||||
@@ -136,7 +141,7 @@ namespace mbts.ui.spark {
|
||||
_show_popup(content, header, resources);
|
||||
}
|
||||
|
||||
void _show_popup(object content, object header, ResourceDictionary resources) {
|
||||
void _show_popup(object content, object header, ResourceDictionary resources, Action<HeaderedContentControl> init_view = null) {
|
||||
var view = new HeaderedContentControl {
|
||||
DataContext = content,
|
||||
Content = content,
|
||||
@@ -146,6 +151,7 @@ namespace mbts.ui.spark {
|
||||
Resources = resources ?? new AuxiliaryDataResources()
|
||||
};
|
||||
view.Style = view.Resources[AuxiliaryDataResources.PopupChildStyleKey] as Style;
|
||||
init_view?.Invoke(view);
|
||||
_popup_man.show(view);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ using bts.view;
|
||||
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.settings;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
@@ -27,12 +29,18 @@ using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewSettings>, ISharedWindow, ISlowMeasureContentSize {
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
|
||||
static int _window_id_counter = 0;
|
||||
|
||||
readonly string _window_id = $"{_window_type}_{Interlocked.Increment(ref _window_id_counter)}";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly ISecurityMetadataStore _sec_meta_store;
|
||||
readonly IPositionModel _position_model;
|
||||
@@ -122,7 +130,7 @@ namespace mbts.spark {
|
||||
Log.notnull(_state);
|
||||
_menu_display_vm = new SparkMenuDisplayVM();
|
||||
_menu_display_vm.load_from(_state.menu_display);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_type_display_name);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_id);
|
||||
var security_md_map_src = Observable.Create<IReadOnlyDictionary<uint, SecurityMD>>(o => {
|
||||
IDisposable sub = null;
|
||||
_security_criteria_changed += on_security_criteria_changed;
|
||||
@@ -286,7 +294,32 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) {
|
||||
_menu_display_vm.safe_bind_to_view(parent_view);
|
||||
ui.WindowStyle.AddWinFormsHostChangedHandler(parent_view, OnWinFormsHostChanged);
|
||||
|
||||
void OnWinFormsHostChanged(object sender, RoutedPropertyChangedEventArgs<WPFPanel> e) {
|
||||
// If this window is "activated" by the user clicking on a popup window (such as an
|
||||
// order ticket), it never receives keyboard focus (seems like a bug in Windows). As a
|
||||
// workaround we explicitly focus the view when the parent WPFPanel gets focus. If the
|
||||
// user clicks on the popup a second time, it should get keyboard focus.
|
||||
if (e.OldValue is not null) {
|
||||
e.OldValue.GotFocus -= OnGotFocus;
|
||||
}
|
||||
if (e.NewValue is not null) {
|
||||
e.NewValue.GotFocus += OnGotFocus;
|
||||
}
|
||||
|
||||
void OnGotFocus(object sender, EventArgs e) =>
|
||||
_view.Dispatcher.InvokeAsync(StealFocus, DispatcherPriority.Send);
|
||||
|
||||
void StealFocus() {
|
||||
if (_view.IsVisible) {
|
||||
Log.Ui.info($"MarketViewWindow. Stealing focus from WPFPanel");
|
||||
_view.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
class TimeAndSalesWindow : SparkWindow<TimeAndSalesSettings>, ISharedWindow {
|
||||
@@ -35,7 +36,9 @@
|
||||
const double _VERTICAL_SCROLLBAR_OVERLAP = 9;
|
||||
|
||||
static readonly TimeSpan _trade_poll_interval = TimeSpan.FromSeconds(0.4);
|
||||
static int _window_id_counter = 0;
|
||||
|
||||
readonly string _window_id = $"{WINDOW_TYPE_NAME}_{Interlocked.Increment(ref _window_id_counter)}";
|
||||
[DisallowNull]
|
||||
readonly MDConsumer _consumer;
|
||||
[DisallowNull]
|
||||
@@ -133,7 +136,7 @@
|
||||
}
|
||||
return result;
|
||||
});
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(WINDOW_DISPLAY_NAME);
|
||||
var update_securities_cmd = SparkCommands.change_securities_callback(_window_id);
|
||||
var security_criteria_changed = new Action<SecurityCriteria>(sc => { });
|
||||
|
||||
SparkCommand toggle_is_paused_cmd = null;
|
||||
|
||||
+20
@@ -314,6 +314,12 @@
|
||||
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Nullable Double-->
|
||||
<usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding param.display_value, Converter={StaticResource DoubleTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<!--Enum-->
|
||||
<usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
@@ -435,6 +441,20 @@
|
||||
</c:TickUpDownDouble>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.DoubleTemplate>
|
||||
<!--Nullable Double-->
|
||||
<usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<DataTemplate>
|
||||
<c:TickUpDownNullableDouble x:Name="tick_up_down" Focusable="False" HorizontalAlignment="Stretch" Value="{Binding param.display_value, UpdateSourceTrigger=PropertyChanged}" ArrowKeyTickModifiers="Ctrl">
|
||||
<c:TickUpDownNullableDouble.CommandBindings>
|
||||
<CommandBinding Command="{x:Static u:NavigationHelper.MoveFocusCommand}" Executed="{x:Static u:NavigationHelper.KeyboardMoveFocusExecuted}"/>
|
||||
</c:TickUpDownNullableDouble.CommandBindings>
|
||||
<c:TickUpDownNullableDouble.InputBindings>
|
||||
<KeyBinding Key="Up" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Up}"/>
|
||||
<KeyBinding Key="Down" Command="{x:Static u:NavigationHelper.MoveFocusCommand}" CommandParameter="{x:Static FocusNavigationDirection.Down}"/>
|
||||
</c:TickUpDownNullableDouble.InputBindings>
|
||||
</c:TickUpDownNullableDouble>
|
||||
</DataTemplate>
|
||||
</usp:AlgoParamTemplateSelector.NullableDoubleTemplate>
|
||||
<!--Enum-->
|
||||
<usp:AlgoParamTemplateSelector.EnumTemplate>
|
||||
<DataTemplate>
|
||||
|
||||
+2
@@ -33,6 +33,7 @@ namespace mbts.ui.spark {
|
||||
public DataTemplate UIntTemplate { get; set; }
|
||||
public DataTemplate NullableUIntTemplate { get; set; }
|
||||
public DataTemplate DoubleTemplate { get; set; }
|
||||
public DataTemplate NullableDoubleTemplate { get; set; }
|
||||
public DataTemplate EnumTemplate { get; set; }
|
||||
public DataTemplate NullableEnumTemplate { get; set; }
|
||||
public DataTemplate StringTemplate { get; set; }
|
||||
@@ -52,6 +53,7 @@ namespace mbts.ui.spark {
|
||||
IAlgoParamVM pvm = item as IAlgoParamVM ?? (item as AlgoParamRowVM)?.param;
|
||||
if (pvm != null) {
|
||||
if (pvm is IAlgoParamVM<double>) return DoubleTemplate;
|
||||
if (pvm is IAlgoParamVM<double?>) return NullableDoubleTemplate;
|
||||
if (pvm is IAlgoParamVM<int>) return IntTemplate;
|
||||
if (pvm is IAlgoParamVM<int?>) return NullableIntTemplate;
|
||||
if (pvm is IAlgoParamVM<uint>) return UIntTemplate;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -66,8 +66,12 @@ namespace mbts.spark {
|
||||
_orders = orders;
|
||||
_real_price = real_price;
|
||||
_text_lines = backing(nameof(text_lines), () => {
|
||||
var fmt = MDDisplayHelper.get_value_formatter(_owner.model.security_md.quantity_display);
|
||||
return BadgeRender.format(side, _quantity, _filled, _current_badge_style, fmt);
|
||||
var tf = _owner.model.security.tick_factors();
|
||||
var fmt = MDDisplayHelper.get_value_formatter(
|
||||
owner.current_settings.size_display,
|
||||
_owner.model.security_md.quantity_display,
|
||||
tf.to_api_tick_display_settingsi());
|
||||
return BadgeRender.format(side, _quantity, _filled, price, _current_badge_style, fmt);
|
||||
});
|
||||
_clear_phantom_price = new(() => {
|
||||
_phantom_price = double.NaN;
|
||||
|
||||
@@ -100,6 +100,9 @@ namespace mbts.spark {
|
||||
readonly OrderEntryPanelItemVisibilityVM _cancel_all_visibility = new("Cancel All");
|
||||
readonly OrderEntryPanelItemVisibilityVM _status_message_visibility = new("Status Message");
|
||||
|
||||
readonly PanelPricePointVM _bid_price = new("Bid");
|
||||
readonly PanelPricePointVM _ask_price = new("Ask");
|
||||
readonly PanelPricePointVM _mid_price = new("Mid");
|
||||
readonly PanelRelativePricePointVM _day_change = new("Day Change");
|
||||
readonly PanelPricePointVM _session_high = new("High");
|
||||
readonly PanelPricePointVM _session_low = new("Low");
|
||||
@@ -139,9 +142,6 @@ namespace mbts.spark {
|
||||
accounts_view = new ListCollectionView<AccountTuple?>(_accounts) {
|
||||
CustomSort = NullCheckComparer<AccountTuple>.Default
|
||||
};
|
||||
// cmds
|
||||
tick_up_ticks_per_row_cmd = new DelegateCommand(increment_ticks_per_row);
|
||||
tick_down_ticks_per_row_cmd = new DelegateCommand(decrement_ticks_per_row);
|
||||
// update
|
||||
_update = new DeferrableAction(() => result_changed?.Invoke());
|
||||
mini_ob.result_changed += _update.Invoke;
|
||||
@@ -191,26 +191,6 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
AlgoDefinitionVM algo_factory(AlgoData data) => AlgoDefinitionVM.create(_model, data);
|
||||
void increment_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < 100; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, ticks_per_row + i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void decrement_ticks_per_row() {
|
||||
var tpr = ticks_per_row;
|
||||
for (uint i = 1; i < tpr; i++) {
|
||||
var new_tpr = TickUtils.fit_ticks_per_row_to_dollars(security, tpr - i);
|
||||
if (new_tpr != tpr) {
|
||||
ticks_per_row = new_tpr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action result_changed;
|
||||
@@ -235,15 +215,8 @@ namespace mbts.spark {
|
||||
}
|
||||
public uint ticks_per_row {
|
||||
get => _ticks_per_row;
|
||||
set => Set(ref _ticks_per_row, value, dependent_props: nameof(rounded_ticks_per_row));
|
||||
set => Set(ref _ticks_per_row, value);
|
||||
}
|
||||
public ICommand tick_up_ticks_per_row_cmd { get; }
|
||||
public ICommand tick_down_ticks_per_row_cmd { get; }
|
||||
// since settings profiles may be shared among ladders
|
||||
// of different securities/tick sizes, the ticks per row setting
|
||||
// may need to be rounded to a number that is a factor of $1.00
|
||||
public uint? rounded_ticks_per_row =>
|
||||
TickUtils.fit_ticks_per_row_to_dollars(_security, ticks_per_row);
|
||||
|
||||
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
|
||||
public SimpleGridSettingsVM mini_ob { get; } = new SimpleGridSettingsVM(OrderBookGridSettingsManager.hard_column_flags);
|
||||
@@ -327,7 +300,12 @@ namespace mbts.spark {
|
||||
|
||||
public IEnumerable order_entry_panel_items {
|
||||
get {
|
||||
// Order these items according to their display order in the actual ladder (from top to
|
||||
// bottom). There are a lot of them, so this is a simple convenience for the user.
|
||||
yield return _zoom_slider_visbility;
|
||||
yield return _ask_price;
|
||||
yield return _mid_price;
|
||||
yield return _bid_price;
|
||||
yield return _day_change;
|
||||
yield return _session_high;
|
||||
yield return _session_low;
|
||||
@@ -450,6 +428,12 @@ namespace mbts.spark {
|
||||
zoom_factor = settings.zoom_factor;
|
||||
// left-panel (show/hide)
|
||||
_zoom_slider_visbility.show = settings.zoom_slider_visible;
|
||||
_bid_price.show = settings.bid_price_visible;
|
||||
_bid_price.load_from(settings.panel_price_point_settings.bid_price);
|
||||
_ask_price.show = settings.ask_price_visible;
|
||||
_ask_price.load_from(settings.panel_price_point_settings.ask_price);
|
||||
_mid_price.show = settings.mid_price_visible;
|
||||
_mid_price.load_from(settings.panel_price_point_settings.mid_price);
|
||||
_day_change.show = settings.day_visible;
|
||||
_day_change.load_from(settings.panel_price_point_settings.day_change);
|
||||
_session_high.show = settings.high_visible;
|
||||
@@ -537,6 +521,9 @@ namespace mbts.spark {
|
||||
.ToList();
|
||||
// left-panel price items
|
||||
clone.panel_price_point_settings = new LadderPanelPricePointSettings {
|
||||
bid_price = _bid_price.create(),
|
||||
ask_price = _ask_price.create(),
|
||||
mid_price = _mid_price.create(),
|
||||
day_change = _day_change.create(),
|
||||
open = _open.create(),
|
||||
session_low = _session_low.create(),
|
||||
@@ -561,6 +548,9 @@ namespace mbts.spark {
|
||||
clone.order_entry_panel_initial_width = _order_entry_panel_width;
|
||||
clone.zoom_factor = zoom_factor;
|
||||
// left-panel (components)
|
||||
clone.bid_price_visible = _bid_price.show;
|
||||
clone.ask_price_visible = _ask_price.show;
|
||||
clone.mid_price_visible = _mid_price.show;
|
||||
clone.day_visible = _day_change.show;
|
||||
clone.high_visible = _session_high.show;
|
||||
clone.low_visible = _session_low.show;
|
||||
@@ -715,6 +705,7 @@ namespace mbts.spark {
|
||||
|
||||
public uint sample_quantity { get; } = 50;
|
||||
public uint sample_filled { get; } = 12;
|
||||
public double sample_price => 2175.50;
|
||||
public Side sample_side { get; } = Side.Buy;
|
||||
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
|
||||
public Brush badge_background { get; } = Brushes.White;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,6 +14,7 @@ using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
@@ -62,6 +63,7 @@ namespace mbts.spark {
|
||||
readonly RefValueContainer<IEnumerable<VolumeAtPriceLevelImmutable>> _volume_container = new();
|
||||
|
||||
IDisposable _settings_sub, _volume_sub, _tick_sub, _fair_value_lines_sub, _vm_sub;
|
||||
readonly ThrottledAction _scroll_to_tick_log_throttle = new(TimeSpan.FromSeconds(5));
|
||||
readonly SingleValuePump<ScrollViewer> _scroll_viewer_source;
|
||||
|
||||
ScrollViewer _scroll_viewer {
|
||||
@@ -92,8 +94,8 @@ namespace mbts.spark {
|
||||
readonly MarketBreakLines _mkt_break_lines;
|
||||
NativePriceSize _last_trade;
|
||||
Func<NativePriceSize, string> _fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi);
|
||||
Func<double, string> _fmt_mkt_quantity_impl = v => v.ToString();
|
||||
MarketSizeDisplay.get_default(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
Func<double, double, string> _fmt_mkt_quantity_impl = (v, p) => v.ToString();
|
||||
int _old_tooltip_row = int.MaxValue;
|
||||
NativePriceSize? _old_last_trade;
|
||||
string _direction_arrow = string.Empty;
|
||||
@@ -452,8 +454,8 @@ namespace mbts.spark {
|
||||
|
||||
public bool scroll_to_tick(long tick) {
|
||||
if (tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
$"Unable to scroll to tick {tick} because it exceeds int.MaxValue");
|
||||
_scroll_to_tick_log_throttle.Execute(() =>
|
||||
Log.Ui.error($"Unable to scroll to tick {tick} because it exceeds int.MaxValue"));
|
||||
return false;
|
||||
}
|
||||
return scroll_to_tick((int)tick);
|
||||
@@ -735,8 +737,8 @@ namespace mbts.spark {
|
||||
};
|
||||
view.RequestClose += (o, _) => popup.IsOpen = false;
|
||||
}
|
||||
string fmt_mbo_qty(int value) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity((uint)value)}" : _fmt_mkt_quantity((uint)value);
|
||||
string fmt_mbo_qty(double value, double price) =>
|
||||
value < 0 ? $"-{_fmt_mkt_quantity(value, price)}" : _fmt_mkt_quantity(value, price);
|
||||
}
|
||||
|
||||
protected override void OnMouseDoubleClick(MouseButtonEventArgs e) {
|
||||
@@ -873,26 +875,7 @@ namespace mbts.spark {
|
||||
render_market_col(_bids, _bid_market_cell_background);
|
||||
break;
|
||||
case ColumnType.Price:
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
_refresh_prices(column);
|
||||
break;
|
||||
case ColumnType.Ask:
|
||||
render_market_col(_asks, _ask_market_cell_background);
|
||||
@@ -1014,8 +997,10 @@ namespace mbts.spark {
|
||||
//System.Diagnostics.Debug.WriteLine(" Tooltip - Previous tooltip is visible, but we've changed rows");
|
||||
_hide_tool_tip();
|
||||
}
|
||||
|
||||
_tooltip_view.Content = _fmt_mkt_quantity(volume);
|
||||
double price = row < 0 || row >= (_price_list?.Count ?? 0)
|
||||
? double.NaN
|
||||
: _price_for_row(row);
|
||||
_tooltip_view.Content = _fmt_mkt_quantity(volume, price);
|
||||
_tooltip_view.IsOpen = true;
|
||||
ToolTip = _tooltip_view;
|
||||
} else {
|
||||
@@ -1102,6 +1087,23 @@ namespace mbts.spark {
|
||||
return Disposable.Create(() => _can_user_send_orders_changed -= o.OnNext);
|
||||
});
|
||||
|
||||
LadderPriceList _create_price_list() {
|
||||
if (_settings is not null) {
|
||||
switch (_settings.net_change) {
|
||||
case LadderSettings.NetChangeTerms.BasisPoints:
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints:
|
||||
return new() {
|
||||
length = _initial_row_count,
|
||||
regional_tick = new RegionalTickFactors(new user.settings.TickFactors(10000, 1)),
|
||||
};
|
||||
}
|
||||
}
|
||||
return new() {
|
||||
length = _initial_row_count,
|
||||
regional_tick = _spec
|
||||
};
|
||||
}
|
||||
|
||||
#region Timer.Tick, Refresh Methods
|
||||
void _tick() {
|
||||
if (_settings != null) {
|
||||
@@ -1111,10 +1113,7 @@ namespace mbts.spark {
|
||||
_mkt_break_lines.refresh();
|
||||
if (_price_list is null) {
|
||||
Log.assert(_price_dirty);
|
||||
_price_list = new LadderPriceList {
|
||||
length = _initial_row_count,
|
||||
security = security
|
||||
};
|
||||
_price_list = _create_price_list();
|
||||
var mid_tick = security.tickdiff(_mkt_break_lines.origin, 0) ?? 0;
|
||||
if (mid_tick > int.MaxValue) {
|
||||
Log.Ui.error(
|
||||
@@ -1151,10 +1150,8 @@ namespace mbts.spark {
|
||||
_scroll_viewer.ScrollToVerticalOffset(new_vertical_offset);
|
||||
}
|
||||
}
|
||||
_refresh_prices();
|
||||
} else {
|
||||
_refresh_prices();
|
||||
}
|
||||
_refresh_prices();
|
||||
scroll_to_pending_target();
|
||||
_refresh_all_visuals();
|
||||
} else {
|
||||
@@ -1163,8 +1160,17 @@ namespace mbts.spark {
|
||||
_refresh_bid_mkt_break();
|
||||
if (_mkt_break_lines.ask_dirty)
|
||||
_refresh_ask_mkt_break();
|
||||
if (_mkt_break_lines.mid_dirty)
|
||||
if (_mkt_break_lines.mid_dirty) {
|
||||
switch (_settings.net_change) {
|
||||
case LadderSettings.NetChangeTerms.BasisPoints:
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints:
|
||||
_refresh_prices();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_refresh_mid_mkt_break();
|
||||
}
|
||||
_refresh_fair_value_lines(false);
|
||||
if (_mkt_break_lines.session_low_dirty)
|
||||
_refresh_session_low_line();
|
||||
@@ -1174,8 +1180,16 @@ namespace mbts.spark {
|
||||
_refresh_session_range();
|
||||
if (last_trade_changed) {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
if (last_index > 0) {
|
||||
LadderRender.render_last(
|
||||
_last_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_last_trade,
|
||||
last_index,
|
||||
_settings.row_height,
|
||||
_settings.last_column,
|
||||
_fmt_mkt_depth_item);
|
||||
}
|
||||
}
|
||||
if (depth_changed)
|
||||
_refresh_sizes();
|
||||
@@ -1190,25 +1204,27 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_prices() {
|
||||
void _refresh_prices(Column column = null) {
|
||||
if (_settings is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settings.net_change != LadderSettings.NetChangeTerms.None) {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _vm.md.settlement_price,
|
||||
_settings.net_change == LadderSettings.NetChangeTerms.Ticks,
|
||||
_settings.row_height, _settings.price_column,
|
||||
_spec);
|
||||
} else {
|
||||
LadderRender.render_prices(
|
||||
_price_vis_tup.visual, _price_vis_tup.glyph_context,
|
||||
_price_list, _settings.row_height,
|
||||
_settings.price_column, _spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
column ??= _price_column;
|
||||
var ref_price = _settings.net_change switch {
|
||||
LadderSettings.NetChangeTerms.BasisPoints or
|
||||
LadderSettings.NetChangeTerms.NetBasisPoints or
|
||||
LadderSettings.NetChangeTerms.Ticks => _get_mid_market_break(),
|
||||
_ => 0.0
|
||||
};
|
||||
LadderRender.render_prices(
|
||||
column.visual_tup.visual,
|
||||
column.visual_tup.glyph_context,
|
||||
_price_list,
|
||||
ref_price,
|
||||
_settings.net_change,
|
||||
_settings.row_height,
|
||||
column.settings,
|
||||
_spec,
|
||||
_settings.price_display_format);
|
||||
}
|
||||
|
||||
void _refresh_all_visuals() {
|
||||
@@ -1264,26 +1280,43 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void _refresh_session_low_line() {
|
||||
if (_settings.session_low_line.show && _mkt_break_lines.low_render_index.HasValue)
|
||||
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index.Value, _settings.session_low_line.color);
|
||||
if (_settings.session_low_line.show) {
|
||||
_render_session_line(_session_low_vis, _mkt_break_lines.low_render_index ?? -1, _settings.session_low_line.color);
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_session_high_line() {
|
||||
if (_settings.session_high_line.show && _mkt_break_lines.high_render_index.HasValue)
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index.Value, _settings.session_high_line.color);
|
||||
if (_settings.session_high_line.show) {
|
||||
_render_session_line(_session_high_vis, _mkt_break_lines.high_render_index ?? -1, _settings.session_high_line.color);
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh_last_trade() {
|
||||
var last_index = LadderRender.index_of(_price_list, _last_trade.price, _price_comparer);
|
||||
if (last_index > 0)
|
||||
LadderRender.render_last(_last_vis_tup.visual, _last_vis_tup.glyph_context, _last_trade, last_index, _settings.row_height, _settings.last_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_last(
|
||||
_last_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_last_trade,
|
||||
last_index,
|
||||
_settings.row_height,
|
||||
_settings.last_column,
|
||||
_fmt_mkt_depth_item);
|
||||
}
|
||||
|
||||
void _refresh_piq() {
|
||||
if (!_settings.piq_column.show)
|
||||
return;
|
||||
int origin_index = LadderRender.index_of(_price_list, _mkt_break_lines.origin, _price_comparer);
|
||||
LadderRender.render_piqs(_piq_vis_tup.visual, _last_vis_tup.glyph_context, _price_list, _vm.price_vs_piq, _settings.row_height, _spec, _settings.piq_column, _fmt_mkt_quantity);
|
||||
LadderRender.render_piqs(
|
||||
_piq_vis_tup.visual,
|
||||
_last_vis_tup.glyph_context,
|
||||
_price_list,
|
||||
_vm.price_vs_piq,
|
||||
_settings.row_height,
|
||||
_spec,
|
||||
_settings.piq_column,
|
||||
_fmt_mkt_quantity);
|
||||
}
|
||||
|
||||
void _refresh_sizes() {
|
||||
@@ -1345,7 +1378,7 @@ namespace mbts.spark {
|
||||
}
|
||||
// regardless of if we've arrived here via a new Last (and new direction arrow)
|
||||
// or we're reusing the old one, this format bit is the same
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size)} ";
|
||||
return $"{_direction_arrow} {_fmt_mkt_quantity(_last_trade.size, _last_trade.price)} ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1361,7 +1394,7 @@ namespace mbts.spark {
|
||||
return;
|
||||
if (_price_list is null) {
|
||||
_price_list = new LadderPriceList {
|
||||
security = security
|
||||
regional_tick = _spec
|
||||
};
|
||||
}
|
||||
_price_list.reset(null, null, _initial_row_count);
|
||||
@@ -1398,7 +1431,10 @@ namespace mbts.spark {
|
||||
double _get_bid_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return LadderPriceLineViewAdapter.bid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
var v = best_bid
|
||||
.if_nan(_spec.tickdown(best_ask))
|
||||
.if_nan(_vm.settlement);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
double _normalize_price(double price) =>
|
||||
@@ -1407,13 +1443,20 @@ namespace mbts.spark {
|
||||
double _get_ask_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return LadderPriceLineViewAdapter.ask_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
var v = _spec.tickdown(best_ask)
|
||||
.if_nan(best_bid)
|
||||
.if_nan(_vm.settlement);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
double _get_mid_market_break() {
|
||||
double best_bid = _get_top_price(_bids);
|
||||
double best_ask = _get_top_price(_asks);
|
||||
return LadderPriceLineViewAdapter.mid_line(best_bid, best_ask, _vm.settlement, _spec);
|
||||
var v = TickUtils.mid_tick(_spec, best_bid, best_ask)
|
||||
.if_nan(best_bid)
|
||||
.if_nan(_spec?.tickdown(best_ask) ?? double.NaN)
|
||||
.if_nan(_vm.settlement);
|
||||
return _spec.round_to_tick(v, RoundingMode.AbsDown);
|
||||
}
|
||||
|
||||
double _get_session_low() => _normalize_price(_get_session_low_raw());
|
||||
@@ -1445,7 +1488,13 @@ namespace mbts.spark {
|
||||
_pending_scroll_to_origin |= _settings == null;
|
||||
_invalidate_spec();
|
||||
if (_price_list != null) {
|
||||
_price_list.security = security;
|
||||
_pending_price_list = null;
|
||||
if (_price_list.Count > 0) {
|
||||
var high_price = _price_list.First();
|
||||
var new_tickdiff = _spec.count_ticks(high_price);
|
||||
_price_list.high_tick = (long)new_tickdiff;
|
||||
}
|
||||
_price_list.regional_tick = _spec;
|
||||
}
|
||||
_bid_vis_tup.glyph_context = glyph_context(s.bid_column);
|
||||
_ask_vis_tup.glyph_context = glyph_context(s.ask_column);
|
||||
@@ -1457,9 +1506,11 @@ namespace mbts.spark {
|
||||
static GlyphContext glyph_context(LadderColumnSettings cs) =>
|
||||
GlyphContext.create(cs.font_family, cs.font_weight);
|
||||
|
||||
var price_display = _spec.tick_factors(0).to_api_tick_display_settingsi();
|
||||
_fmt_mkt_depth_item_impl = MDDisplayHelper.get_formatter_nps(
|
||||
_settings.size_display, _qty_display);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(_qty_display);
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_fmt_mkt_quantity_impl = MDDisplayHelper.get_value_formatter(
|
||||
_settings.size_display, _qty_display, price_display);
|
||||
_set_columns();
|
||||
_apply_column_visual_offset();
|
||||
if (prev_row_height != _settings.row_height)
|
||||
@@ -1530,6 +1581,7 @@ namespace mbts.spark {
|
||||
// }
|
||||
scroll_to_pending_target();
|
||||
visible_columns_changed?.Invoke();
|
||||
_refresh_all_visuals();
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_depth_item_impl.
|
||||
@@ -1537,16 +1589,17 @@ namespace mbts.spark {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_depth_item_impl;
|
||||
impl ??= MDDisplayHelper.get_formatter_nps(MarketSizeDisplay.get_default(), _qty_display);
|
||||
impl ??= MDDisplayHelper.get_formatter_nps(
|
||||
MarketSizeDisplay.get_default(), _qty_display, _spec.tick_factors(0).to_api_tick_display_settingsi());
|
||||
return impl(value);
|
||||
}
|
||||
|
||||
// This is designed to be a redundant null check for _fmt_mkt_quantity_impl.
|
||||
string _fmt_mkt_quantity(double value) {
|
||||
string _fmt_mkt_quantity(double value, double price) {
|
||||
// Assign the impl delegate to a local variable, just in case it is reassigned on another thread.
|
||||
// This shouldn't happen, but better to avoid the potential null ref just in case it does. Expect the unexpected.
|
||||
var impl = _fmt_mkt_quantity_impl;
|
||||
return impl is null ? value.ToString() : impl(value);
|
||||
return impl is null ? value.ToString() : impl(value, price);
|
||||
}
|
||||
|
||||
void _init_tactile_adorner() {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
using bts.api;
|
||||
|
||||
namespace mbts.spark {
|
||||
using RegionalTickFactors = user.settings.RegionalTickFactors;
|
||||
|
||||
class LadderPriceLineViewAdapter {
|
||||
public static double bid_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ask_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (!double.IsNaN(ask)) {
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (!double.IsNaN(bid)) {
|
||||
return bid;
|
||||
}
|
||||
return settlement;
|
||||
}
|
||||
}
|
||||
|
||||
public static double mid_line(
|
||||
double bid, double ask, double settlement, RegionalTickFactors spec) {
|
||||
return TickUtils.normalize_price(spec, impl());
|
||||
double impl() {
|
||||
if (double.IsNaN(bid)) {
|
||||
if (double.IsNaN(ask)) {
|
||||
return settlement;
|
||||
}
|
||||
return _try_tick_down(spec, ask);
|
||||
}
|
||||
if (double.IsNaN(ask)) {
|
||||
return bid;
|
||||
}
|
||||
if (spec is not null) {
|
||||
return TickUtils.mid_tick(spec, bid, ask);
|
||||
}
|
||||
return bid;
|
||||
}
|
||||
}
|
||||
|
||||
static double _try_tick_down(RegionalTickFactors spec, double value) {
|
||||
if (spec is not null) {
|
||||
return spec.tickdown(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,33 +16,34 @@ namespace mbts.spark {
|
||||
long _length;
|
||||
long _high_tick;
|
||||
SecurityImmutable? _security;
|
||||
RegionalTickFactors _regional_tick_backing;
|
||||
RegionalTickFactors _regional_tick =>
|
||||
_regional_tick_backing ??= TickUtils.get_regional_ticks(_security);
|
||||
|
||||
public LadderPriceList() { }
|
||||
public LadderPriceList()
|
||||
: this(RegionalTickFactors.Default) { }
|
||||
|
||||
public LadderPriceList(RegionalTickFactors regional_tick) =>
|
||||
this.regional_tick = regional_tick;
|
||||
|
||||
public LadderPriceList(LadderPriceList other) {
|
||||
_length = other._length;
|
||||
_high_tick = other._high_tick;
|
||||
_security = other._security;
|
||||
regional_tick = other.regional_tick;
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
|
||||
public SecurityImmutable? security {
|
||||
get => _security;
|
||||
RegionalTickFactors _regional_tick;
|
||||
public RegionalTickFactors regional_tick {
|
||||
get => _regional_tick;
|
||||
set {
|
||||
if (value == _security) {
|
||||
return;
|
||||
if (value is null) {
|
||||
_regional_tick = RegionalTickFactors.Default;
|
||||
} else {
|
||||
_regional_tick = value;
|
||||
}
|
||||
_security = value;
|
||||
_regional_tick_backing = null;
|
||||
}
|
||||
}
|
||||
|
||||
public ContractSpecificationImmutable spec { get; set; }
|
||||
|
||||
public long high_tick {
|
||||
get => _high_tick;
|
||||
set => _high_tick = value;
|
||||
@@ -54,8 +55,8 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public double start {
|
||||
get => _regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = _regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
get => regional_tick.tickup<double>(0, (uint)high_tick);
|
||||
set => high_tick = regional_tick.tickdiff(value, 0) ?? high_tick;
|
||||
}
|
||||
|
||||
public long length {
|
||||
@@ -80,7 +81,7 @@ namespace mbts.spark {
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public double this[int idx] {
|
||||
get => _regional_tick.tick<double>(0, high_tick - idx);
|
||||
get => regional_tick.tick<double>(0, high_tick - idx);
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -94,7 +95,7 @@ namespace mbts.spark {
|
||||
GetEnumerator();
|
||||
|
||||
public int IndexOf(double item) {
|
||||
var idx = _regional_tick.tickdiff(start, item) ?? -1;
|
||||
var idx = regional_tick.tickdiff(start, item) ?? -1;
|
||||
if (idx < 0 || idx >= length) {
|
||||
return -1;
|
||||
}
|
||||
@@ -209,7 +210,7 @@ namespace mbts.spark {
|
||||
other != null &&
|
||||
_high_tick == other._high_tick &&
|
||||
_length == other._length &&
|
||||
_regional_tick.safe_equals(other._regional_tick);
|
||||
regional_tick.safe_equals(other.regional_tick);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as LadderPriceList);
|
||||
@@ -218,7 +219,7 @@ namespace mbts.spark {
|
||||
HashCode.Combine(_high_tick, _length);
|
||||
|
||||
double _tick_to_price(long tick) =>
|
||||
_regional_tick.tick<double>(0, tick);
|
||||
regional_tick.tick<double>(0, tick);
|
||||
|
||||
void _add_tick(long tick) {
|
||||
int idx;
|
||||
|
||||
@@ -31,19 +31,19 @@ namespace mbts.spark {
|
||||
Side side,
|
||||
double quantity,
|
||||
double filled,
|
||||
double price,
|
||||
LadderSettings.BadgeStyle style,
|
||||
Func<double, string> fmt_value = null
|
||||
Func<double, double, string> fmt_value = null
|
||||
) {
|
||||
if (fmt_value is null)
|
||||
fmt_value = FormattingDelegates.default_string<double>();
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
switch (style) {
|
||||
case LadderSettings.BadgeStyle.Alternate:
|
||||
var working_qty = quantity > filled ? quantity - filled : 0;
|
||||
return new[] {
|
||||
$"{side.abbreviate()}:{fmt_value(filled)}",
|
||||
$"W:{fmt_value(working_qty)}"
|
||||
$"{side.abbreviate()}:{fmt_value(filled, price)}",
|
||||
$"W:{fmt_value(working_qty, price)}"
|
||||
};
|
||||
default: return new[] { $"{fmt_value(filled)}/{fmt_value(quantity)}" };
|
||||
default: return new[] { $"{fmt_value(filled, price)}/{fmt_value(quantity, price)}" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,13 +79,6 @@ namespace mbts.spark {
|
||||
public static int index_of(IList<double> prices, double price, IComparer<double> comparer, bool relative_idx) {
|
||||
for (int i = 1; i < prices.Count; i++) {
|
||||
Log.assert(prices[i - 1] > prices[i]);
|
||||
var c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
if (c >= 0) {
|
||||
c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
} else {
|
||||
c = comparer.Compare(prices[i - 1], prices[i]);
|
||||
}
|
||||
Log.assert(c < 0);
|
||||
}
|
||||
var idx = CollectionHelper.binary_search(prices, price, comparer);
|
||||
if (relative_idx && idx < 0) {
|
||||
@@ -109,17 +102,64 @@ namespace mbts.spark {
|
||||
settings.font_size, settings.width, row_height, HorizontalAlignment.Right,
|
||||
-4, VerticalAlignment.Center, row * row_height, true);
|
||||
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double reference_price, bool format_in_tick_terms, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec) {
|
||||
public static GlyphRun[] format_prices(
|
||||
IList<double> prices,
|
||||
double reference_price,
|
||||
LadderSettings.NetChangeTerms terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
GlyphContext glyph_context,
|
||||
RegionalTickFactors spec,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default
|
||||
) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
string basis_pts_fmt = terms switch {
|
||||
LadderSettings.NetChangeTerms.BasisPoints or
|
||||
LadderSettings.NetChangeTerms.NetBasisPoints => get_basis_pts_fmt(),
|
||||
_ => null
|
||||
};
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i] - reference_price, format_in_tick_terms), i, row_height, settings, glyph_context));
|
||||
return result;
|
||||
}
|
||||
public static GlyphRun[] format_prices(IList<double> prices, double row_height, LadderColumnSettings settings, GlyphContext glyph_context, RegionalTickFactors spec, LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var result = new GlyphRun[prices.Count];
|
||||
Parallel.For(0, prices.Count, i =>
|
||||
result[i] = format_text(spec.format(prices[i]), i, row_height, settings, glyph_context, price_display_format));
|
||||
result[i] = format_text(fmt(i), i, row_height, settings, glyph_context, price_display_format));
|
||||
return result;
|
||||
|
||||
string fmt(int i) {
|
||||
switch (terms) {
|
||||
case LadderSettings.NetChangeTerms.Dollars:
|
||||
return prices[i].ToString("0.00");
|
||||
case LadderSettings.NetChangeTerms.BasisPoints: {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return spec.format(double.NaN);
|
||||
}
|
||||
var basis_pts = prices[i] * 10000.0 / reference_price;
|
||||
return basis_pts.ToString(basis_pts_fmt);
|
||||
}
|
||||
case LadderSettings.NetChangeTerms.NetBasisPoints: {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return spec.format(double.NaN);
|
||||
}
|
||||
var net_basis_pts = prices[i] * 10000.0 / reference_price - 10000.0;
|
||||
return net_basis_pts.ToString(basis_pts_fmt);
|
||||
}
|
||||
default:
|
||||
return spec.format(prices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
string get_basis_pts_fmt() {
|
||||
if (!reference_price.is_real_non_zero()) {
|
||||
return "0.000";
|
||||
}
|
||||
var tick_sz = spec.ticksize(reference_price);
|
||||
var basis_pt = reference_price.abs() / 10000.0;
|
||||
var precision_log10 = Math.Log10(tick_sz / basis_pt);
|
||||
int n = 0;
|
||||
if (precision_log10 < 0) {
|
||||
n = (int)Math.Ceiling(-precision_log10);
|
||||
}
|
||||
return n < 1
|
||||
? "0"
|
||||
: "0." + new string('0', n);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear_prices(DrawingVisual price, int row_count, double row_height, LadderColumnSettings settings, GlyphContext glyph_context) {
|
||||
@@ -135,51 +175,20 @@ namespace mbts.spark {
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> price_list,
|
||||
IList<double> dprices,
|
||||
double wrt_price,
|
||||
bool format_in_tick_terms,
|
||||
LadderSettings.NetChangeTerms terms,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick) {
|
||||
if (price_list.IsNullOrEmpty()) {
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format = LadderSettings.PriceDisplayFormat.Default) {
|
||||
if (dprices.IsNullOrEmpty()) {
|
||||
return;
|
||||
}
|
||||
render_prices(
|
||||
price, glyph_context, price_list, row_height, settings, tick,
|
||||
wrt_price, format_in_tick_terms);
|
||||
}
|
||||
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick,
|
||||
double wrt_price,
|
||||
bool format_in_tick_terms) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, wrt_price, format_in_tick_terms, row_height, settings, glyph_context, tick);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
for (int x = 0; x < dprices.Count; ++x)
|
||||
dc.DrawGlyphRun(foreground, fprices[x]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void render_prices(
|
||||
DrawingVisual price,
|
||||
GlyphContext glyph_context,
|
||||
IList<double> dprices,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
RegionalTickFactors tick,
|
||||
LadderSettings.PriceDisplayFormat price_display_format) {
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
//var background = new SolidColorBrush(settings.background.rgb_color());
|
||||
var fprices = format_prices(dprices, row_height, settings, glyph_context, tick, price_display_format);
|
||||
var fprices = format_prices(
|
||||
dprices, wrt_price, terms, row_height, settings, glyph_context, tick, price_display_format);
|
||||
using (var dc = price.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, dprices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, row_count * row_height));
|
||||
@@ -232,8 +241,9 @@ namespace mbts.spark {
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
//dc.DrawRectangle(background, null, new Rect(0, 0, settings.width, prices.Count * row_height));
|
||||
if (!prices.within_range(origin_index) || rungs.is_null_or_empty())
|
||||
if (rungs.is_null_or_empty()) {
|
||||
return;
|
||||
}
|
||||
var foreground = settings.foreground.rgb_brush();
|
||||
int curr_index = origin_index;
|
||||
double curr_price = prices[curr_index];
|
||||
@@ -254,8 +264,8 @@ namespace mbts.spark {
|
||||
var price = ticks.HasValue ? spec.tickup(0, ticks.Value) : rung.price;
|
||||
// prices are in descending order, so get diff from curr_price - price
|
||||
var tick_diff = spec.tickdiff(curr_price, price).assert_value();
|
||||
checked {
|
||||
curr_index += (int)tick_diff;
|
||||
if (!MathUtils.try_increment(ref curr_index, tick_diff)) {
|
||||
continue;
|
||||
}
|
||||
curr_price = price;
|
||||
if (curr_index >= 0 && curr_index < prices.Count) {
|
||||
@@ -280,7 +290,7 @@ namespace mbts.spark {
|
||||
VolumeColumnSettings settings,
|
||||
int last_index,
|
||||
string last_size_and_direction,
|
||||
Func<double, string> fmt_value) {
|
||||
Func<double, double, string> fmt_value) {
|
||||
//
|
||||
if (!data.Any()) {
|
||||
_clear_visual(dv);
|
||||
@@ -303,12 +313,12 @@ namespace mbts.spark {
|
||||
_clear_visual(dv);
|
||||
return;
|
||||
}
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
// inflate the max so that the graph never fills the available space
|
||||
double max = data.Max(v => v.Volume) * 1.1;
|
||||
_render_cells(dv, row_height, data.Select(d => (d.Price, d)), settings.general, prices, spec, render_cell);
|
||||
/// local methods
|
||||
void render_cell(DrawingContext dc, int r, VolumeAtPriceLevelImmutable vap) {
|
||||
void render_cell(DrawingContext dc, int r, double price, VolumeAtPriceLevelImmutable vap) {
|
||||
Brush text_brush = settings.general.foreground.rgb_brush();
|
||||
double y = row_height * r;
|
||||
var last_trade_here = r == last_index;
|
||||
@@ -335,11 +345,11 @@ namespace mbts.spark {
|
||||
}
|
||||
x_bar += width;
|
||||
if (show_numbers)
|
||||
numbers_text.Add(fmt_value(v));
|
||||
numbers_text.Add(fmt_value(v, vap.Price));
|
||||
}
|
||||
|
||||
if (settings.total.show_numbers) {
|
||||
numbers_text.Add(fmt_value(vap.Volume));
|
||||
numbers_text.Add(fmt_value(vap.Volume, vap.Price));
|
||||
}
|
||||
if (settings.show_ltq && last_trade_here) {
|
||||
numbers_text.Clear();
|
||||
@@ -366,9 +376,17 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, T data_item);
|
||||
public delegate void RenderCell<T>(DrawingContext dc, int row, double price, T data_item);
|
||||
|
||||
static void _render_cells<T>(DrawingVisual dv, double row_height, IEnumerable<(double price, T value)> cells, LadderColumnSettings settings, IList<double> prices, RegionalTickFactors spec, RenderCell<T> render_cell) {
|
||||
static void _render_cells<T>(
|
||||
DrawingVisual dv,
|
||||
double row_height,
|
||||
IEnumerable<(double price, T value)> cells,
|
||||
LadderColumnSettings settings,
|
||||
IList<double> prices,
|
||||
RegionalTickFactors spec,
|
||||
RenderCell<T> render_cell
|
||||
) {
|
||||
using (var dc = dv.RenderOpen())
|
||||
using (_draw_with_clip(dc, settings, row_height, prices.Count)) {
|
||||
if (prices.is_null_or_empty() || cells.is_null_or_empty()) {
|
||||
@@ -388,7 +406,7 @@ namespace mbts.spark {
|
||||
var check_price = spec.tickdown(max_price, idx);
|
||||
Log.assert(spec.tick_equals(price, check_price));
|
||||
Log.assert(idx >= 0 && idx < prices.Count);
|
||||
render_cell(dc, idx, value);
|
||||
render_cell(dc, idx, price, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,10 +418,10 @@ namespace mbts.spark {
|
||||
int index,
|
||||
double row_height,
|
||||
LadderColumnSettings settings,
|
||||
Func<double, string> fmt_value) {
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
Func<NativePriceSize, string> fmt_value) {
|
||||
fmt_value ??= nps => nps.size.ToString();
|
||||
using (var dc = dv.RenderOpen()) {
|
||||
var gr = format_text(fmt_value(last.size), index, row_height, settings, glyph_context);
|
||||
var gr = format_text(fmt_value(last), index, row_height, settings, glyph_context);
|
||||
dc.DrawGlyphRun(settings.foreground.rgb_brush(), gr);
|
||||
}
|
||||
}
|
||||
@@ -417,13 +435,13 @@ namespace mbts.spark {
|
||||
double row_height,
|
||||
RegionalTickFactors spec,
|
||||
LadderColumnSettings settings,
|
||||
Func<double, string> fmt_value
|
||||
Func<double, double, string> fmt_value
|
||||
) {
|
||||
fmt_value ??= FormattingDelegates.default_string<double>();
|
||||
fmt_value ??= (v, p) => v.ToString();
|
||||
var fg = settings.foreground.rgb_brush();
|
||||
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, piq) =>
|
||||
_render_cells(dv, row_height, pvp, settings, prices, spec, (dc, row, price, piq) =>
|
||||
dc.DrawGlyphRun(
|
||||
fg, format_text(fmt_value(piq), row, row_height, settings, glyph_context)));
|
||||
fg, format_text(fmt_value(piq, price), row, row_height, settings, glyph_context)));
|
||||
}
|
||||
|
||||
public static void render_mkt_break(DrawingVisual dv, int index, double row_height, uint color) =>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -39,6 +39,9 @@
|
||||
|
||||
<cv:EnumCharacterCasingConverter x:Key="casing_converter"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel" />
|
||||
<cv:ResponsiveTextConverter x:Key="BidPriceTextConverter" LongText="Bid:" ShortText="B:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="AskPriceTextConverter" LongText="Ask:" ShortText="A:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="MidPriceTextConverter" LongText="Mid:" ShortText="M:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="DayTextConverter" LongText="Day:" ShortText="D:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="HighTextConverter" LongText="High:" ShortText="H:" Threshold="99" />
|
||||
<cv:ResponsiveTextConverter x:Key="LowTextConverter" LongText="Low:" ShortText="L:" Threshold="99" />
|
||||
@@ -363,13 +366,103 @@
|
||||
<Setter Property="Grid.Column" Value="2"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Margin" Value="0,0,0,2"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
<Setter Property="Padding" Value="4,1"/>
|
||||
</Style>
|
||||
<Style x:Key="LeftPanelPricePointText" TargetType="TextBlock">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.default_text}"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="LeftPanelPricePointButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Grid.Column" Value="2" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
<Setter Property="Margin" Value="0,0,0,2" />
|
||||
<Setter Property="Padding" Value="4,1" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0"
|
||||
Height="Auto"
|
||||
Padding="0"
|
||||
SnapsToDevicePixels="True">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="CommonStates">
|
||||
<VisualState Name="Normal" />
|
||||
|
||||
<VisualState Name="MouseOver">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="HoverRect" Storyboard.TargetProperty="Fill.Opacity" BeginTime="0" Duration="00:00:00.15" To="0.20">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.mouseover_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_background}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.pressed_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_background}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.disabled_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup Name="FocusStates">
|
||||
<VisualState Name="Unfocused" />
|
||||
|
||||
<VisualState Name="Focused">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{u:BTSBrush UI.Button.focused_border}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid>
|
||||
<Rectangle Name="HoverRect" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" IsHitTestVisible="False">
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="White" Opacity="0" />
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
RecognizesAccessKey="True" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<sys:Double x:Key="MaxZoomFactor">1.5</sys:Double>
|
||||
<sys:Double x:Key="MinZoomFactor">0.5</sys:Double>
|
||||
</ResourceDictionary>
|
||||
@@ -434,6 +527,9 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
@@ -441,9 +537,123 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Day Change (label) -->
|
||||
<!-- Ask Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource AskPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
ask_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Ask Price (value) -->
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Visibility="{Binding
|
||||
ask_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Ask"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding ask_price}"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding ask_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding ask_price_style.font_family}"
|
||||
FontSize="{Binding ask_price_style.font_size}"
|
||||
FontWeight="{Binding ask_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
ask_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="ask_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Mid Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource MidPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
mid_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Mid Price (value) -->
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding mid_price}"
|
||||
Visibility="{Binding
|
||||
mid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Mid"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding mid_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding mid_price_style.font_family}"
|
||||
FontSize="{Binding mid_price_style.font_size}"
|
||||
FontWeight="{Binding mid_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
mid_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="mid_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Bid Price (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource BidPriceTextConverter}}"
|
||||
Visibility="{Binding
|
||||
bid_price_style.actual_show_label,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Bid Price (value) -->
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding bid_price}"
|
||||
Visibility="{Binding
|
||||
bid_price_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Bid"
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{Binding bid_price_style.background_color}" Opacity="1" />
|
||||
</Button.Background>
|
||||
<TextBlock
|
||||
FontFamily="{Binding bid_price_style.font_family}"
|
||||
FontSize="{Binding bid_price_style.font_size}"
|
||||
FontWeight="{Binding bid_price_style.font_weight}"
|
||||
Foreground="{Binding
|
||||
bid_price_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="bid_price"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Day Change (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DayTextConverter}}"
|
||||
Visibility="{Binding
|
||||
day_change_style.actual_show_label,
|
||||
@@ -452,7 +662,7 @@
|
||||
|
||||
<!-- Day Change (value) -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Row="3"
|
||||
Background="{Binding
|
||||
day_change_background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -479,7 +689,7 @@
|
||||
|
||||
<!-- High (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Row="4"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource HighTextConverter}}"
|
||||
Visibility="{Binding
|
||||
session_high_style.actual_show_label,
|
||||
@@ -487,15 +697,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- High (value) -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
Background="{Binding
|
||||
session_high_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding session_high}"
|
||||
Visibility="{Binding
|
||||
session_high_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session High"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_high_style.font_family}"
|
||||
FontSize="{Binding session_high_style.font_size}"
|
||||
@@ -504,24 +716,19 @@
|
||||
session_high_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_high}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_high"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Low (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Row="5"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource LowTextConverter}}"
|
||||
Visibility="{Binding
|
||||
session_low_style.actual_show_label,
|
||||
@@ -529,15 +736,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Low (value) -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
<Button
|
||||
Grid.Row="5"
|
||||
Background="{Binding
|
||||
session_low_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding session_low}"
|
||||
Visibility="{Binding
|
||||
session_low_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Session Low"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding session_low_style.font_family}"
|
||||
FontSize="{Binding session_low_style.font_size}"
|
||||
@@ -546,24 +755,19 @@
|
||||
session_low_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding session_low}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="session_low"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Open (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Row="6"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource OpenTextConverter}}"
|
||||
Visibility="{Binding
|
||||
open_style.actual_show_label,
|
||||
@@ -571,15 +775,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Open (value) -->
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
<Button
|
||||
Grid.Row="6"
|
||||
Background="{Binding
|
||||
open_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding open}"
|
||||
Visibility="{Binding
|
||||
open_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Open"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding open_style.font_family}"
|
||||
FontSize="{Binding open_style.font_size}"
|
||||
@@ -587,23 +793,19 @@
|
||||
Foreground="{Binding
|
||||
open_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding open}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="open"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Settlement (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Row="7"
|
||||
Text="{Binding
|
||||
left_panel_width, Converter={StaticResource SettlementTextConverter}}"
|
||||
Visibility="{Binding
|
||||
@@ -612,15 +814,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Settlement (value) -->
|
||||
<Border
|
||||
Grid.Row="4"
|
||||
<Button
|
||||
Grid.Row="7"
|
||||
Background="{Binding
|
||||
settlement_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding settlement}"
|
||||
Visibility="{Binding
|
||||
settlement_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Settlement"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding settlement_style.font_family}"
|
||||
FontSize="{Binding settlement_style.font_size}"
|
||||
@@ -629,23 +833,18 @@
|
||||
settlement_style.font_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink
|
||||
Command="{Binding go_to_price_cmd}" CommandParameter="{Binding settlement}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="settlement"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Theo (label) -->
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Row="8"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource TheoTextConverter}}"
|
||||
Visibility="{Binding
|
||||
theo_style.actual_show_label,
|
||||
@@ -653,15 +852,17 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<!-- Theo (value) -->
|
||||
<Border
|
||||
Grid.Row="5"
|
||||
<Button
|
||||
Grid.Row="8"
|
||||
Background="{Binding
|
||||
theo_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Command="{Binding go_to_price_cmd}"
|
||||
CommandParameter="{Binding theo}"
|
||||
Visibility="{Binding
|
||||
theo_style.show, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
ToolTip="Theo"
|
||||
Style="{StaticResource LeftPanelPricePointBorder}">
|
||||
Style="{StaticResource LeftPanelPricePointButton}">
|
||||
<TextBlock
|
||||
FontFamily="{Binding theo_style.font_family}"
|
||||
FontSize="{Binding theo_style.font_size}"
|
||||
@@ -669,23 +870,19 @@
|
||||
Foreground="{Binding
|
||||
theo_style.font_color, Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
Style="{StaticResource LeftPanelPricePointText}">
|
||||
<Hyperlink Command="{Binding go_to_price_cmd}" CommandParameter="{Binding theo}">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="theo"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Hyperlink>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding
|
||||
Converter="{StaticResource TickDisplayConverter}" Mode="OneWay">
|
||||
<Binding Path="theo"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Button>
|
||||
|
||||
<!-- Delta -->
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Row="9"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource DeltaTextConverter}}"
|
||||
Visibility="{Binding
|
||||
delta_style.actual_show_label,
|
||||
@@ -693,7 +890,7 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<Border
|
||||
Grid.Row="6"
|
||||
Grid.Row="9"
|
||||
Background="{Binding
|
||||
delta_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -714,7 +911,7 @@
|
||||
|
||||
<!-- Vega -->
|
||||
<TextBlock
|
||||
Grid.Row="7"
|
||||
Grid.Row="10"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VegaTextConverter}}"
|
||||
Visibility="{Binding
|
||||
vega_style.actual_show_label,
|
||||
@@ -722,7 +919,7 @@
|
||||
Style="{StaticResource LeftPanelPricePointLabel}" />
|
||||
|
||||
<Border
|
||||
Grid.Row="7"
|
||||
Grid.Row="10"
|
||||
Background="{Binding
|
||||
vega_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -743,7 +940,7 @@
|
||||
<!-- Volume (label) -->
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.Row="8"
|
||||
Grid.Row="11"
|
||||
Text="{Binding left_panel_width, Converter={StaticResource VolumeTextConverter}}"
|
||||
Visibility="{Binding
|
||||
volume_style.actual_show_label,
|
||||
@@ -752,7 +949,7 @@
|
||||
|
||||
<!-- Volume (price) -->
|
||||
<Border
|
||||
Grid.Row="8"
|
||||
Grid.Row="11"
|
||||
Background="{Binding
|
||||
volume_style.background_color,
|
||||
Converter={x:Static cv:ColorToBrushConverter.Instance}}"
|
||||
@@ -793,6 +990,7 @@
|
||||
Margin="2,10,2,2"
|
||||
ItemsSource="{Binding accounts}"
|
||||
SelectedItem="{Binding selected_account}"
|
||||
ToolTip="Account"
|
||||
Visibility="{Binding
|
||||
DataContext.show_account,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -802,6 +1000,7 @@
|
||||
<ComboBox
|
||||
DataContext="{Binding order_properties_vm}" ItemsSource="{Binding time_in_forces}"
|
||||
SelectedItem="{Binding selected_time_in_force}" Margin="2"
|
||||
ToolTip="Time In Force"
|
||||
Visibility="{Binding
|
||||
DataContext.show_tif,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -824,6 +1023,7 @@
|
||||
IsOpen="{Binding is_expanded}"
|
||||
ItemsSource="{Binding algo_definitions_view}"
|
||||
SelectedItem="{Binding selected_algo_definition}"
|
||||
ToolTip="Order/Algo Type"
|
||||
Visibility="{Binding
|
||||
DataContext.show_order_type,
|
||||
RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
@@ -879,6 +1079,7 @@
|
||||
Increment="{Binding size_calculator.unit_quantity}"
|
||||
Minimum="0"
|
||||
TickDisplaySettings="{Binding size_calculator.display_settings}"
|
||||
ToolTip="Size"
|
||||
Value="{Binding size_calculator.display_value, Mode=TwoWay}" />
|
||||
|
||||
<!-- Qty quick buttons -->
|
||||
@@ -901,7 +1102,8 @@
|
||||
RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Foreground="{u:BTSColor UI.Button.normal_foreground}">
|
||||
Foreground="{u:BTSColor UI.Button.normal_foreground}"
|
||||
ToolTip="Add to Size">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding
|
||||
Converter="{x:Static cv:DynamicDoubleMultiplierConverter.Instance}">
|
||||
@@ -921,6 +1123,7 @@
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding size_calculator.clear}"
|
||||
ToolTip="Clear Size"
|
||||
Visibility="{Binding
|
||||
show_clear, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource CommonButton}">
|
||||
@@ -931,6 +1134,7 @@
|
||||
<CheckBox
|
||||
Margin="2"
|
||||
IsChecked="{Binding auto_clear}"
|
||||
ToolTip="Clear Size after order"
|
||||
Visibility="{Binding
|
||||
show_auto_clear, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
Auto Clear
|
||||
|
||||
@@ -76,14 +76,8 @@ namespace mbts.spark {
|
||||
if (show_total_pos) add_part(total_pos_text, " (", ")");
|
||||
//
|
||||
if (part_count > 1) {
|
||||
// This is a complex expression (showing more than 1 value) - wrap it in single quotes.
|
||||
// Explicitly state which value will be copied to the size input.
|
||||
sb.Insert(0, "'");
|
||||
sb.Add("'");
|
||||
sb.Add(" position\n(click to copy ", primary_pos, " to size)");
|
||||
} else {
|
||||
// Only showing 1 value, no need for single quotes,
|
||||
// nor an explicit statement that indicates which value will be copied to the size input.
|
||||
sb.Add(" position\n(click to copy to size)");
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
void refresh_quantity_display() {
|
||||
foreach (var row in _row_collection.all_rows) {
|
||||
foreach (var row in _row_collection.GetAllRows()) {
|
||||
if (_cached_securities.TryGetValue(row.security_key, out var smd)) {
|
||||
row.quantity_display_settings = smd.quantity_display;
|
||||
}
|
||||
|
||||
@@ -289,7 +289,8 @@ namespace bts.shell.pnl {
|
||||
|
||||
public AccountTuple account => new(account_id, account_name);
|
||||
public string symbol => security?.pnl_symbol() ?? string.Empty;
|
||||
public DateTime expiration => security?.earliest_expiration() ?? DateTime.MinValue;
|
||||
public DateTime expiration =>
|
||||
security?.normal_expirations()?.MinOrDefault(DateTime.MinValue) ?? DateTime.MinValue;
|
||||
public double effective_cpp => override_cpp ?? default_cpp;
|
||||
|
||||
/// Market/Calc
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<sys:Double x:Key="VerticalSpacing">4.0</sys:Double>
|
||||
<sys:Double x:Key="ButtonHeight">22.0</sys:Double>
|
||||
<cv:NullableDoubleConverter x:Key="TickDefaultPriceConverter" DefaultValue="0"/>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DefaultButton}">
|
||||
<Setter Property="Height" Value="{StaticResource ButtonHeight}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
@@ -53,8 +53,6 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="140"/>
|
||||
@@ -77,6 +75,7 @@
|
||||
RegionalTick="{Binding price_tick}"
|
||||
Focusable="false"
|
||||
TickDefaultValue="{Binding current_price, Converter={StaticResource TickDefaultPriceConverter}, FallbackValue={u:Double 0}}"
|
||||
ToolTip="Price"
|
||||
Value="{Binding price}"
|
||||
/>
|
||||
<c:TickUpDownNullableDouble
|
||||
@@ -88,6 +87,7 @@
|
||||
Increment="{Binding size_calculator.unit_quantity}"
|
||||
Minimum="0"
|
||||
TickDisplaySettings="{Binding size_calculator.display_settings}"
|
||||
ToolTip="Size"
|
||||
Value="{Binding size_calculator.display_value}"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -97,13 +97,24 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalSpacing}}"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Command="{Binding set_price_command}" CommandParameter="{Binding bbo.bid}" Foreground="{u:BTSColor MarketData.bid}" FontWeight="Bold">
|
||||
<Button
|
||||
Command="{Binding set_price_command}"
|
||||
CommandParameter="{Binding bbo.bid}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
FontWeight="Bold"
|
||||
ToolTip="Bid Price">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.bid"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
<Button Grid.Column="2" Command="{Binding set_price_command}" CommandParameter="{Binding bbo.ask}" Foreground="{u:BTSColor MarketData.ask}" FontWeight="Bold">
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
Command="{Binding set_price_command}"
|
||||
CommandParameter="{Binding bbo.ask}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
FontWeight="Bold"
|
||||
ToolTip="Ask Price">
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="bbo.ask"/>
|
||||
<Binding Path="security.ContractSpec"/>
|
||||
@@ -120,7 +131,7 @@
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.bid_size}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
>
|
||||
ToolTip="Bid Size">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding Path="bbo.bid_size" />
|
||||
<Binding Path="qty_display" />
|
||||
@@ -131,20 +142,20 @@
|
||||
Command="{Binding size_calculator.set_actual_value_command}"
|
||||
CommandParameter="{Binding bbo.ask_size}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
>
|
||||
ToolTip="Ask Size">
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding Path="bbo.ask_size" />
|
||||
<Binding Path="qty_display" />
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.RowSpan="3">
|
||||
<Grid Grid.Row="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ItemsControl Margin="-2,-2,-2,0" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
|
||||
<ItemsControl Margin="0,0,-4,-4" DataContext="{Binding size_calculator}" ItemsSource="{Binding buttons}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="3" Focusable="False"/>
|
||||
@@ -153,11 +164,11 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Margin="2,4,2,0"
|
||||
Margin="0,0,4,4"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Command="{Binding Path=DataContext.click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
>
|
||||
ToolTip="Add to Size">
|
||||
<Button.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static cv:TickDisplayConverter.Instance}">
|
||||
<Binding />
|
||||
@@ -170,35 +181,35 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button Grid.Row="2" Command="{Binding size_calculator.clear}">Clear</Button>
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Command="{Binding size_calculator.clear}"
|
||||
Content="Clear"
|
||||
ToolTip="Clear Size" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="4" Grid.Column="2" Grid.RowSpan="3">
|
||||
<Grid Grid.Row="4" Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalSpacing}}"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox
|
||||
Grid.ColumnSpan="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding account_dirty, FallbackValue={u:False}}"
|
||||
IsEnabled="{Binding can_change_account, FallbackValue={u:True}}"
|
||||
ItemsSource="{Binding properties_vm.accounts}"
|
||||
Margin="0,2"
|
||||
SelectedItem="{Binding properties_vm.selected_account}"
|
||||
/>
|
||||
ToolTip="Account" />
|
||||
<ComboBox
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
usp:OrderModificationControl.IsDirty="{Binding time_in_force_dirty, FallbackValue={u:False}}"
|
||||
IsEnabled="{Binding can_change_time_in_force, FallbackValue={u:True}}"
|
||||
ItemsSource="{Binding properties_vm.time_in_forces}"
|
||||
Margin="0,2"
|
||||
SelectedItem="{Binding properties_vm.selected_time_in_force}"
|
||||
/>
|
||||
ToolTip="Time In Force" />
|
||||
<uag:OrderDetailsPopupControl
|
||||
Grid.Row="2"
|
||||
Margin="0,2"
|
||||
Grid.Row="4"
|
||||
DataContext="{Binding properties_vm}"
|
||||
usp:OrderModificationControl.IsDirty="{Binding DataContext.order_type_dirty, RelativeSource={RelativeSource AncestorType=usp:OrderModificationControl}, FallbackValue={u:False}}"
|
||||
HasEditableProperties="{Binding can_expand, FallbackValue={u:False}}"
|
||||
@@ -207,7 +218,7 @@
|
||||
IsOpen="{Binding is_expanded, FallbackValue={u:False}}"
|
||||
ItemsSource="{Binding algo_definitions_view}"
|
||||
SelectedItem="{Binding selected_algo_definition}"
|
||||
>
|
||||
ToolTip="Order/Algo Type">
|
||||
<uag:OrderDetailsPopupControl.Style>
|
||||
<Style TargetType="uag:OrderDetailsPopupControl" BasedOn="{StaticResource {x:Type uag:OrderDetailsPopupControl}}">
|
||||
<Style.Triggers>
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace mbts.spark {
|
||||
new PropertyValidation<AccountTuple?>(
|
||||
a => (a.HasValue && !string.IsNullOrWhiteSpace(a.Value.name) && accounts.Contains(a.Value), "Please select an account"))
|
||||
});
|
||||
attach(_accounts, accounts_src.Select(accs => accs.Values.ToList()), inline_account_src);
|
||||
attach(_accounts, accounts_src.Select(accs => accs.Values.ToListSorted()), inline_account_src);
|
||||
|
||||
// time in force
|
||||
_time_in_force = new SelectorProperty<TimeInForce>(
|
||||
|
||||
@@ -398,7 +398,7 @@
|
||||
$"Expected rounded qty to be real, non-negative. " +
|
||||
$"qty: {qty}; rounded_qty: {rounded_qty}; display: {display})");
|
||||
var iqty = Math.Floor(rounded_qty);
|
||||
return ((uint)iqty, new() { Fraction = Math.Round(rounded_qty - iqty, 2) });
|
||||
return ((uint)iqty, new() { Fraction = Math.Round(rounded_qty - iqty, 6) });
|
||||
}
|
||||
|
||||
private CustomOrderDefinitionImmutable? _create_custom_def_for_stop_and_stop_limit(OrderRecordImmutable order, ref double stop_price) {
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace mbts.ui {
|
||||
|
||||
public static BTSColor atm_background => BTSCoreColors.gray_dark;
|
||||
|
||||
public static BTSColor session_low => 0xff59596b;
|
||||
public static BTSColor session_low => 0xff8f5924;
|
||||
public static BTSColor session_high => 0xff239b00;
|
||||
public static BTSColor session_range_fill => 0x1a000000;
|
||||
|
||||
@@ -984,7 +984,7 @@ namespace mbts.ui {
|
||||
public static BTSColor alternate_column_foreground => UI.foreground_standout;
|
||||
public static BTSColor bid_line => BTSCoreColors.blue_light_dark_saturated;
|
||||
public static BTSColor ask_line => BTSCoreColors.red_light_dark_saturated;
|
||||
public static BTSColor mid_line => 0x40000000;
|
||||
public static BTSColor mid_line => 0xffa4a4a4;
|
||||
|
||||
public static BTSColor default_column_background => 0xff141420;//0xff28283b;
|
||||
public static BTSColor alternate_column_background => 0xff141420;// 0xff494957;
|
||||
|
||||
@@ -5,7 +5,8 @@ using mbts.ui.utility.text;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
@@ -13,7 +14,7 @@ using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.proto {
|
||||
[ProtoContract]
|
||||
class FontParamsProto : IEquatable<FontParamsProto> {
|
||||
class FontParamsProto : IProtobuf<FontParamsProto> {
|
||||
[ProtoMember(1)]
|
||||
public TypefaceProto typeface { get; set; }
|
||||
[ProtoMember(2)]
|
||||
@@ -24,6 +25,27 @@ namespace bts.proto {
|
||||
public string font_family => typeface.font_family;
|
||||
public int font_weight => typeface.font_weight;
|
||||
|
||||
public FontParamsProto With(TypefaceProto? typeface = null, double? font_size = null, uint? font_color = null, bool in_place = false) {
|
||||
var rv = in_place ? this : Clone();
|
||||
if (typeface.HasValue) {
|
||||
rv.typeface = typeface.Value;
|
||||
}
|
||||
if (font_size.HasValue) {
|
||||
rv.font_size = font_size.Value;
|
||||
}
|
||||
if (font_color.HasValue) {
|
||||
rv.font_color = font_color.Value;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public FontParamsProto Clone() =>
|
||||
new() {
|
||||
typeface = typeface.Clone(),
|
||||
font_size = font_size,
|
||||
font_color = font_color,
|
||||
};
|
||||
|
||||
public bool Equals(FontParamsProto other) =>
|
||||
other != null &&
|
||||
typeface == other.typeface &&
|
||||
|
||||
@@ -4,14 +4,15 @@ using mbts.ui.utility.text;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using Spark.Core.Protobuf;
|
||||
|
||||
using System.Windows;
|
||||
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace bts.proto {
|
||||
[ProtoContract]
|
||||
readonly struct TypefaceProto : IEquatable<TypefaceProto> {
|
||||
readonly struct TypefaceProto : IProtobuf<TypefaceProto> {
|
||||
static readonly int _type_hash = typeof(TypefaceProto).GetHashCode();
|
||||
|
||||
[ProtoMember(1)]
|
||||
@@ -30,6 +31,9 @@ namespace bts.proto {
|
||||
font_family ?? this.font_family,
|
||||
font_weight ?? this.font_weight);
|
||||
|
||||
public TypefaceProto Clone() =>
|
||||
new(font_family.ToString(), font_weight);
|
||||
|
||||
public bool Equals(TypefaceProto other) =>
|
||||
font_family == other.font_family &&
|
||||
font_weight == other.font_weight;
|
||||
|
||||
@@ -379,8 +379,15 @@ namespace mbts.ui.controls {
|
||||
|
||||
class BadgeTextLinesConverter : IMultiValueConverter {
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (ConvertUtils.try_convert_args(values, out Side side, out uint quantity, out uint filled, out mbts.spark.LadderSettings.BadgeStyle style))
|
||||
return BadgeRender.format(side, quantity, filled, style);
|
||||
if (ConvertUtils.TryConvertArgs(
|
||||
values,
|
||||
out Side side,
|
||||
out uint quantity,
|
||||
out uint filled,
|
||||
out double price,
|
||||
out LadderSettings.BadgeStyle style)) {
|
||||
return BadgeRender.format(side, quantity, filled, price, style);
|
||||
}
|
||||
else return new string[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,23 @@ namespace mbts.ui {
|
||||
}
|
||||
|
||||
public static class AutoFocusTargetProvider {
|
||||
public static IAutoFocusTargetProvider<T> TypeProvider<T>(FrameworkElement root, bool focus, bool focus_keyboard, bool release_keyboard, bool capture_mouse, bool release_mouse) where T : FrameworkElement =>
|
||||
new AnonymousProvider<T>(() => root.descendants_of_type<T>().FirstOrDefault(), focus, focus_keyboard, release_keyboard, capture_mouse, release_mouse);
|
||||
|
||||
public static IAutoFocusTargetProvider TypeProvider(Type type, FrameworkElement root, bool focus, bool focus_keyboard, bool release_keybobard, bool capture_mouse, bool release_mouse) =>
|
||||
public static IAutoFocusTargetProvider TypeProvider(
|
||||
Type type,
|
||||
FrameworkElement root,
|
||||
bool focus = true,
|
||||
bool focus_keyboard = false,
|
||||
bool release_keyboard = true,
|
||||
bool capture_mouse = false,
|
||||
bool release_mouse = true) =>
|
||||
new AnonymousProvider<FrameworkElement>(
|
||||
() => root.visual_children_where(el => type.IsAssignableFrom(el.GetType())).Cast<FrameworkElement>().FirstOrDefault(),
|
||||
focus, focus_keyboard, release_keybobard, capture_mouse, release_mouse);
|
||||
() => root.visual_children_where(el => type.IsAssignableFrom(el.GetType()))
|
||||
.Cast<FrameworkElement>()
|
||||
.FirstOrDefault(),
|
||||
focus,
|
||||
focus_keyboard,
|
||||
release_keyboard,
|
||||
capture_mouse,
|
||||
release_mouse);
|
||||
|
||||
private class AnonymousProvider<T> : IAutoFocusTargetProvider<T> where T : FrameworkElement {
|
||||
readonly Func<T> _get_target;
|
||||
|
||||
@@ -473,7 +473,7 @@ namespace mbts.spark.filters {
|
||||
// Exchange
|
||||
public static IDataGridColumnPersistentFilter<TRow, Venue[]> exchange<TRow>(
|
||||
Func<TRow, Venue> get_row_exchange, string? prop_name = null) =>
|
||||
Create(EnumHelper.get_values_except(Venue.Unset).ToList(), get_row_exchange, prop_name);
|
||||
Create(EnumHelper.get_values_except(Venue.Unset).ToListSorted(e => e.ToString()), get_row_exchange, prop_name);
|
||||
|
||||
// Symbol
|
||||
public static IDataGridColumnPersistentFilter<TRow, string[]> symbol<TRow>(
|
||||
|
||||
@@ -12,7 +12,10 @@ namespace mbts.spark.filters {
|
||||
str = str.Trim();
|
||||
if (str == string.Empty)
|
||||
return null;
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
try {
|
||||
return new Regex(str, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
} catch (RegexParseException) { }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
class RegexFilterVM<TRow> : FilterVMBase<TRow, string, string> {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
@@ -21,7 +22,7 @@
|
||||
<GroupBox Header="Font Family" Height="{Binding ActualHeight, ElementName=_panel_right}">
|
||||
<ListBox
|
||||
x:Name="_font_family_list_box"
|
||||
Background="White"
|
||||
Background="{u:BTSBrush UI.ListBox.normal_background}"
|
||||
ItemsSource="{Binding FontFamilies, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
SelectedItem="{Binding SelectedFontFamily, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
>
|
||||
@@ -40,7 +41,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<GroupBox Header="Font Weight">
|
||||
<ListBox
|
||||
Background="White"
|
||||
Background="{u:BTSBrush UI.ListBox.normal_background}"
|
||||
ItemsSource="{Binding FontWeights, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
SelectedItem="{Binding SelectedFontWeight, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
>
|
||||
@@ -73,7 +74,7 @@
|
||||
FontFamily="{Binding SelectedFontFamily, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
FontSize="{Binding SelectedFontSize, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
FontWeight="{Binding SelectedFontWeight, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}}"
|
||||
Foreground="{Binding SelectedFontColor, Mode=OneWay, RelativeSource={RelativeSource AncestorType=c:FontPicker}, Converter={StaticResource ColorToBrushConverter}}"
|
||||
Foreground="{u:BTSBrush UI.default_text}"
|
||||
TextWrapping="Wrap"
|
||||
>
|
||||
<TextBlock.Inlines>
|
||||
|
||||
@@ -12,8 +12,12 @@
|
||||
x:Name="order_type"
|
||||
Text="{Binding algo_root.order_type, Converter={StaticResource OrderTypeDisplayTextConverter}, StringFormat='{}{0}: ', Mode=OneWay}"
|
||||
FontWeight="Bold"
|
||||
/>
|
||||
<TextBlock x:Name="display_name" Text="{Binding name, Mode=OneWay}" FontWeight="Normal"/>
|
||||
Foreground="{u:BTSBrush UI.default_text}" />
|
||||
<TextBlock
|
||||
x:Name="display_name"
|
||||
Text="{Binding name, Mode=OneWay}"
|
||||
FontWeight="Normal"
|
||||
Foreground="{u:BTSBrush UI.default_text}" />
|
||||
</StackPanel>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Value="{u:True}">
|
||||
|
||||
@@ -87,10 +87,10 @@
|
||||
</c:TickUpDown>
|
||||
</ControlTemplate>
|
||||
<Style TargetType="usp:PriceEntryControl">
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.input_background}" />
|
||||
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.input_text}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Template" Value="{StaticResource PriceEntryTemplate}"/>
|
||||
</Style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<Style TargetType="{x:Type c:TickUpDown}">
|
||||
<Setter Property="Background" Value="{StaticResource TextBox.Static.Background.Brush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Height" Value="20" />
|
||||
@@ -75,7 +75,8 @@
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
|
||||
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
@@ -133,7 +134,8 @@
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource TextBox.MouseOver.Border.Brush}"/>
|
||||
<Setter TargetName="Bd" Property="Background" Value="{u:BTSBrush UI.Button.mouseover_background}"/>
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="{u:BTSBrush UI.Button.mouseover_border}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
@@ -145,7 +147,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -188,7 +190,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -230,7 +232,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -272,7 +274,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -310,7 +312,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -348,7 +350,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -384,7 +386,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -428,7 +430,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -466,7 +468,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -548,7 +550,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
@@ -586,7 +588,7 @@
|
||||
<Setter Property="Background" Value="{u:BTSColor UI.TextBox.default_background}"/>
|
||||
<Setter Property="Foreground" Value="{u:BTSColor UI.TextBox.default_foreground}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border.Brush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
|
||||
@@ -1,248 +1,264 @@
|
||||
using bts.utils;
|
||||
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.ViewModel.Commands;
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui {
|
||||
static class Behavior {
|
||||
#region CloseWindow
|
||||
public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached("CloseWindow", typeof(bool), typeof(Behavior),
|
||||
new PropertyMetadata(false, (d, dpc_args) => {
|
||||
var button = d as Button;
|
||||
if (button != null && (bool)dpc_args.NewValue) {
|
||||
RoutedEventHandler close_window = (sender, e) => {
|
||||
var window = button.find_ancestor_of_type<Window>();
|
||||
if (window != null)
|
||||
window.Close();
|
||||
};
|
||||
button.Click += close_window;
|
||||
IDisposable dispose = Disposable.Create(() => {
|
||||
button.Click -= close_window;
|
||||
close_window = null;
|
||||
});
|
||||
IDisposable early_dispose = button.ObserveProperty(CloseWindowProperty, (sender, e) => {
|
||||
if (!GetCloseWindow(button))
|
||||
Disposable.dispose(ref dispose);
|
||||
});
|
||||
DisposeExtensions.attach_to_disposer(button, dispose);
|
||||
DisposeExtensions.attach_to_disposer(button, early_dispose);
|
||||
}
|
||||
}));
|
||||
public static bool GetCloseWindow(Button obj) { return (bool)obj.GetValue(CloseWindowProperty); }
|
||||
public static void SetCloseWindow(Button obj, bool value) { obj.SetValue(CloseWindowProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AnimatedScrollViewerVerticalOffset
|
||||
static double GetAnimatedScrollViewerVerticalOffset(DependencyObject obj) =>
|
||||
(double)obj.GetValue(AnimatedScrollViewerVerticalOffsetProperty);
|
||||
|
||||
public static void SetAnimatedScrollViewerVerticalOffset(DependencyObject obj, double value) =>
|
||||
obj.SetValue(AnimatedScrollViewerVerticalOffsetProperty, value);
|
||||
|
||||
public static readonly DependencyProperty AnimatedScrollViewerVerticalOffsetProperty =
|
||||
DependencyProperty.RegisterAttached("AnimatedScrollViewerVerticalOffset", typeof(double), typeof(Behavior), new PropertyMetadata(0.0, scroll_changed));
|
||||
|
||||
static void scroll_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
ScrollViewer target = d as ScrollViewer;
|
||||
if (target != null) {
|
||||
target.ScrollToVerticalOffset((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AutoFocusTargetProvider
|
||||
public static readonly DependencyProperty AutoFocusTargetProviderProperty = DependencyProperty.RegisterAttached("AutoFocusTargetProvider", typeof(bool),
|
||||
typeof(Behavior), new PropertyMetadata(OnAutoFocusTargetProviderChanged));
|
||||
|
||||
static void OnAutoFocusTargetProviderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (e.NewValue is IAutoFocusTargetProvider aftp) {
|
||||
var el = ExceptionAssert.Argument.Is<FrameworkElement>(d, nameof(d));
|
||||
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, new AutoFocusTargetProviderLifeDependency(el, aftp));
|
||||
} else
|
||||
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, null);
|
||||
}
|
||||
|
||||
public static bool GetAutoFocusTargetProvider(DependencyObject d) => (bool)d.GetValue(AutoFocusTargetProviderProperty);
|
||||
public static void SetAutoFocusTargetProvider(DependencyObject d, bool value) => d.SetValue(AutoFocusTargetProviderProperty, value);
|
||||
#endregion
|
||||
|
||||
#region AutoFocusTargetProviderLifeDependency (ReadOnly)
|
||||
static readonly DependencyPropertyKey AutoFocusTargetProviderLifeDependencyPropertyKey = DependencyProperty.RegisterAttachedReadOnly("AutoFocusTargetProviderLifeDependency",
|
||||
typeof(FrameworkElementLifeDependency), typeof(Behavior), new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOldInitializeNew));
|
||||
|
||||
public static readonly DependencyProperty AutoFocusTargetProviderLifeDependencyProperty = AutoFocusTargetProviderLifeDependencyPropertyKey.DependencyProperty;
|
||||
|
||||
public static FrameworkElementLifeDependency GetAutoFocusTargetProviderLifeDependency(DependencyObject d) => d.GetValue(AutoFocusTargetProviderLifeDependencyProperty) as FrameworkElementLifeDependency;
|
||||
public static void SetAutoFocusTargetProviderLifeDependency(DependencyObject d, FrameworkElementLifeDependency value) => d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, value);
|
||||
|
||||
class AutoFocusTargetProviderLifeDependency : FrameworkElementLifeDependency {
|
||||
readonly IAutoFocusTargetProvider _focus_target_provider;
|
||||
|
||||
public AutoFocusTargetProviderLifeDependency(FrameworkElement root, IAutoFocusTargetProvider focus_target_provider) : base(root) =>
|
||||
_focus_target_provider = focus_target_provider;
|
||||
|
||||
protected override void OnLoaded() {
|
||||
var el = _focus_target_provider.Target;
|
||||
if (el != null) {
|
||||
if (_focus_target_provider.Focus)
|
||||
el.Focus();
|
||||
if (_focus_target_provider.FocusKeyboard)
|
||||
Keyboard.Focus(el);
|
||||
if (_focus_target_provider.CaptureMouse)
|
||||
el.CaptureMouse();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUnloaded() {
|
||||
var el = _focus_target_provider.Target;
|
||||
if (el != null) {
|
||||
if (_focus_target_provider.ReleaseKeyboard) {
|
||||
ReferenceEquals(Keyboard.FocusedElement, el);
|
||||
Keyboard.ClearFocus();
|
||||
}
|
||||
if (_focus_target_provider.ReleaseMouse)
|
||||
el.ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EatMouseDown
|
||||
public static readonly DependencyProperty EatMouseDownProperty =
|
||||
DependencyProperty.RegisterAttached("EatMouseDown", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnEatMouseDownChanged));
|
||||
|
||||
static void OnEatMouseDownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var el = (FrameworkElement)d;
|
||||
if ((bool)e.NewValue) el.MouseDown += _eat_mouse_down;
|
||||
else el.MouseDown -= _eat_mouse_down;
|
||||
}
|
||||
|
||||
static void _eat_mouse_down(object sender, MouseButtonEventArgs e) =>
|
||||
e.Handled = true;
|
||||
|
||||
public static bool GetEatMouseDown(DependencyObject d) => (bool)d.GetValue(EatMouseDownProperty);
|
||||
public static void SetEatMouseDown(DependencyObject d, bool value) => d.SetValue(EatMouseDownProperty, value);
|
||||
#endregion
|
||||
|
||||
#region DisablePaste
|
||||
public static readonly DependencyProperty DisablePasteProperty = DependencyProperty.RegisterAttached(
|
||||
"DisablePaste", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnDisablePasteChanged));
|
||||
|
||||
static void OnDisablePasteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if ((bool)e.NewValue)
|
||||
DataObject.AddPastingHandler(d, _disable_paste_pasting_handler);
|
||||
else DataObject.RemovePastingHandler(d, _disable_paste_pasting_handler);
|
||||
}
|
||||
static void _disable_paste_pasting_handler(object sender, DataObjectPastingEventArgs e) => e.CancelCommand();
|
||||
|
||||
public static bool GetDisablePaste(DependencyObject d) => (bool)d.GetValue(DisablePasteProperty);
|
||||
public static void SetDisablePaste(DependencyObject d, bool value) => d.SetValue(DisablePasteProperty, value);
|
||||
#endregion
|
||||
|
||||
public static readonly DependencyProperty BindingUpdateGroupsProperty =
|
||||
DependencyProperty.RegisterAttached("BindingUpdateGroups", typeof(BindingUpdateGroupCollection), typeof(Behavior), new PropertyMetadata(null));
|
||||
|
||||
public static BindingUpdateGroupCollection GetBindingUpdateGroups(DependencyObject d) => d.GetValue(BindingUpdateGroupsProperty) as BindingUpdateGroupCollection;
|
||||
public static void SetBindingUpdateGroups(DependencyObject d, BindingUpdateGroupCollection value) => d.SetValue(BindingUpdateGroupsProperty, value);
|
||||
|
||||
#region AutoRaiseCanExecuteChanged
|
||||
public static readonly DependencyProperty AutoRaiseCanExecuteChangedProperty =
|
||||
DependencyProperty.RegisterAttached("AutoRaiseCanExecuteChanged", typeof(bool), typeof(Behavior),
|
||||
new PropertyMetadata(false, (d, e) => {
|
||||
if ((bool)e.NewValue) {
|
||||
BindingOperations.SetBinding(d, AutoCommandProperty, new Binding(nameof(ButtonBase.Command)) { Source = d, Mode = BindingMode.OneWay });
|
||||
BindingOperations.SetBinding(d, AutoCommandParameterProperty, new Binding(nameof(ButtonBase.CommandParameter)) { Source = d, Mode = BindingMode.OneWay });
|
||||
} else {
|
||||
BindingOperations.ClearBinding(d, AutoCommandProperty);
|
||||
BindingOperations.ClearBinding(d, AutoCommandParameterProperty);
|
||||
}
|
||||
}));
|
||||
|
||||
public static bool GetAutoRaiseCanExecuteChanged(DependencyObject d) => (bool)d.GetValue(AutoRaiseCanExecuteChangedProperty);
|
||||
public static void SetAutoRaiseCanExecuteChanged(DependencyObject d, bool value) => d.SetValue(AutoRaiseCanExecuteChangedProperty, value);
|
||||
|
||||
/// Do not bind directly to <see cref="AutoCommandProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
|
||||
public static readonly DependencyProperty AutoCommandProperty =
|
||||
DependencyProperty.RegisterAttached("AutoCommand", typeof(ICommand), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
|
||||
|
||||
public static ICommand GetAutoCommand(DependencyObject d) => d.GetValue(AutoCommandProperty) as ICommand;
|
||||
public static void SetAutoCommand(DependencyObject d, ICommand value) => d.SetValue(AutoCommandProperty, value);
|
||||
|
||||
/// Do not bind directly to <see cref="AutoCommandParameterProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
|
||||
public static readonly DependencyProperty AutoCommandParameterProperty =
|
||||
DependencyProperty.RegisterAttached("AutoCommandParameter", typeof(object), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
|
||||
|
||||
public static object GetAutoCommandParameter(DependencyObject d) => d.GetValue(AutoCommandParameterProperty);
|
||||
public static void SetAutoCommandParameter(DependencyObject d, object value) => d.SetValue(AutoCommandParameterProperty, value);
|
||||
|
||||
static void _try_raise_can_execute(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
|
||||
(d.GetValue(ButtonBase.CommandProperty) as IRaiseCanExecuteChangedCommand)?.RaiseCanExecuteChanged();
|
||||
#endregion
|
||||
|
||||
#region FocusOnLoaded
|
||||
public static readonly DependencyProperty FocusOnLoadedProperty =
|
||||
DependencyProperty.RegisterAttached("FocusOnLoaded", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnFocusOnLoadedChanged));
|
||||
|
||||
static void OnFocusOnLoadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = (FrameworkElement)d;
|
||||
if ((bool)e.NewValue) target.Loaded += _target_Loaded;
|
||||
else target.Loaded -= _target_Loaded;
|
||||
}
|
||||
|
||||
static void _target_Loaded(object sender, RoutedEventArgs e) {
|
||||
var target = (FrameworkElement)sender;
|
||||
Keyboard.Focus(target);
|
||||
}
|
||||
public static bool GetFocusOnLoaded(DependencyObject d) => (bool)d.GetValue(FocusOnLoadedProperty);
|
||||
public static void SetFocusOnLoaded(DependencyObject d, bool value) => d.SetValue(FocusOnLoadedProperty, value);
|
||||
#endregion
|
||||
|
||||
#region MirrorLayout
|
||||
static readonly DependencyProperty[] _layout_props = new[] {
|
||||
FrameworkElement.HeightProperty,
|
||||
FrameworkElement.WidthProperty,
|
||||
FrameworkElement.MarginProperty,
|
||||
Control.PaddingProperty,
|
||||
FrameworkElement.HorizontalAlignmentProperty,
|
||||
Control.HorizontalContentAlignmentProperty,
|
||||
FrameworkElement.VerticalAlignmentProperty,
|
||||
Control.VerticalContentAlignmentProperty
|
||||
};
|
||||
|
||||
public static readonly DependencyProperty MirrorLayoutForProperty =
|
||||
DependencyProperty.RegisterAttached("MirrorLayoutFor", typeof(FrameworkElement), typeof(Behavior),
|
||||
new PropertyMetadata(OnMirrorLayoutForChanged));
|
||||
|
||||
static void OnMirrorLayoutForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = new LazyCast<FrameworkElement, DependencyObject>(d);
|
||||
if (e.OldValue is FrameworkElement old_src) {
|
||||
target.value.clear_bindings(_layout_props);
|
||||
}
|
||||
if (e.NewValue is FrameworkElement new_src) {
|
||||
target.value.bind(FrameworkElement.WidthProperty, new_src, FrameworkElement.ActualWidthProperty);
|
||||
target.value.bind(FrameworkElement.HeightProperty, new_src, FrameworkElement.ActualHeightProperty);
|
||||
target.value.bind(FrameworkElement.MarginProperty, new_src, FrameworkElement.MarginProperty);
|
||||
target.value.bind(FrameworkElement.HorizontalAlignmentProperty, new_src, FrameworkElement.HorizontalAlignmentProperty);
|
||||
target.value.bind(FrameworkElement.VerticalAlignmentProperty, new_src, FrameworkElement.VerticalAlignmentProperty);
|
||||
if (target.value is Control && new_src is Control) {
|
||||
target.value.bind(Control.PaddingProperty, new_src, Control.PaddingProperty);
|
||||
target.value.bind(Control.HorizontalContentAlignmentProperty, new_src, Control.HorizontalContentAlignmentProperty);
|
||||
target.value.bind(Control.VerticalContentAlignmentProperty, new_src, Control.VerticalContentAlignmentProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FrameworkElement GetMirrorLayoutFor(DependencyObject d) => d.GetValue(MirrorLayoutForProperty) as FrameworkElement;
|
||||
public static void SetMirrorLayoutFor(DependencyObject d, FrameworkElement value) => d.SetValue(MirrorLayoutForProperty, value);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
using bts.utils;
|
||||
|
||||
using mbts.utils;
|
||||
|
||||
using Spark.Core.Disposables;
|
||||
using Spark.ViewModel.Commands;
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui {
|
||||
static class Behavior {
|
||||
#region CloseWindow
|
||||
public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached("CloseWindow", typeof(bool), typeof(Behavior),
|
||||
new PropertyMetadata(false, (d, dpc_args) => {
|
||||
var button = d as Button;
|
||||
if (button != null && (bool)dpc_args.NewValue) {
|
||||
RoutedEventHandler close_window = (sender, e) => {
|
||||
var window = button.find_ancestor_of_type<Window>();
|
||||
if (window != null)
|
||||
window.Close();
|
||||
};
|
||||
button.Click += close_window;
|
||||
IDisposable dispose = Disposable.Create(() => {
|
||||
button.Click -= close_window;
|
||||
close_window = null;
|
||||
});
|
||||
IDisposable early_dispose = button.ObserveProperty(CloseWindowProperty, (sender, e) => {
|
||||
if (!GetCloseWindow(button))
|
||||
Disposable.dispose(ref dispose);
|
||||
});
|
||||
DisposeExtensions.attach_to_disposer(button, dispose);
|
||||
DisposeExtensions.attach_to_disposer(button, early_dispose);
|
||||
}
|
||||
}));
|
||||
public static bool GetCloseWindow(Button obj) { return (bool)obj.GetValue(CloseWindowProperty); }
|
||||
public static void SetCloseWindow(Button obj, bool value) { obj.SetValue(CloseWindowProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region AnimatedScrollViewerVerticalOffset
|
||||
static double GetAnimatedScrollViewerVerticalOffset(DependencyObject obj) =>
|
||||
(double)obj.GetValue(AnimatedScrollViewerVerticalOffsetProperty);
|
||||
|
||||
public static void SetAnimatedScrollViewerVerticalOffset(DependencyObject obj, double value) =>
|
||||
obj.SetValue(AnimatedScrollViewerVerticalOffsetProperty, value);
|
||||
|
||||
public static readonly DependencyProperty AnimatedScrollViewerVerticalOffsetProperty =
|
||||
DependencyProperty.RegisterAttached("AnimatedScrollViewerVerticalOffset", typeof(double), typeof(Behavior), new PropertyMetadata(0.0, scroll_changed));
|
||||
|
||||
static void scroll_changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
ScrollViewer target = d as ScrollViewer;
|
||||
if (target != null) {
|
||||
target.ScrollToVerticalOffset((double)e.NewValue);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AutoFocusTargetProvider
|
||||
public static readonly DependencyProperty AutoFocusTargetProviderProperty = DependencyProperty.RegisterAttached(
|
||||
"AutoFocusTargetProvider", typeof(IAutoFocusTargetProvider), typeof(Behavior), new PropertyMetadata(OnAutoFocusTargetProviderChanged));
|
||||
|
||||
static void OnAutoFocusTargetProviderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (e.NewValue is IAutoFocusTargetProvider aftp) {
|
||||
var el = ExceptionAssert.Argument.Is<FrameworkElement>(d, nameof(d));
|
||||
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, new AutoFocusTargetProviderLifeDependency(el, aftp));
|
||||
} else
|
||||
d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, null);
|
||||
}
|
||||
|
||||
public static IAutoFocusTargetProvider GetAutoFocusTargetProvider(DependencyObject d) =>
|
||||
d.GetValue(AutoFocusTargetProviderProperty) as IAutoFocusTargetProvider;
|
||||
|
||||
public static void SetAutoFocusTargetProvider(DependencyObject d, IAutoFocusTargetProvider value) =>
|
||||
d.SetValue(AutoFocusTargetProviderProperty, value);
|
||||
#endregion
|
||||
|
||||
#region AutoFocusTargetProviderLifeDependency (ReadOnly)
|
||||
static readonly DependencyPropertyKey AutoFocusTargetProviderLifeDependencyPropertyKey = DependencyProperty.RegisterAttachedReadOnly("AutoFocusTargetProviderLifeDependency",
|
||||
typeof(FrameworkElementLifeDependency), typeof(Behavior), new PropertyMetadata(FrameworkElementLifeDependencyHelper.DisposeOldInitializeNew));
|
||||
|
||||
public static readonly DependencyProperty AutoFocusTargetProviderLifeDependencyProperty = AutoFocusTargetProviderLifeDependencyPropertyKey.DependencyProperty;
|
||||
|
||||
public static FrameworkElementLifeDependency GetAutoFocusTargetProviderLifeDependency(DependencyObject d) => d.GetValue(AutoFocusTargetProviderLifeDependencyProperty) as FrameworkElementLifeDependency;
|
||||
public static void SetAutoFocusTargetProviderLifeDependency(DependencyObject d, FrameworkElementLifeDependency value) => d.SetValue(AutoFocusTargetProviderLifeDependencyPropertyKey, value);
|
||||
|
||||
class AutoFocusTargetProviderLifeDependency : FrameworkElementLifeDependency {
|
||||
readonly IAutoFocusTargetProvider _focus_target_provider;
|
||||
|
||||
public AutoFocusTargetProviderLifeDependency(FrameworkElement root, IAutoFocusTargetProvider focus_target_provider) : base(root) =>
|
||||
_focus_target_provider = focus_target_provider;
|
||||
|
||||
protected override void OnLoaded() {
|
||||
var el = _focus_target_provider.Target;
|
||||
if (el != null) {
|
||||
if (_focus_target_provider.Focus) {
|
||||
var fs = FocusManager.GetFocusScope(el);
|
||||
if (fs is not null) {
|
||||
FocusManager.SetFocusedElement(fs, el);
|
||||
if (fs is UIElement fsUiEl) {
|
||||
fsUiEl.Focus();
|
||||
} else if (fs is IInputElement fsInputEl) {
|
||||
Keyboard.Focus(fsInputEl);
|
||||
} else {
|
||||
el.Focus();
|
||||
}
|
||||
} else {
|
||||
el.Focus();
|
||||
}
|
||||
}
|
||||
if (_focus_target_provider.FocusKeyboard)
|
||||
Keyboard.Focus(el);
|
||||
if (_focus_target_provider.CaptureMouse)
|
||||
el.CaptureMouse();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUnloaded() {
|
||||
var el = _focus_target_provider.Target;
|
||||
if (el != null) {
|
||||
if (_focus_target_provider.ReleaseKeyboard) {
|
||||
ReferenceEquals(Keyboard.FocusedElement, el);
|
||||
Keyboard.ClearFocus();
|
||||
}
|
||||
if (_focus_target_provider.ReleaseMouse)
|
||||
el.ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EatMouseDown
|
||||
public static readonly DependencyProperty EatMouseDownProperty =
|
||||
DependencyProperty.RegisterAttached("EatMouseDown", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnEatMouseDownChanged));
|
||||
|
||||
static void OnEatMouseDownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var el = (FrameworkElement)d;
|
||||
if ((bool)e.NewValue) el.MouseDown += _eat_mouse_down;
|
||||
else el.MouseDown -= _eat_mouse_down;
|
||||
}
|
||||
|
||||
static void _eat_mouse_down(object sender, MouseButtonEventArgs e) =>
|
||||
e.Handled = true;
|
||||
|
||||
public static bool GetEatMouseDown(DependencyObject d) => (bool)d.GetValue(EatMouseDownProperty);
|
||||
public static void SetEatMouseDown(DependencyObject d, bool value) => d.SetValue(EatMouseDownProperty, value);
|
||||
#endregion
|
||||
|
||||
#region DisablePaste
|
||||
public static readonly DependencyProperty DisablePasteProperty = DependencyProperty.RegisterAttached(
|
||||
"DisablePaste", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnDisablePasteChanged));
|
||||
|
||||
static void OnDisablePasteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if ((bool)e.NewValue)
|
||||
DataObject.AddPastingHandler(d, _disable_paste_pasting_handler);
|
||||
else DataObject.RemovePastingHandler(d, _disable_paste_pasting_handler);
|
||||
}
|
||||
static void _disable_paste_pasting_handler(object sender, DataObjectPastingEventArgs e) => e.CancelCommand();
|
||||
|
||||
public static bool GetDisablePaste(DependencyObject d) => (bool)d.GetValue(DisablePasteProperty);
|
||||
public static void SetDisablePaste(DependencyObject d, bool value) => d.SetValue(DisablePasteProperty, value);
|
||||
#endregion
|
||||
|
||||
public static readonly DependencyProperty BindingUpdateGroupsProperty =
|
||||
DependencyProperty.RegisterAttached("BindingUpdateGroups", typeof(BindingUpdateGroupCollection), typeof(Behavior), new PropertyMetadata(null));
|
||||
|
||||
public static BindingUpdateGroupCollection GetBindingUpdateGroups(DependencyObject d) => d.GetValue(BindingUpdateGroupsProperty) as BindingUpdateGroupCollection;
|
||||
public static void SetBindingUpdateGroups(DependencyObject d, BindingUpdateGroupCollection value) => d.SetValue(BindingUpdateGroupsProperty, value);
|
||||
|
||||
#region AutoRaiseCanExecuteChanged
|
||||
public static readonly DependencyProperty AutoRaiseCanExecuteChangedProperty =
|
||||
DependencyProperty.RegisterAttached("AutoRaiseCanExecuteChanged", typeof(bool), typeof(Behavior),
|
||||
new PropertyMetadata(false, (d, e) => {
|
||||
if ((bool)e.NewValue) {
|
||||
BindingOperations.SetBinding(d, AutoCommandProperty, new Binding(nameof(ButtonBase.Command)) { Source = d, Mode = BindingMode.OneWay });
|
||||
BindingOperations.SetBinding(d, AutoCommandParameterProperty, new Binding(nameof(ButtonBase.CommandParameter)) { Source = d, Mode = BindingMode.OneWay });
|
||||
} else {
|
||||
BindingOperations.ClearBinding(d, AutoCommandProperty);
|
||||
BindingOperations.ClearBinding(d, AutoCommandParameterProperty);
|
||||
}
|
||||
}));
|
||||
|
||||
public static bool GetAutoRaiseCanExecuteChanged(DependencyObject d) => (bool)d.GetValue(AutoRaiseCanExecuteChangedProperty);
|
||||
public static void SetAutoRaiseCanExecuteChanged(DependencyObject d, bool value) => d.SetValue(AutoRaiseCanExecuteChangedProperty, value);
|
||||
|
||||
/// Do not bind directly to <see cref="AutoCommandProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
|
||||
public static readonly DependencyProperty AutoCommandProperty =
|
||||
DependencyProperty.RegisterAttached("AutoCommand", typeof(ICommand), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
|
||||
|
||||
public static ICommand GetAutoCommand(DependencyObject d) => d.GetValue(AutoCommandProperty) as ICommand;
|
||||
public static void SetAutoCommand(DependencyObject d, ICommand value) => d.SetValue(AutoCommandProperty, value);
|
||||
|
||||
/// Do not bind directly to <see cref="AutoCommandParameterProperty"/> (used internally by <see cref="AutoRaiseCanExecuteChangedProperty"/>
|
||||
public static readonly DependencyProperty AutoCommandParameterProperty =
|
||||
DependencyProperty.RegisterAttached("AutoCommandParameter", typeof(object), typeof(Behavior), new PropertyMetadata(null, _try_raise_can_execute));
|
||||
|
||||
public static object GetAutoCommandParameter(DependencyObject d) => d.GetValue(AutoCommandParameterProperty);
|
||||
public static void SetAutoCommandParameter(DependencyObject d, object value) => d.SetValue(AutoCommandParameterProperty, value);
|
||||
|
||||
static void _try_raise_can_execute(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
|
||||
(d.GetValue(ButtonBase.CommandProperty) as IRaiseCanExecuteChangedCommand)?.RaiseCanExecuteChanged();
|
||||
#endregion
|
||||
|
||||
#region FocusOnLoaded
|
||||
public static readonly DependencyProperty FocusOnLoadedProperty =
|
||||
DependencyProperty.RegisterAttached("FocusOnLoaded", typeof(bool), typeof(Behavior), new PropertyMetadata(false, OnFocusOnLoadedChanged));
|
||||
|
||||
static void OnFocusOnLoadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = (FrameworkElement)d;
|
||||
if ((bool)e.NewValue) target.Loaded += _target_Loaded;
|
||||
else target.Loaded -= _target_Loaded;
|
||||
}
|
||||
|
||||
static void _target_Loaded(object sender, RoutedEventArgs e) {
|
||||
var target = (FrameworkElement)sender;
|
||||
Keyboard.Focus(target);
|
||||
}
|
||||
public static bool GetFocusOnLoaded(DependencyObject d) => (bool)d.GetValue(FocusOnLoadedProperty);
|
||||
public static void SetFocusOnLoaded(DependencyObject d, bool value) => d.SetValue(FocusOnLoadedProperty, value);
|
||||
#endregion
|
||||
|
||||
#region MirrorLayout
|
||||
static readonly DependencyProperty[] _layout_props = new[] {
|
||||
FrameworkElement.HeightProperty,
|
||||
FrameworkElement.WidthProperty,
|
||||
FrameworkElement.MarginProperty,
|
||||
Control.PaddingProperty,
|
||||
FrameworkElement.HorizontalAlignmentProperty,
|
||||
Control.HorizontalContentAlignmentProperty,
|
||||
FrameworkElement.VerticalAlignmentProperty,
|
||||
Control.VerticalContentAlignmentProperty
|
||||
};
|
||||
|
||||
public static readonly DependencyProperty MirrorLayoutForProperty =
|
||||
DependencyProperty.RegisterAttached("MirrorLayoutFor", typeof(FrameworkElement), typeof(Behavior),
|
||||
new PropertyMetadata(OnMirrorLayoutForChanged));
|
||||
|
||||
static void OnMirrorLayoutForChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = new LazyCast<FrameworkElement, DependencyObject>(d);
|
||||
if (e.OldValue is FrameworkElement old_src) {
|
||||
target.value.clear_bindings(_layout_props);
|
||||
}
|
||||
if (e.NewValue is FrameworkElement new_src) {
|
||||
target.value.bind(FrameworkElement.WidthProperty, new_src, FrameworkElement.ActualWidthProperty);
|
||||
target.value.bind(FrameworkElement.HeightProperty, new_src, FrameworkElement.ActualHeightProperty);
|
||||
target.value.bind(FrameworkElement.MarginProperty, new_src, FrameworkElement.MarginProperty);
|
||||
target.value.bind(FrameworkElement.HorizontalAlignmentProperty, new_src, FrameworkElement.HorizontalAlignmentProperty);
|
||||
target.value.bind(FrameworkElement.VerticalAlignmentProperty, new_src, FrameworkElement.VerticalAlignmentProperty);
|
||||
if (target.value is Control && new_src is Control) {
|
||||
target.value.bind(Control.PaddingProperty, new_src, Control.PaddingProperty);
|
||||
target.value.bind(Control.HorizontalContentAlignmentProperty, new_src, Control.HorizontalContentAlignmentProperty);
|
||||
target.value.bind(Control.VerticalContentAlignmentProperty, new_src, Control.VerticalContentAlignmentProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FrameworkElement GetMirrorLayoutFor(DependencyObject d) => d.GetValue(MirrorLayoutForProperty) as FrameworkElement;
|
||||
public static void SetMirrorLayoutFor(DependencyObject d, FrameworkElement value) => d.SetValue(MirrorLayoutForProperty, value);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -557,6 +557,8 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" />
|
||||
|
||||
<Style TargetType="RepeatButton">
|
||||
<Setter Property="Foreground" Value="{u:BTSBrush UI.Button.normal_foreground}" />
|
||||
<Setter Property="Background" Value="{u:BTSBrush UI.Button.normal_background}" />
|
||||
|
||||
@@ -1,348 +1,371 @@
|
||||
using bts;
|
||||
|
||||
using mbts.ui.native;
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace mbts.ui {
|
||||
public partial class WindowStyle : ResourceDictionary {
|
||||
// 'Top' is 0 because the window's TitleContent overlaps the top-resize-border.
|
||||
public static readonly Thickness NegativeResizeBorderThickness = new Thickness(-5, 0, -5, -5);
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler
|
||||
CloseWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Close(), "closing"),
|
||||
HideWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Visibility = Visibility.Hidden, "hiding"),
|
||||
MinimizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = WindowState.Minimized),
|
||||
MaximizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = w.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized);
|
||||
|
||||
#region UnsafeWindow
|
||||
// UnsafeWindow is described as 'Unsafe' because the window may be on a different dispatcher than the DependencyObject target.
|
||||
public static readonly DependencyProperty UnsafeWindowProperty =
|
||||
DependencyProperty.RegisterAttached("UnsafeWindow", typeof(Window), typeof(WindowStyle), new PropertyMetadata(OnUnsafeWindowChanged));
|
||||
|
||||
static void OnUnsafeWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is UIElement el)
|
||||
el.RaiseEvent(new RoutedPropertyChangedEventArgs<Window>(e.OldValue as Window, e.NewValue as Window, UnsafeWindowChangedEvent));
|
||||
}
|
||||
|
||||
public static Window GetUnsafeWindow(DependencyObject d) => d.GetValue(UnsafeWindowProperty) as Window;
|
||||
public static void SetUnsafeWindow(DependencyObject d, Window value) => d.SetValue(UnsafeWindowProperty, value);
|
||||
#endregion
|
||||
|
||||
#region UnsafeWindowChanged (Event)
|
||||
internal static readonly RoutedEvent UnsafeWindowChangedEvent = EventManager.RegisterRoutedEvent("UnsafeWindowChanged", RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler<Window>), typeof(WindowStyle));
|
||||
|
||||
internal static void AddUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
|
||||
el.AddHandler(UnsafeWindowChangedEvent, handler);
|
||||
|
||||
internal static void RemoveUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
|
||||
el.RemoveHandler(UnsafeWindowChangedEvent, handler);
|
||||
#endregion
|
||||
|
||||
static class WindowCallbackHelper {
|
||||
public static ExecutedRoutedEventHandler safe_command_executed_handler(Action<Window> action, string action_name) =>
|
||||
(s, e) => _safe_execute_on_window(s, action, action_name);
|
||||
|
||||
public static ExecutedRoutedEventHandler command_executed_handler(Action<Window> action) =>
|
||||
(s, e) => _execute_on_window(s, action);
|
||||
|
||||
public static RoutedEventHandler routed_event_handler(Action<Window> action) =>
|
||||
(s, e) => _execute_on_window(s, action);
|
||||
|
||||
static void _execute_on_window(object sender, Action<Window> action) {
|
||||
if (_get_host_window(sender) is Window window)
|
||||
action(window);
|
||||
}
|
||||
|
||||
static void _safe_execute_on_window(object sender, Action<Window> action, string action_name) {
|
||||
try {
|
||||
_execute_on_window(sender, action);
|
||||
} catch (Exception err) {
|
||||
Log.Ui.warn($"Exception {action_name} window : {err}");
|
||||
}
|
||||
}
|
||||
|
||||
static Window _get_host_window(object sender) =>
|
||||
sender is Window w ? w : sender is DependencyObject d ? Window.GetWindow(d) : null;
|
||||
}
|
||||
|
||||
#region Attached properties to control the style b/c the default Window properties are lame
|
||||
|
||||
#region Close button
|
||||
public static bool GetShowCloseButton(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(ShowCloseButtonProperty);
|
||||
}
|
||||
public static void SetShowCloseButton(DependencyObject obj, bool value) {
|
||||
obj.SetValue(ShowCloseButtonProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowCloseButtonProperty =
|
||||
DependencyProperty.RegisterAttached("ShowCloseButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
|
||||
#endregion
|
||||
|
||||
#region Minimize Button
|
||||
public static bool GetShowMinimizeButton(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(ShowMinimizeButtonProperty);
|
||||
}
|
||||
public static void SetShowMinimizeButton(DependencyObject obj, bool value) {
|
||||
obj.SetValue(ShowMinimizeButtonProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowMinimizeButtonProperty =
|
||||
DependencyProperty.RegisterAttached("ShowMinimizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
|
||||
#endregion
|
||||
|
||||
#region Maximize Button
|
||||
|
||||
public static bool GetShowMaximizeButton(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(ShowMaximizeButtonProperty);
|
||||
}
|
||||
public static void SetShowMaximizeButton(DependencyObject obj, bool value) {
|
||||
obj.SetValue(ShowMaximizeButtonProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowMaximizeButtonProperty =
|
||||
DependencyProperty.RegisterAttached("ShowMaximizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false));
|
||||
#endregion
|
||||
|
||||
#region AllowResize
|
||||
public static bool GetAllowResize(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(AllowResizeProperty);
|
||||
}
|
||||
public static void SetAllowResize(DependencyObject obj, bool value) {
|
||||
obj.SetValue(AllowResizeProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty AllowResizeProperty =
|
||||
DependencyProperty.RegisterAttached("AllowResize", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, (s, e) => {
|
||||
var wnd = s as Window;
|
||||
if (wnd != null) {
|
||||
if ((bool)e.NewValue == true) {
|
||||
wnd.ResizeMode = ResizeMode.CanResize;
|
||||
} else {
|
||||
wnd.ResizeMode = ResizeMode.NoResize;
|
||||
}
|
||||
}
|
||||
}));
|
||||
#endregion
|
||||
|
||||
#region IsNonClientArea Inherited property
|
||||
|
||||
public static bool GetIsNonClientArea(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(IsNonClientAreaProperty);
|
||||
}
|
||||
|
||||
public static void SetIsNonClientArea(DependencyObject obj, bool value) {
|
||||
obj.SetValue(IsNonClientAreaProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsNonClientAreaProperty =
|
||||
DependencyProperty.RegisterAttached("IsNonClientArea", typeof(bool), typeof(WindowStyle), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
|
||||
#endregion
|
||||
|
||||
#region [Custom]SizeToContent property (some additional logic is needed to avoid ugly black border margins on the edge of the window)
|
||||
|
||||
public static readonly DependencyProperty SizeToContentProperty = DependencyProperty.RegisterAttached("SizeToContent", typeof(SizeToContent), typeof(WindowStyle), new PropertyMetadata(SizeToContent.Manual, OnSizeToContentChanged));
|
||||
private static void OnSizeToContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
SizeToContent old_val = (SizeToContent)e.OldValue;
|
||||
SizeToContent new_val = (SizeToContent)e.NewValue;
|
||||
Window w = (Window)d;
|
||||
if (old_val != SizeToContent.Manual) {
|
||||
if (new_val == SizeToContent.Manual) w.ContentRendered -= InvalidateVisualOnContentRendered;
|
||||
} else w.ContentRendered += InvalidateVisualOnContentRendered;
|
||||
w.SizeToContent = new_val;
|
||||
}
|
||||
private static void InvalidateVisualOnContentRendered(object sender, EventArgs e) { ((Window)sender).InvalidateVisual(); }
|
||||
public static SizeToContent GetSizeToContent(Window w) { return (SizeToContent)w.GetValue(SizeToContentProperty); }
|
||||
public static void SetSizeToContent(Window w, SizeToContent value) { w.SetValue(SizeToContentProperty, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region TitleContent
|
||||
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.RegisterAttached("TitleContent", typeof(object), typeof(WindowStyle), new PropertyMetadata(null));
|
||||
public static object GetTitleContent(DependencyObject d) { return d.GetValue(TitleContentProperty); }
|
||||
public static void SetTitleContent(DependencyObject d, object value) { d.SetValue(TitleContentProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region TitleContentTemplate
|
||||
public static readonly DependencyProperty TitleContentTemplateProperty = DependencyProperty.RegisterAttached("TitleContentTemplate", typeof(DataTemplate), typeof(WindowStyle), new PropertyMetadata(null));
|
||||
public static DataTemplate GetTitleContentTemplate(DependencyObject d) { return d.GetValue(TitleContentTemplateProperty) as DataTemplate; }
|
||||
public static void SetTitleContentTemplate(DependencyObject d, DataTemplate value) { d.SetValue(TitleContentTemplateProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region DisposeVMOnClose
|
||||
public static readonly DependencyProperty DisposeVMOnCloseProperty = DependencyProperty.RegisterAttached("DisposeVMOnClose", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, OnDisposeVMOnCloseChanged));
|
||||
private static void OnDisposeVMOnCloseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = d as Window;
|
||||
target?.SetValue(DisposeVMOnCloseHandlerWrapperPropertyKey, (bool)e.NewValue ? new DisposeVMOnCloseHandlerWrapper(target) : null);
|
||||
}
|
||||
public static bool GetDisposeVMOnClose(DependencyObject d) { return (bool)d.GetValue(DisposeVMOnCloseProperty); }
|
||||
public static void SetDisposeVMOnClose(DependencyObject d, bool value) { d.SetValue(DisposeVMOnCloseProperty, value); }
|
||||
|
||||
private static readonly DependencyPropertyKey DisposeVMOnCloseHandlerWrapperPropertyKey = DependencyProperty.RegisterAttachedReadOnly("DisposeVMOnCloseHandlerWrapper", typeof(IDisposable), typeof(WindowStyle), new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
|
||||
internal static readonly DependencyProperty DisposeVMOnCloseHandlerWrapperProperty = DisposeVMOnCloseHandlerWrapperPropertyKey.DependencyProperty;
|
||||
internal static IDisposable GetDisposeVMOnCloseHandlerWrapper(Window w) { return w.GetValue(DisposeVMOnCloseHandlerWrapperProperty) as IDisposable; }
|
||||
private static void SetDisposeVMOnCloseHandlerWrapper(Window w, IDisposable value) { w.SetValue(DisposeVMOnCloseHandlerWrapperProperty, value); }
|
||||
|
||||
private class DisposeVMOnCloseHandlerWrapper : IDisposable {
|
||||
public DisposeVMOnCloseHandlerWrapper(Window target) { (_target = target).Closed += _target_Closed; }
|
||||
private readonly Window _target;
|
||||
private void _target_Closed(object sender, EventArgs e) { (_target.DataContext as IDisposable)?.Dispose(); }
|
||||
public void Dispose() { _target.Closed -= _target_Closed; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
private void style_loaded(object sender, RoutedEventArgs _) {
|
||||
if (sender is DependencyObject) {
|
||||
var window = Window.GetWindow(((DependencyObject)sender));
|
||||
if (window != null) {
|
||||
if (WindowStyle.GetAllowResize(window)) {
|
||||
set_resize_window_events(sender as FrameworkElement, window);
|
||||
} else {
|
||||
// Simpler dragmove interaction
|
||||
var border = (FrameworkElement)sender;
|
||||
border.MouseLeftButtonDown += (s, e) => {
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.DragMove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Close command automagically
|
||||
if (window.CommandBindings.Count == 0) {
|
||||
window.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindowExecutedHandler));
|
||||
window.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindowExecutedHandler));
|
||||
window.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindowExecutedHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void set_resize_window_events(FrameworkElement border, Window window) {
|
||||
HookWndProc(window);
|
||||
Log.assert(border != null);
|
||||
var restore_on_move = false;
|
||||
border.MouseLeftButtonDown += (s, e) => {
|
||||
FrameworkElement fe = e.OriginalSource as FrameworkElement;
|
||||
if (fe != null && (bool)fe.GetValue(WindowStyle.IsNonClientAreaProperty)) {
|
||||
if (e.ClickCount == 2) {
|
||||
toggle_max(window);
|
||||
} else {
|
||||
if (window.WindowState == WindowState.Maximized) { restore_on_move = true; }
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
border.MouseLeftButtonUp += (s, e) => restore_on_move = false;
|
||||
border.MouseMove += (s, e) => {
|
||||
if (restore_on_move && window.WindowState == WindowState.Maximized && Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.WindowState = WindowState.Normal;
|
||||
window.Top = 0;
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.DragMove();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void toggle_max(Window window) {
|
||||
if (window.WindowState == WindowState.Maximized) {
|
||||
window.WindowState = WindowState.Normal;
|
||||
} else {
|
||||
window.WindowState = WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void HookWndProc(Window window) {
|
||||
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (source != null) {
|
||||
source.AddHook(new HwndSourceHook(WndProc));
|
||||
}
|
||||
}
|
||||
private static void UnhookWndProc(Window window) {
|
||||
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (source != null) {
|
||||
source.RemoveHook(new HwndSourceHook(WndProc));
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) {
|
||||
WM wmsg = (WM)msg;
|
||||
if (WM.NCHITTEST == wmsg /*|| WM.MOUSEMOVE == wmsg*/) {
|
||||
Thickness t = new Thickness(6);
|
||||
// Get the mouse coordinates in screen space
|
||||
short x = (short)lparam.ToInt32();
|
||||
short y = (short)(lparam.ToInt32() >> 16);
|
||||
// Get the window bounds in screen coords
|
||||
Win32Rect w;
|
||||
NativeMethods.GetWindowRect(hwnd, out w);
|
||||
// Assume that mouse position is inside the window
|
||||
bool left = x < (w.Left + t.Left);
|
||||
bool right = x > (w.Right - t.Right);
|
||||
bool top = y < (w.Top + t.Top);
|
||||
bool bottom = y > (w.Bottom - t.Bottom);
|
||||
|
||||
// A B C
|
||||
// D E
|
||||
// F G H
|
||||
|
||||
HT ret = HT.CLIENT;
|
||||
if (top) {
|
||||
if (left) ret = HT.TOPLEFT; // A
|
||||
else if (right) ret = HT.TOPRIGHT; // C
|
||||
else ret = HT.TOP; // B
|
||||
} else if (bottom) {
|
||||
if (left) ret = HT.BOTTOMLEFT; // F
|
||||
else if (right) ret = HT.BOTTOMRIGHT; // H
|
||||
else ret = HT.BOTTOM; // G
|
||||
} else {
|
||||
if (left) ret = HT.LEFT; // D
|
||||
if (right) ret = HT.RIGHT; // E
|
||||
}
|
||||
|
||||
handled = true;
|
||||
return new IntPtr((int)ret);
|
||||
} else if (wmsg == WM.NCCALCSIZE) {
|
||||
handled = true;
|
||||
} else if (wmsg == WM.NCPAINT) {
|
||||
handled = true;
|
||||
} else if (wmsg == WM.NCACTIVATE) {
|
||||
handled = true;
|
||||
return NativeMethods.DefWindowProc(hwnd, msg, wparam, new IntPtr(-1));
|
||||
} else if (wmsg == WM.ERASEBKGND) {
|
||||
handled = true;
|
||||
} else if (wmsg == WM.GETMINMAXINFO) {
|
||||
SetMinMaxInfo(hwnd, lparam);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static void SetMinMaxInfo(IntPtr hwnd, IntPtr lparam) {
|
||||
var mminfo = (MINMAXINFO)Marshal.PtrToStructure(lparam, typeof(MINMAXINFO));
|
||||
|
||||
var hmonitor = NativeMethods.MonitorFromWindow(hwnd, NativeMethods.MONITOR_DEFAULTONNEAREST);
|
||||
if (hmonitor != IntPtr.Zero) {
|
||||
MONITORINFO minfo = new MONITORINFO();
|
||||
minfo.cbSize = Marshal.SizeOf(minfo);
|
||||
NativeMethods.GetMonitorInfo(hmonitor, ref minfo);
|
||||
var rc_monitor = minfo.rcMonitor;
|
||||
var rc_work = minfo.rcWork;
|
||||
mminfo.ptMaxPosition.x = Math.Abs(rc_work.Left - rc_monitor.Left);
|
||||
mminfo.ptMaxPosition.y = Math.Abs(rc_work.Top - rc_monitor.Top);
|
||||
mminfo.ptMaxSize.x = Math.Abs(rc_work.Right - rc_work.Left);
|
||||
mminfo.ptMaxSize.y = Math.Abs(rc_work.Bottom - rc_work.Top);
|
||||
|
||||
Marshal.StructureToPtr(mminfo, lparam, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
using bts;
|
||||
|
||||
using mbts.ui.native;
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace mbts.ui {
|
||||
public partial class WindowStyle : ResourceDictionary {
|
||||
// 'Top' is 0 because the window's TitleContent overlaps the top-resize-border.
|
||||
public static readonly Thickness NegativeResizeBorderThickness = new Thickness(-5, 0, -5, -5);
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler
|
||||
CloseWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Close(), "closing"),
|
||||
HideWindowExecutedHandler = WindowCallbackHelper.safe_command_executed_handler(w => w.Visibility = Visibility.Hidden, "hiding"),
|
||||
MinimizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = WindowState.Minimized),
|
||||
MaximizeWindowExecutedHandler = WindowCallbackHelper.command_executed_handler(w => w.WindowState = w.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized);
|
||||
|
||||
#region UnsafeWindow
|
||||
// UnsafeWindow is described as 'Unsafe' because the window may be on a different dispatcher than the DependencyObject target.
|
||||
public static readonly DependencyProperty UnsafeWindowProperty =
|
||||
DependencyProperty.RegisterAttached("UnsafeWindow", typeof(Window), typeof(WindowStyle), new PropertyMetadata(OnUnsafeWindowChanged));
|
||||
|
||||
static void OnUnsafeWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is UIElement el)
|
||||
el.RaiseEvent(new RoutedPropertyChangedEventArgs<Window>(e.OldValue as Window, e.NewValue as Window, UnsafeWindowChangedEvent));
|
||||
}
|
||||
|
||||
public static Window GetUnsafeWindow(DependencyObject d) => d.GetValue(UnsafeWindowProperty) as Window;
|
||||
public static void SetUnsafeWindow(DependencyObject d, Window value) => d.SetValue(UnsafeWindowProperty, value);
|
||||
#endregion
|
||||
|
||||
#region UnsafeWindowChanged (Event)
|
||||
internal static readonly RoutedEvent UnsafeWindowChangedEvent = EventManager.RegisterRoutedEvent("UnsafeWindowChanged", RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler<Window>), typeof(WindowStyle));
|
||||
|
||||
internal static void AddUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
|
||||
el.AddHandler(UnsafeWindowChangedEvent, handler);
|
||||
|
||||
internal static void RemoveUnsafeWindowChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<Window> handler) =>
|
||||
el.RemoveHandler(UnsafeWindowChangedEvent, handler);
|
||||
#endregion
|
||||
|
||||
#region WinFormsHost
|
||||
public static readonly DependencyProperty WinFormsHostProperty =
|
||||
DependencyProperty.RegisterAttached("WinFormsHost", typeof(WPFPanel), typeof(WindowStyle), new PropertyMetadata(OnWinFormsHostChanged));
|
||||
|
||||
static void OnWinFormsHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
if (d is UIElement el)
|
||||
el.RaiseEvent(new RoutedPropertyChangedEventArgs<WPFPanel>(e.OldValue as WPFPanel, e.NewValue as WPFPanel, WinFormsHostChangedEvent));
|
||||
}
|
||||
|
||||
public static WPFPanel GetWinFormsHost(DependencyObject d) => d.GetValue(WinFormsHostProperty) as WPFPanel;
|
||||
public static void SetWinFormsHost(DependencyObject d, WPFPanel value) => d.SetValue(WinFormsHostProperty, value);
|
||||
#endregion
|
||||
|
||||
#region WinFormsHostChanged (Event)
|
||||
internal static readonly RoutedEvent WinFormsHostChangedEvent = EventManager.RegisterRoutedEvent("WinFormsHostChanged", RoutingStrategy.Direct, typeof(RoutedPropertyChangedEventHandler<WPFPanel>), typeof(WindowStyle));
|
||||
|
||||
internal static void AddWinFormsHostChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<WPFPanel> handler) =>
|
||||
el.AddHandler(WinFormsHostChangedEvent, handler);
|
||||
|
||||
internal static void RemoveWinFormsHostChangedHandler(UIElement el, RoutedPropertyChangedEventHandler<WPFPanel> handler) =>
|
||||
el.RemoveHandler(WinFormsHostChangedEvent, handler);
|
||||
#endregion
|
||||
|
||||
static class WindowCallbackHelper {
|
||||
public static ExecutedRoutedEventHandler safe_command_executed_handler(Action<Window> action, string action_name) =>
|
||||
(s, e) => _safe_execute_on_window(s, action, action_name);
|
||||
|
||||
public static ExecutedRoutedEventHandler command_executed_handler(Action<Window> action) =>
|
||||
(s, e) => _execute_on_window(s, action);
|
||||
|
||||
public static RoutedEventHandler routed_event_handler(Action<Window> action) =>
|
||||
(s, e) => _execute_on_window(s, action);
|
||||
|
||||
static void _execute_on_window(object sender, Action<Window> action) {
|
||||
if (_get_host_window(sender) is Window window)
|
||||
action(window);
|
||||
}
|
||||
|
||||
static void _safe_execute_on_window(object sender, Action<Window> action, string action_name) {
|
||||
try {
|
||||
_execute_on_window(sender, action);
|
||||
} catch (Exception err) {
|
||||
Log.Ui.warn($"Exception {action_name} window : {err}");
|
||||
}
|
||||
}
|
||||
|
||||
static Window _get_host_window(object sender) =>
|
||||
sender is Window w ? w : sender is DependencyObject d ? Window.GetWindow(d) : null;
|
||||
}
|
||||
|
||||
#region Attached properties to control the style b/c the default Window properties are lame
|
||||
|
||||
#region Close button
|
||||
public static bool GetShowCloseButton(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(ShowCloseButtonProperty);
|
||||
}
|
||||
public static void SetShowCloseButton(DependencyObject obj, bool value) {
|
||||
obj.SetValue(ShowCloseButtonProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowCloseButtonProperty =
|
||||
DependencyProperty.RegisterAttached("ShowCloseButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
|
||||
#endregion
|
||||
|
||||
#region Minimize Button
|
||||
public static bool GetShowMinimizeButton(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(ShowMinimizeButtonProperty);
|
||||
}
|
||||
public static void SetShowMinimizeButton(DependencyObject obj, bool value) {
|
||||
obj.SetValue(ShowMinimizeButtonProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowMinimizeButtonProperty =
|
||||
DependencyProperty.RegisterAttached("ShowMinimizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(true));
|
||||
#endregion
|
||||
|
||||
#region Maximize Button
|
||||
|
||||
public static bool GetShowMaximizeButton(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(ShowMaximizeButtonProperty);
|
||||
}
|
||||
public static void SetShowMaximizeButton(DependencyObject obj, bool value) {
|
||||
obj.SetValue(ShowMaximizeButtonProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowMaximizeButtonProperty =
|
||||
DependencyProperty.RegisterAttached("ShowMaximizeButton", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false));
|
||||
#endregion
|
||||
|
||||
#region AllowResize
|
||||
public static bool GetAllowResize(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(AllowResizeProperty);
|
||||
}
|
||||
public static void SetAllowResize(DependencyObject obj, bool value) {
|
||||
obj.SetValue(AllowResizeProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty AllowResizeProperty =
|
||||
DependencyProperty.RegisterAttached("AllowResize", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, (s, e) => {
|
||||
var wnd = s as Window;
|
||||
if (wnd != null) {
|
||||
if ((bool)e.NewValue == true) {
|
||||
wnd.ResizeMode = ResizeMode.CanResize;
|
||||
} else {
|
||||
wnd.ResizeMode = ResizeMode.NoResize;
|
||||
}
|
||||
}
|
||||
}));
|
||||
#endregion
|
||||
|
||||
#region IsNonClientArea Inherited property
|
||||
|
||||
public static bool GetIsNonClientArea(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(IsNonClientAreaProperty);
|
||||
}
|
||||
|
||||
public static void SetIsNonClientArea(DependencyObject obj, bool value) {
|
||||
obj.SetValue(IsNonClientAreaProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsNonClientAreaProperty =
|
||||
DependencyProperty.RegisterAttached("IsNonClientArea", typeof(bool), typeof(WindowStyle), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
|
||||
#endregion
|
||||
|
||||
#region [Custom]SizeToContent property (some additional logic is needed to avoid ugly black border margins on the edge of the window)
|
||||
|
||||
public static readonly DependencyProperty SizeToContentProperty = DependencyProperty.RegisterAttached("SizeToContent", typeof(SizeToContent), typeof(WindowStyle), new PropertyMetadata(SizeToContent.Manual, OnSizeToContentChanged));
|
||||
private static void OnSizeToContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
SizeToContent old_val = (SizeToContent)e.OldValue;
|
||||
SizeToContent new_val = (SizeToContent)e.NewValue;
|
||||
Window w = (Window)d;
|
||||
if (old_val != SizeToContent.Manual) {
|
||||
if (new_val == SizeToContent.Manual) w.ContentRendered -= InvalidateVisualOnContentRendered;
|
||||
} else w.ContentRendered += InvalidateVisualOnContentRendered;
|
||||
w.SizeToContent = new_val;
|
||||
}
|
||||
private static void InvalidateVisualOnContentRendered(object sender, EventArgs e) { ((Window)sender).InvalidateVisual(); }
|
||||
public static SizeToContent GetSizeToContent(Window w) { return (SizeToContent)w.GetValue(SizeToContentProperty); }
|
||||
public static void SetSizeToContent(Window w, SizeToContent value) { w.SetValue(SizeToContentProperty, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region TitleContent
|
||||
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.RegisterAttached("TitleContent", typeof(object), typeof(WindowStyle), new PropertyMetadata(null));
|
||||
public static object GetTitleContent(DependencyObject d) { return d.GetValue(TitleContentProperty); }
|
||||
public static void SetTitleContent(DependencyObject d, object value) { d.SetValue(TitleContentProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region TitleContentTemplate
|
||||
public static readonly DependencyProperty TitleContentTemplateProperty = DependencyProperty.RegisterAttached("TitleContentTemplate", typeof(DataTemplate), typeof(WindowStyle), new PropertyMetadata(null));
|
||||
public static DataTemplate GetTitleContentTemplate(DependencyObject d) { return d.GetValue(TitleContentTemplateProperty) as DataTemplate; }
|
||||
public static void SetTitleContentTemplate(DependencyObject d, DataTemplate value) { d.SetValue(TitleContentTemplateProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region DisposeVMOnClose
|
||||
public static readonly DependencyProperty DisposeVMOnCloseProperty = DependencyProperty.RegisterAttached("DisposeVMOnClose", typeof(bool), typeof(WindowStyle), new PropertyMetadata(false, OnDisposeVMOnCloseChanged));
|
||||
private static void OnDisposeVMOnCloseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var target = d as Window;
|
||||
target?.SetValue(DisposeVMOnCloseHandlerWrapperPropertyKey, (bool)e.NewValue ? new DisposeVMOnCloseHandlerWrapper(target) : null);
|
||||
}
|
||||
public static bool GetDisposeVMOnClose(DependencyObject d) { return (bool)d.GetValue(DisposeVMOnCloseProperty); }
|
||||
public static void SetDisposeVMOnClose(DependencyObject d, bool value) { d.SetValue(DisposeVMOnCloseProperty, value); }
|
||||
|
||||
private static readonly DependencyPropertyKey DisposeVMOnCloseHandlerWrapperPropertyKey = DependencyProperty.RegisterAttachedReadOnly("DisposeVMOnCloseHandlerWrapper", typeof(IDisposable), typeof(WindowStyle), new PropertyMetadata(null, FrameworkElementLifeDependencyHelper.DisposeOld));
|
||||
internal static readonly DependencyProperty DisposeVMOnCloseHandlerWrapperProperty = DisposeVMOnCloseHandlerWrapperPropertyKey.DependencyProperty;
|
||||
internal static IDisposable GetDisposeVMOnCloseHandlerWrapper(Window w) { return w.GetValue(DisposeVMOnCloseHandlerWrapperProperty) as IDisposable; }
|
||||
private static void SetDisposeVMOnCloseHandlerWrapper(Window w, IDisposable value) { w.SetValue(DisposeVMOnCloseHandlerWrapperProperty, value); }
|
||||
|
||||
private class DisposeVMOnCloseHandlerWrapper : IDisposable {
|
||||
public DisposeVMOnCloseHandlerWrapper(Window target) { (_target = target).Closed += _target_Closed; }
|
||||
private readonly Window _target;
|
||||
private void _target_Closed(object sender, EventArgs e) { (_target.DataContext as IDisposable)?.Dispose(); }
|
||||
public void Dispose() { _target.Closed -= _target_Closed; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
private void style_loaded(object sender, RoutedEventArgs _) {
|
||||
if (sender is DependencyObject) {
|
||||
var window = Window.GetWindow(((DependencyObject)sender));
|
||||
if (window != null) {
|
||||
if (WindowStyle.GetAllowResize(window)) {
|
||||
set_resize_window_events(sender as FrameworkElement, window);
|
||||
} else {
|
||||
// Simpler dragmove interaction
|
||||
var border = (FrameworkElement)sender;
|
||||
border.MouseLeftButtonDown += (s, e) => {
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.DragMove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle Close command automagically
|
||||
if (window.CommandBindings.Count == 0) {
|
||||
window.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindowExecutedHandler));
|
||||
window.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindowExecutedHandler));
|
||||
window.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindowExecutedHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void set_resize_window_events(FrameworkElement border, Window window) {
|
||||
HookWndProc(window);
|
||||
Log.assert(border != null);
|
||||
var restore_on_move = false;
|
||||
border.MouseLeftButtonDown += (s, e) => {
|
||||
FrameworkElement fe = e.OriginalSource as FrameworkElement;
|
||||
if (fe != null && (bool)fe.GetValue(WindowStyle.IsNonClientAreaProperty)) {
|
||||
if (e.ClickCount == 2) {
|
||||
toggle_max(window);
|
||||
} else {
|
||||
if (window.WindowState == WindowState.Maximized) { restore_on_move = true; }
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
border.MouseLeftButtonUp += (s, e) => restore_on_move = false;
|
||||
border.MouseMove += (s, e) => {
|
||||
if (restore_on_move && window.WindowState == WindowState.Maximized && Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.WindowState = WindowState.Normal;
|
||||
window.Top = 0;
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) {
|
||||
window.DragMove();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void toggle_max(Window window) {
|
||||
if (window.WindowState == WindowState.Maximized) {
|
||||
window.WindowState = WindowState.Normal;
|
||||
} else {
|
||||
window.WindowState = WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void HookWndProc(Window window) {
|
||||
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (source != null) {
|
||||
source.AddHook(new HwndSourceHook(WndProc));
|
||||
}
|
||||
}
|
||||
private static void UnhookWndProc(Window window) {
|
||||
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (source != null) {
|
||||
source.RemoveHook(new HwndSourceHook(WndProc));
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) {
|
||||
WM wmsg = (WM)msg;
|
||||
if (WM.NCHITTEST == wmsg /*|| WM.MOUSEMOVE == wmsg*/) {
|
||||
Thickness t = new Thickness(6);
|
||||
// Get the mouse coordinates in screen space
|
||||
short x = (short)lparam.ToInt32();
|
||||
short y = (short)(lparam.ToInt32() >> 16);
|
||||
// Get the window bounds in screen coords
|
||||
Win32Rect w;
|
||||
NativeMethods.GetWindowRect(hwnd, out w);
|
||||
// Assume that mouse position is inside the window
|
||||
bool left = x < (w.Left + t.Left);
|
||||
bool right = x > (w.Right - t.Right);
|
||||
bool top = y < (w.Top + t.Top);
|
||||
bool bottom = y > (w.Bottom - t.Bottom);
|
||||
|
||||
// A B C
|
||||
// D E
|
||||
// F G H
|
||||
|
||||
HT ret = HT.CLIENT;
|
||||
if (top) {
|
||||
if (left) ret = HT.TOPLEFT; // A
|
||||
else if (right) ret = HT.TOPRIGHT; // C
|
||||
else ret = HT.TOP; // B
|
||||
} else if (bottom) {
|
||||
if (left) ret = HT.BOTTOMLEFT; // F
|
||||
else if (right) ret = HT.BOTTOMRIGHT; // H
|
||||
else ret = HT.BOTTOM; // G
|
||||
} else {
|
||||
if (left) ret = HT.LEFT; // D
|
||||
if (right) ret = HT.RIGHT; // E
|
||||
}
|
||||
|
||||
handled = true;
|
||||
return new IntPtr((int)ret);
|
||||
} else if (wmsg == WM.NCCALCSIZE) {
|
||||
handled = true;
|
||||
} else if (wmsg == WM.NCPAINT) {
|
||||
handled = true;
|
||||
} else if (wmsg == WM.NCACTIVATE) {
|
||||
handled = true;
|
||||
return NativeMethods.DefWindowProc(hwnd, msg, wparam, new IntPtr(-1));
|
||||
} else if (wmsg == WM.ERASEBKGND) {
|
||||
handled = true;
|
||||
} else if (wmsg == WM.GETMINMAXINFO) {
|
||||
SetMinMaxInfo(hwnd, lparam);
|
||||
handled = true;
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static void SetMinMaxInfo(IntPtr hwnd, IntPtr lparam) {
|
||||
var mminfo = (MINMAXINFO)Marshal.PtrToStructure(lparam, typeof(MINMAXINFO));
|
||||
|
||||
var hmonitor = NativeMethods.MonitorFromWindow(hwnd, NativeMethods.MONITOR_DEFAULTONNEAREST);
|
||||
if (hmonitor != IntPtr.Zero) {
|
||||
MONITORINFO minfo = new MONITORINFO();
|
||||
minfo.cbSize = Marshal.SizeOf(minfo);
|
||||
NativeMethods.GetMonitorInfo(hmonitor, ref minfo);
|
||||
var rc_monitor = minfo.rcMonitor;
|
||||
var rc_work = minfo.rcWork;
|
||||
mminfo.ptMaxPosition.x = Math.Abs(rc_work.Left - rc_monitor.Left);
|
||||
mminfo.ptMaxPosition.y = Math.Abs(rc_work.Top - rc_monitor.Top);
|
||||
mminfo.ptMaxSize.x = Math.Abs(rc_work.Right - rc_work.Left);
|
||||
mminfo.ptMaxSize.y = Math.Abs(rc_work.Bottom - rc_work.Top);
|
||||
|
||||
Marshal.StructureToPtr(mminfo, lparam, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@
|
||||
}
|
||||
|
||||
void PerformMEFComposition(string config_source) {
|
||||
Log.Ui.info($"Generating MEF dependencies for the shell " +
|
||||
Log.Ui.info($"this is v2.2.1 Generating MEF dependencies for the shell" +
|
||||
$"({typeof(IInitializable)}s, {typeof(IShellModule)}s, " +
|
||||
$"{typeof(IShellDaemon)}s, etc.)");
|
||||
try {
|
||||
|
||||
@@ -1,241 +1,242 @@
|
||||
namespace mbts.ui.shell {
|
||||
using bts;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Async;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
public class ShellWindowVM : DisposableViewModelBase {
|
||||
readonly IWindowManager _window_manager;
|
||||
readonly Property<bool> _edit_mode;
|
||||
readonly Property<string> _title;
|
||||
readonly Property<string> _title_override;
|
||||
readonly Property<bool> _has_custom_title;
|
||||
|
||||
IShellWindow _model;
|
||||
UIElement _ui;
|
||||
ShellWindowUI _document;
|
||||
HwndContentHost _hwnd_host;
|
||||
FloatingWindow _owner;
|
||||
ImageSource _icon;
|
||||
ICommand _close_command;
|
||||
ICommand _rename_command;
|
||||
ICommand _unrename_command;
|
||||
|
||||
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = null) {
|
||||
Log.assert(model != null);
|
||||
_model = model;
|
||||
_model.IconChanged += on_icon_changed;
|
||||
_window_manager = wm;
|
||||
host_id = (model as ITabAffineWindow)?.tab_group ?? host ?? string.Empty;
|
||||
|
||||
// init Dispatcher
|
||||
var stf = (ShellThreadFactory)threads;
|
||||
if (IsShared) {
|
||||
Dispatcher = stf.get_dispatcher((ISharedWindow)model);
|
||||
} else if (IsSlow) {
|
||||
Dispatcher = stf.create_dispatcher((ISlowWindow)model);
|
||||
} else {
|
||||
Dispatcher = Dispatcher.CurrentDispatcher;
|
||||
}
|
||||
|
||||
_edit_mode = backing(nameof(EditMode), false);
|
||||
|
||||
_title_override = backing<string>(nameof(TitleOverride));
|
||||
_title = backing(
|
||||
nameof(Title),
|
||||
_model.observe_title()
|
||||
.CombineLatest(
|
||||
_title_override.observe_current(true),
|
||||
(title, title_override) => (title, title_override))
|
||||
.Select(t => t.title_override ?? t.title)
|
||||
.OnDispatcher());
|
||||
_has_custom_title = backing(
|
||||
nameof(HasCustomTitle),
|
||||
_title_override.observe_current(true).Select(t => !t.is_null_or_white_space()).OnDispatcher());
|
||||
|
||||
model.on_shell_window_vm(this);
|
||||
|
||||
_dispose = Disposable.Create(() => {
|
||||
_model.IconChanged -= on_icon_changed;
|
||||
if (IsShared) {
|
||||
// Shared threads continue running
|
||||
Dispatcher.Invoke(() => Disposable.dispose(ref _model));
|
||||
} else if (IsSlow) {
|
||||
// Terminating the background thread will dispose the model on that thread
|
||||
Log.Ui.info("Beginning asyncronous dispose of slow window {0} .".fmt(_model.GetType().ToString()));
|
||||
Dispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
|
||||
} else {
|
||||
Disposable.dispose(ref _model);
|
||||
}
|
||||
_ui = null;
|
||||
Disposable.dispose(ref _hwnd_host);
|
||||
});
|
||||
|
||||
void on_icon_changed() {
|
||||
_icon = null;
|
||||
RaisePropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncEvent<(object sender, ShellWindowVM e)> Closing { get; } = new();
|
||||
|
||||
public string host_id { get; set; }
|
||||
public string OwnerName => _model.Owner?.name ?? string.Empty;
|
||||
public IShellWindow Model => _model;
|
||||
public Dispatcher Dispatcher { get; }
|
||||
|
||||
// title
|
||||
public string Title => _title.Value;
|
||||
public string TitleOverride {
|
||||
get => _title_override.Value;
|
||||
set {
|
||||
_title_override.Value = value;
|
||||
_edit_mode.set(false);
|
||||
}
|
||||
}
|
||||
public bool HasCustomTitle => _has_custom_title.Value;
|
||||
|
||||
public ImageSource Icon =>
|
||||
_icon ??= _model.Icon ?? _model.Owner?.icon;
|
||||
|
||||
public bool EditMode => _edit_mode.Value;
|
||||
public double InitialWidth { get; private set; }
|
||||
public double InitialHeight { get; private set; }
|
||||
|
||||
public UIElement AppContent =>
|
||||
_ui ??= _create_contents();
|
||||
|
||||
public bool IsSlow => _model is ISlowWindow;
|
||||
public bool IsShared => _model is ISharedWindow;
|
||||
public bool HasTabAffinity => _model is ITabAffineWindow;
|
||||
|
||||
public ICommand rename_command =>
|
||||
_rename_command ??= new DelegateCommand(_begin_rename);
|
||||
|
||||
public ICommand unrename_command =>
|
||||
_unrename_command ??= new DelegateCommand(() => TitleOverride = string.Empty);
|
||||
|
||||
public ICommand CloseCommand =>
|
||||
_close_command ??= new DelegateCommand(_begin_close);
|
||||
|
||||
internal FloatingWindow owner {
|
||||
get => _owner;
|
||||
set {
|
||||
if (Set(ref _owner, value)) {
|
||||
_try_set_wpf_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<string> observe_title() =>
|
||||
_title.observe_current(true);
|
||||
|
||||
void _begin_rename() {
|
||||
if (string.IsNullOrEmpty(TitleOverride)) {
|
||||
_title_override.Value = Title;
|
||||
}
|
||||
_edit_mode.set(true);
|
||||
}
|
||||
|
||||
async void _begin_close(object sender) {
|
||||
if (_window_manager is null) {
|
||||
return;
|
||||
}
|
||||
if (!_window_manager.contains(_model)) {
|
||||
return;
|
||||
}
|
||||
await Closing?.InvokeAsync((sender, this));
|
||||
// Removing the window will cause this object to be Disposed
|
||||
// Which will terminate the background thread
|
||||
// Which will dispose the model (on that thread)
|
||||
await _window_manager.remove(_model);
|
||||
}
|
||||
|
||||
UIElement _create_contents() {
|
||||
string error_text = null;
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
// Executes possibly on background thread
|
||||
try {
|
||||
_document = _model.GetContent();
|
||||
} catch (Exception ex) {
|
||||
error_text = ex.Message;
|
||||
Log.Ui.error($"Error getting window contents: {ex}");
|
||||
}
|
||||
}, DispatcherPriority.Send);
|
||||
|
||||
if (_document == null && string.IsNullOrEmpty(error_text)) {
|
||||
error_text = $"No content for window {_model.GetType()}";
|
||||
}
|
||||
|
||||
// If there's an error draw a big red X
|
||||
if (!error_text.is_null_or_empty()) {
|
||||
_document = new ShellWindowUI(new TextBlock() {
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Foreground = Brushes.Red,
|
||||
Text = error_text
|
||||
});
|
||||
_model = new ErrorShellWindow {
|
||||
Content = _document,
|
||||
Icon = _model.Icon,
|
||||
Title = $"Error: {_model.GetType()}"
|
||||
};
|
||||
RaisePropertyChanged("Title");
|
||||
}
|
||||
|
||||
Log.assert(_document != null);
|
||||
return _document.content.Match(on_wpf_element, on_handle_ref);
|
||||
|
||||
UIElement on_wpf_element(UIElement element) {
|
||||
if (IsSlow || IsShared) {
|
||||
// Create a WPFPanel (a Winforms window), then attach it with the HwndContentHost.
|
||||
// This gives the document content a separate thread affinity from the shell window,
|
||||
// and lets the win32 layer handle the thread boundary.
|
||||
var retval = Dispatcher.Invoke(() => {
|
||||
var interop_window = new WPFPanel(element);
|
||||
return new HandleRef(interop_window, interop_window.Handle);
|
||||
}, DispatcherPriority.Send);
|
||||
_hwnd_host = new HwndContentHost(retval);
|
||||
InitialHeight = _hwnd_host.InitialHeight;
|
||||
InitialWidth = _hwnd_host.InitialWidth;
|
||||
return _hwnd_host;
|
||||
}
|
||||
if (element is FrameworkElement fe) {
|
||||
InitialHeight = fe.Height;
|
||||
InitialWidth = fe.Width;
|
||||
fe.Height = fe.Width = double.NaN;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
UIElement on_handle_ref(HandleRef handle_ref) {
|
||||
_hwnd_host = new HwndContentHost(handle_ref);
|
||||
InitialHeight = _hwnd_host.Height;
|
||||
InitialWidth = _hwnd_host.Width;
|
||||
return _hwnd_host;
|
||||
}
|
||||
}
|
||||
|
||||
void _try_set_wpf_bus() {
|
||||
if (_owner is null) {
|
||||
return;
|
||||
}
|
||||
_document.content.try_match(on_wpf_element);
|
||||
|
||||
void on_wpf_element(UIElement element) =>
|
||||
element.Dispatcher.InvokeAsync(() =>
|
||||
ui.WindowStyle.SetUnsafeWindow(element, _owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace mbts.ui.shell {
|
||||
using bts;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Async;
|
||||
using Spark.Core.Disposables;
|
||||
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
public class ShellWindowVM : DisposableViewModelBase {
|
||||
readonly IWindowManager _window_manager;
|
||||
readonly Property<bool> _edit_mode;
|
||||
readonly Property<string> _title;
|
||||
readonly Property<string> _title_override;
|
||||
readonly Property<bool> _has_custom_title;
|
||||
|
||||
IShellWindow _model;
|
||||
UIElement _ui;
|
||||
ShellWindowUI _document;
|
||||
HwndContentHost _hwnd_host;
|
||||
FloatingWindow _owner;
|
||||
ImageSource _icon;
|
||||
ICommand _close_command;
|
||||
ICommand _rename_command;
|
||||
ICommand _unrename_command;
|
||||
|
||||
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = null) {
|
||||
Log.assert(model != null);
|
||||
_model = model;
|
||||
_model.IconChanged += on_icon_changed;
|
||||
_window_manager = wm;
|
||||
host_id = (model as ITabAffineWindow)?.tab_group ?? host ?? string.Empty;
|
||||
|
||||
// init Dispatcher
|
||||
var stf = (ShellThreadFactory)threads;
|
||||
if (IsShared) {
|
||||
Dispatcher = stf.get_dispatcher((ISharedWindow)model);
|
||||
} else if (IsSlow) {
|
||||
Dispatcher = stf.create_dispatcher((ISlowWindow)model);
|
||||
} else {
|
||||
Dispatcher = Dispatcher.CurrentDispatcher;
|
||||
}
|
||||
|
||||
_edit_mode = backing(nameof(EditMode), false);
|
||||
|
||||
_title_override = backing<string>(nameof(TitleOverride));
|
||||
_title = backing(
|
||||
nameof(Title),
|
||||
_model.observe_title()
|
||||
.CombineLatest(
|
||||
_title_override.observe_current(true),
|
||||
(title, title_override) => (title, title_override))
|
||||
.Select(t => t.title_override ?? t.title)
|
||||
.OnDispatcher());
|
||||
_has_custom_title = backing(
|
||||
nameof(HasCustomTitle),
|
||||
_title_override.observe_current(true).Select(t => !t.is_null_or_white_space()).OnDispatcher());
|
||||
|
||||
model.on_shell_window_vm(this);
|
||||
|
||||
_dispose = Disposable.Create(() => {
|
||||
_model.IconChanged -= on_icon_changed;
|
||||
if (IsShared) {
|
||||
// Shared threads continue running
|
||||
Dispatcher.Invoke(() => Disposable.dispose(ref _model));
|
||||
} else if (IsSlow) {
|
||||
// Terminating the background thread will dispose the model on that thread
|
||||
Log.Ui.info("Beginning asyncronous dispose of slow window {0} .".fmt(_model.GetType().ToString()));
|
||||
Dispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
|
||||
} else {
|
||||
Disposable.dispose(ref _model);
|
||||
}
|
||||
_ui = null;
|
||||
Disposable.dispose(ref _hwnd_host);
|
||||
});
|
||||
|
||||
void on_icon_changed() {
|
||||
_icon = null;
|
||||
RaisePropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncEvent<(object sender, ShellWindowVM e)> Closing { get; } = new();
|
||||
|
||||
public string host_id { get; set; }
|
||||
public string OwnerName => _model.Owner?.name ?? string.Empty;
|
||||
public IShellWindow Model => _model;
|
||||
public Dispatcher Dispatcher { get; }
|
||||
|
||||
// title
|
||||
public string Title => _title.Value;
|
||||
public string TitleOverride {
|
||||
get => _title_override.Value;
|
||||
set {
|
||||
_title_override.Value = value;
|
||||
_edit_mode.set(false);
|
||||
}
|
||||
}
|
||||
public bool HasCustomTitle => _has_custom_title.Value;
|
||||
|
||||
public ImageSource Icon =>
|
||||
_icon ??= _model.Icon ?? _model.Owner?.icon;
|
||||
|
||||
public bool EditMode => _edit_mode.Value;
|
||||
public double InitialWidth { get; private set; }
|
||||
public double InitialHeight { get; private set; }
|
||||
|
||||
public UIElement AppContent =>
|
||||
_ui ??= _create_contents();
|
||||
|
||||
public bool IsSlow => _model is ISlowWindow;
|
||||
public bool IsShared => _model is ISharedWindow;
|
||||
public bool HasTabAffinity => _model is ITabAffineWindow;
|
||||
|
||||
public ICommand rename_command =>
|
||||
_rename_command ??= new DelegateCommand(_begin_rename);
|
||||
|
||||
public ICommand unrename_command =>
|
||||
_unrename_command ??= new DelegateCommand(() => TitleOverride = string.Empty);
|
||||
|
||||
public ICommand CloseCommand =>
|
||||
_close_command ??= new DelegateCommand(_begin_close);
|
||||
|
||||
internal FloatingWindow owner {
|
||||
get => _owner;
|
||||
set {
|
||||
if (Set(ref _owner, value)) {
|
||||
_try_set_wpf_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<string> observe_title() =>
|
||||
_title.observe_current(true);
|
||||
|
||||
void _begin_rename() {
|
||||
if (string.IsNullOrEmpty(TitleOverride)) {
|
||||
_title_override.Value = Title;
|
||||
}
|
||||
_edit_mode.set(true);
|
||||
}
|
||||
|
||||
async void _begin_close(object sender) {
|
||||
if (_window_manager is null) {
|
||||
return;
|
||||
}
|
||||
if (!_window_manager.contains(_model)) {
|
||||
return;
|
||||
}
|
||||
await Closing?.InvokeAsync((sender, this));
|
||||
// Removing the window will cause this object to be Disposed
|
||||
// Which will terminate the background thread
|
||||
// Which will dispose the model (on that thread)
|
||||
await _window_manager.remove(_model);
|
||||
}
|
||||
|
||||
UIElement _create_contents() {
|
||||
string error_text = null;
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
// Executes possibly on background thread
|
||||
try {
|
||||
_document = _model.GetContent();
|
||||
} catch (Exception ex) {
|
||||
error_text = ex.Message;
|
||||
Log.Ui.error($"Error getting window contents: {ex}");
|
||||
}
|
||||
}, DispatcherPriority.Send);
|
||||
|
||||
if (_document == null && string.IsNullOrEmpty(error_text)) {
|
||||
error_text = $"No content for window {_model.GetType()}";
|
||||
}
|
||||
|
||||
// If there's an error draw a big red X
|
||||
if (!error_text.is_null_or_empty()) {
|
||||
_document = new ShellWindowUI(new TextBlock() {
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Foreground = Brushes.Red,
|
||||
Text = error_text
|
||||
});
|
||||
_model = new ErrorShellWindow {
|
||||
Content = _document,
|
||||
Icon = _model.Icon,
|
||||
Title = $"Error: {_model.GetType()}"
|
||||
};
|
||||
RaisePropertyChanged("Title");
|
||||
}
|
||||
|
||||
Log.assert(_document != null);
|
||||
return _document.content.Match(on_wpf_element, on_handle_ref);
|
||||
|
||||
UIElement on_wpf_element(UIElement element) {
|
||||
if (IsSlow || IsShared) {
|
||||
// Create a WPFPanel (a Winforms window), then attach it with the HwndContentHost.
|
||||
// This gives the document content a separate thread affinity from the shell window,
|
||||
// and lets the win32 layer handle the thread boundary.
|
||||
var retval = Dispatcher.Invoke(() => {
|
||||
var interop_window = new WPFPanel(element);
|
||||
element.SetCurrentValue(ui.WindowStyle.WinFormsHostProperty, interop_window);
|
||||
return new HandleRef(interop_window, interop_window.Handle);
|
||||
}, DispatcherPriority.Send);
|
||||
_hwnd_host = new HwndContentHost(retval);
|
||||
InitialHeight = _hwnd_host.InitialHeight;
|
||||
InitialWidth = _hwnd_host.InitialWidth;
|
||||
return _hwnd_host;
|
||||
}
|
||||
if (element is FrameworkElement fe) {
|
||||
InitialHeight = fe.Height;
|
||||
InitialWidth = fe.Width;
|
||||
fe.Height = fe.Width = double.NaN;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
UIElement on_handle_ref(HandleRef handle_ref) {
|
||||
_hwnd_host = new HwndContentHost(handle_ref);
|
||||
InitialHeight = _hwnd_host.Height;
|
||||
InitialWidth = _hwnd_host.Width;
|
||||
return _hwnd_host;
|
||||
}
|
||||
}
|
||||
|
||||
void _try_set_wpf_bus() {
|
||||
if (_owner is null) {
|
||||
return;
|
||||
}
|
||||
_document.content.try_match(on_wpf_element);
|
||||
|
||||
void on_wpf_element(UIElement element) =>
|
||||
element.Dispatcher.InvokeAsync(() =>
|
||||
ui.WindowStyle.SetUnsafeWindow(element, _owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,16 +47,16 @@ namespace mbts.spark {
|
||||
readonly SecurityImmutable _security;
|
||||
readonly MBOSecurityQueryImmutable _query;
|
||||
readonly Dictionary<(string, bool), FormattedText> _cache;
|
||||
readonly Func<int, string> _fmt_mbo_qty;
|
||||
readonly Func<double, double, string> _fmt_mbo_qty;
|
||||
int _max_order_size = 1;
|
||||
double _height = 0;
|
||||
double _number_width = 0;
|
||||
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<int, string> fmt_mbo_qty) {
|
||||
public MBOPopupModel(MBOService mbo, SecurityImmutable security, MBOSecurityQueryImmutable query, Func<double, double, string> fmt_mbo_qty) {
|
||||
_mbo = mbo;
|
||||
_security = security;
|
||||
_query = query;
|
||||
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<int, string>(v => v.ToString());
|
||||
_fmt_mbo_qty = fmt_mbo_qty ?? new Func<double, double, string>((v, p) => v.ToString());
|
||||
|
||||
// prime the cache with numbers on the left hand side
|
||||
_cache =
|
||||
@@ -112,7 +112,7 @@ namespace mbts.spark {
|
||||
|
||||
pdp.row_numbers = p.orders.Select((o, i) => format_text((i + 1).ToString(), ismine(o))).ToList();
|
||||
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
|
||||
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
pdp.row_labels = p.orders.Select(o => format_text(_fmt_mbo_qty(o.Size, o.Price) + (ismine(o) ? "*" : ""), ismine(o))).ToList();
|
||||
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
|
||||
|
||||
var available_width = pdp.width - pdp.numbers_width - max_label_width;
|
||||
|
||||
@@ -1,335 +1,389 @@
|
||||
using bts.api;
|
||||
using bts.api.domain;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
[ProtoContract]
|
||||
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElement[] normal;
|
||||
[ProtoMember(2)]
|
||||
public readonly SizeFormattingElement[] implied_present;
|
||||
|
||||
public MarketSizeDisplay() { }
|
||||
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
|
||||
this.normal = normal;
|
||||
this.implied_present = implied_present;
|
||||
}
|
||||
|
||||
public enum SizeFormattingElementType {
|
||||
Merged,
|
||||
Direct,
|
||||
Implied,
|
||||
Const
|
||||
}
|
||||
|
||||
public static MarketSizeDisplay get_default() =>
|
||||
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
|
||||
|
||||
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
|
||||
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
|
||||
|
||||
public bool Equals(MarketSizeDisplay other) =>
|
||||
other != null &&
|
||||
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
|
||||
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as MarketSizeDisplay);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
|
||||
|
||||
[ProtoContract]
|
||||
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElementType type;
|
||||
[ProtoMember(2)]
|
||||
public readonly string data;
|
||||
|
||||
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
|
||||
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
|
||||
public override string ToString() => type == SizeFormattingElementType.Const ? data ?? string.Empty : type.ToString();
|
||||
|
||||
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
|
||||
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
|
||||
|
||||
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
|
||||
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
|
||||
public static SizeFormattingElement[] direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
|
||||
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
|
||||
public static SizeFormattingElement[] merged_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class MDDisplayHelper {
|
||||
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
public delegate string SizeFormatter(double merged, double implied);
|
||||
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
|
||||
/// The refs, therefor, avoid making extra copies of the variables.
|
||||
/// These are private members, so any code-readability cost is confined to a limited scope.
|
||||
delegate void _SizeFormatter(ref double merged, ref double implied, ref StringBuilder sb);
|
||||
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
|
||||
|
||||
public static Func<double, string> get_value_formatter(double divisor) =>
|
||||
value => (value / divisor).ToString();
|
||||
|
||||
public static Func<double, string> get_value_formatter(
|
||||
TickDisplaySettingsImmutable display) =>
|
||||
v => display.format(v);
|
||||
|
||||
public static Func<NativePriceSize, string> get_formatter_nps(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display);
|
||||
return nps => raw_fmt_size(
|
||||
nps.implied_size + nps.direct_size,
|
||||
nps.implied_size);
|
||||
}
|
||||
|
||||
public static SizeFormatter get_formatter(MarketSizeDisplay size_display, TickDisplaySettingsImmutable display) {
|
||||
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display);
|
||||
_SizeFormatter implied_present = _get_formatter(
|
||||
size_display.implied_present ?? size_display.normal, display);
|
||||
return (merged, implied) => {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref sb);
|
||||
return sb.ToString();
|
||||
};
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display) {
|
||||
var formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatterNPS>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display));
|
||||
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref nps, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display) {
|
||||
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatter>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display));
|
||||
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref m, ref i, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter_nps(display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter(display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static class SizeTokenFormatters {
|
||||
public static _SizeFormatter char_formatter(string data) {
|
||||
string local = data.ToString();
|
||||
return new _SizeFormatter((ref double m, ref double i, ref StringBuilder sb) => sb.Append(local));
|
||||
}
|
||||
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m));
|
||||
|
||||
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(m - i));
|
||||
|
||||
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref StringBuilder sb) => sb.Append(display.format(i));
|
||||
|
||||
public static _SizeFormatterNPS char_formatter_nps(string data) {
|
||||
string local = data.ToString();
|
||||
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
|
||||
}
|
||||
|
||||
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.size));
|
||||
|
||||
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.direct_size));
|
||||
|
||||
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.implied_size));
|
||||
}
|
||||
}
|
||||
|
||||
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
|
||||
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
|
||||
readonly Property<FormattingVM> _selected_format, _selected_alternate_implied_format;
|
||||
readonly DelegateProperty<bool> _can_choose_alternate_implied_format;
|
||||
|
||||
public MarketSizeDisplayVM() {
|
||||
_selected_format = backing<FormattingVM>(nameof(selected_format));
|
||||
_selected_alternate_implied_format = backing<FormattingVM>(nameof(selected_alternate_implied_format));
|
||||
_can_choose_alternate_implied_format = backing(nameof(can_choose_alternate_implied_format), () =>
|
||||
_comp.Equals(MarketSizeDisplay.SizeFormattingElement.merged, selected_format?.value));
|
||||
sample_items = new[] {
|
||||
new SampleItem(90, 0, this),
|
||||
new SampleItem(75, 25, this)
|
||||
};
|
||||
_selected_format.Changed += refresh_sample_items;
|
||||
_selected_alternate_implied_format.Changed += refresh_sample_items;
|
||||
void refresh_sample_items(FormattingVM oldval, FormattingVM newval) {
|
||||
_can_choose_alternate_implied_format.refresh();
|
||||
foreach (var si in sample_items)
|
||||
si.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<FormattingVM> format_options { get; } = new[] {
|
||||
MarketSizeDisplay.SizeFormattingElement.direct,
|
||||
MarketSizeDisplay.SizeFormattingElement.implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.direct_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged
|
||||
}.Select(ts => new FormattingVM(ts)).ToArray();
|
||||
|
||||
public IEnumerable<FormattingVM> alternate_implied_format_options { get; } = new[] {
|
||||
MarketSizeDisplay.SizeFormattingElement.merged,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_asterisk,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_implied,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_direct,
|
||||
MarketSizeDisplay.SizeFormattingElement.merged_direct_implied
|
||||
}.Select(ts => new FormattingVM(ts)).ToArray();
|
||||
|
||||
public FormattingVM selected_format {
|
||||
get => _selected_format.Value;
|
||||
set => _selected_format.Value = value;
|
||||
}
|
||||
public FormattingVM selected_alternate_implied_format {
|
||||
get => _selected_alternate_implied_format.Value;
|
||||
set => _selected_alternate_implied_format.Value = value;
|
||||
}
|
||||
|
||||
public bool can_choose_alternate_implied_format => _can_choose_alternate_implied_format.Value;
|
||||
|
||||
public IEnumerable<SampleItem> sample_items { get; }
|
||||
|
||||
public MarketSizeDisplay create() {
|
||||
var normal_fmt = selected_format?.value;
|
||||
var alt_implied_fmt = can_choose_alternate_implied_format ? selected_alternate_implied_format?.value : normal_fmt;
|
||||
return new MarketSizeDisplay(normal_fmt, alt_implied_fmt);
|
||||
}
|
||||
|
||||
public void load_from(MarketSizeDisplay msd) {
|
||||
selected_format = format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.normal));
|
||||
if (can_choose_alternate_implied_format)
|
||||
selected_alternate_implied_format = alternate_implied_format_options.FirstOrDefault(f => _comp.Equals(f.value, msd.implied_present));
|
||||
}
|
||||
|
||||
public class FormattingVM : ViewModelBase, IEquatable<FormattingVM> {
|
||||
public FormattingVM(MarketSizeDisplay.SizeFormattingElement[] tokens) =>
|
||||
value = tokens;
|
||||
|
||||
public MarketSizeDisplay.SizeFormattingElement[] value { get; }
|
||||
|
||||
public bool Equals(FormattingVM other) =>
|
||||
other != null &&
|
||||
EqualityComparer.safe_sequence_equals(value, other.value);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FormattingVM);
|
||||
public override int GetHashCode() => EqualityComparer.safe_aggregate_hash(value);
|
||||
public override string ToString() => string.Join(string.Empty, value);
|
||||
}
|
||||
|
||||
public class SampleItem : ViewModelBase {
|
||||
readonly DelegateProperty<string> _display;
|
||||
|
||||
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
|
||||
this.direct = direct;
|
||||
this.implied = implied;
|
||||
_display = backing(nameof(display), fmt);
|
||||
|
||||
string fmt() {
|
||||
var impl = MDDisplayHelper.get_formatter(
|
||||
owner.create(), Security.Defaults.quantity_display_settingsi);
|
||||
return impl(merged, implied);
|
||||
}
|
||||
}
|
||||
|
||||
public uint direct { get; }
|
||||
public uint implied { get; }
|
||||
public uint merged => direct + implied;
|
||||
public string display => _display.Value;
|
||||
public void refresh() => _display.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
using bts.api;
|
||||
using bts.api.domain;
|
||||
using bts.utils;
|
||||
|
||||
using mbts.co2;
|
||||
using mbts.spark;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
|
||||
using ProtoBuf;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
[ProtoContract]
|
||||
class MarketSizeDisplay : IEquatable<MarketSizeDisplay> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElement[] normal;
|
||||
[ProtoMember(2)]
|
||||
public readonly SizeFormattingElement[] implied_present;
|
||||
|
||||
public MarketSizeDisplay() { }
|
||||
public MarketSizeDisplay(SizeFormattingElement[] normal, SizeFormattingElement[] implied_present) {
|
||||
this.normal = normal;
|
||||
this.implied_present = implied_present;
|
||||
}
|
||||
|
||||
public enum SizeFormattingElementType {
|
||||
Merged,
|
||||
Direct,
|
||||
Implied,
|
||||
Const,
|
||||
MergedNotional,
|
||||
}
|
||||
|
||||
public static MarketSizeDisplay get_default() =>
|
||||
new MarketSizeDisplay(SizeFormattingElement.merged, SizeFormattingElement.merged_asterisk);
|
||||
|
||||
public static bool TryParse(string expression, out List<SizeFormattingElement> elements) {
|
||||
elements = new List<SizeFormattingElement>();
|
||||
if (expression is null) {
|
||||
return false;
|
||||
}
|
||||
int tokenStart = 0;
|
||||
for (int i = 0; i < expression.Length - 3; i++) {
|
||||
if (expression[i] != '$' || expression[i + 1] != '{') {
|
||||
continue;
|
||||
}
|
||||
if (i > tokenStart) {
|
||||
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart, i - tokenStart)));
|
||||
}
|
||||
i += 2;
|
||||
tokenStart = i;
|
||||
while (i < expression.Length && expression[i] != '}') {
|
||||
i++;
|
||||
}
|
||||
if (i == expression.Length
|
||||
|| !Enum.TryParse(expression.Substring(tokenStart, i - tokenStart), out SizeFormattingElementType typ)
|
||||
|| typ == SizeFormattingElementType.Const) {
|
||||
elements = null;
|
||||
return false;
|
||||
}
|
||||
elements.Add(SizeFormattingElement.Create(typ));
|
||||
tokenStart = i + 1;
|
||||
}
|
||||
if (tokenStart < expression.Length) {
|
||||
elements.Add(SizeFormattingElement.Create(expression.Substring(tokenStart)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public MarketSizeDisplay with(SizeFormattingElement[] normal = null, SizeFormattingElement[] implied_present = null) =>
|
||||
new MarketSizeDisplay(normal ?? this.normal, implied_present ?? this.implied_present);
|
||||
|
||||
public bool Equals(MarketSizeDisplay other) =>
|
||||
other != null &&
|
||||
EqualityComparer.safe_sequence_equals(normal, other.normal) &&
|
||||
EqualityComparer.safe_sequence_equals(implied_present, other.implied_present);
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
Equals(obj as MarketSizeDisplay);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(implied_present.GetHashCode(), normal.GetHashCode());
|
||||
|
||||
[ProtoContract]
|
||||
public struct SizeFormattingElement : IEquatable<SizeFormattingElement> {
|
||||
[ProtoMember(1)]
|
||||
public readonly SizeFormattingElementType type;
|
||||
[ProtoMember(2)]
|
||||
public readonly string data;
|
||||
|
||||
SizeFormattingElement(SizeFormattingElementType type, string data = null) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
internal static SizeFormattingElement Create(string str) =>
|
||||
new(SizeFormattingElementType.Const, str);
|
||||
|
||||
internal static SizeFormattingElement Create(SizeFormattingElementType type) =>
|
||||
new(type);
|
||||
|
||||
public bool Equals(SizeFormattingElement other) => other.type == type && string.Equals(data, other.data);
|
||||
public override bool Equals(object obj) => obj is SizeFormattingElement other && Equals(other);
|
||||
public override int GetHashCode() => EqualityComparer.aggregate_hash(type.GetHashCode(), EqualityComparer.safe_get_hash_code(data));
|
||||
|
||||
public override string ToString() =>
|
||||
type == SizeFormattingElementType.Const
|
||||
? data ?? string.Empty
|
||||
: "${" + type.ToString() + "}";
|
||||
|
||||
public static bool operator ==(SizeFormattingElement a, SizeFormattingElement b) => a.Equals(b);
|
||||
public static bool operator !=(SizeFormattingElement a, SizeFormattingElement b) => !a.Equals(b);
|
||||
|
||||
public static SizeFormattingElement[] direct => new[] { new SizeFormattingElement(SizeFormattingElementType.Direct) };
|
||||
public static SizeFormattingElement[] implied => new[] { new SizeFormattingElement(SizeFormattingElementType.Implied) };
|
||||
public static SizeFormattingElement[] direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged) };
|
||||
public static SizeFormattingElement[] merged_asterisk => new[] { new SizeFormattingElement(SizeFormattingElementType.Merged), new SizeFormattingElement(SizeFormattingElementType.Const, "*") };
|
||||
public static SizeFormattingElement[] merged_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_direct_implied => new[] {
|
||||
new SizeFormattingElement(SizeFormattingElementType.Merged),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, " ("),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Direct),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ":"),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Implied),
|
||||
new SizeFormattingElement(SizeFormattingElementType.Const, ")")
|
||||
};
|
||||
public static SizeFormattingElement[] merged_notional => new SizeFormattingElement[] {
|
||||
new(SizeFormattingElementType.MergedNotional)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static class MDDisplayHelper {
|
||||
/// This delegate should be created by calling <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
public delegate string SizeFormatter(double merged, double implied, double price);
|
||||
/// The private delegates below use refs bc the <see cref="SizeFormatter"/> returned by <see cref="get_formatter(MarketSizeDisplay)"/>
|
||||
/// internally uses several <see cref="_SizeFormatter"/>s to add different text components to the resultant string.
|
||||
/// The refs, therefor, avoid making extra copies of the variables.
|
||||
/// These are private members, so any code-readability cost is confined to a limited scope.
|
||||
delegate void _SizeFormatter(ref double merged, ref double implied, ref double price, ref StringBuilder sb);
|
||||
delegate void _SizeFormatterNPS(ref NativePriceSize nps, ref StringBuilder sb);
|
||||
|
||||
public static Func<double, double, string> get_value_formatter(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, price_display);
|
||||
return (q, p) => raw_fmt_size(q, 0, p);
|
||||
}
|
||||
|
||||
public static Func<NativePriceSize, string> get_formatter_nps(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
|
||||
var raw_fmt_size = get_formatter(size_display, display, price_display);
|
||||
return nps => raw_fmt_size(
|
||||
nps.implied_size + nps.direct_size,
|
||||
nps.implied_size,
|
||||
nps.price);
|
||||
}
|
||||
|
||||
public static SizeFormatter get_formatter(
|
||||
MarketSizeDisplay size_display, TickDisplaySettingsImmutable display, TickDisplaySettingsImmutable price_display) {
|
||||
_SizeFormatter implied_absent = _get_formatter(size_display.normal, display, price_display);
|
||||
_SizeFormatter implied_present = _get_formatter(
|
||||
size_display.implied_present ?? size_display.normal, display, price_display);
|
||||
return (merged, implied, price) => {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
(implied > 0 ? implied_present : implied_absent)(ref merged, ref implied, ref price, ref sb);
|
||||
return sb.ToString();
|
||||
};
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) {
|
||||
var formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatterNPS>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter_nps(sfe, display, price_display));
|
||||
return new _SizeFormatterNPS((ref NativePriceSize nps, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref nps, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement[] fmt_elements,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) {
|
||||
_SizeFormatter[] formatters = fmt_elements.is_null_or_empty() ?
|
||||
Array.Empty<_SizeFormatter>() :
|
||||
CollectionHelper.convert_array(fmt_elements, sfe => _get_formatter(sfe, display, price_display));
|
||||
return new _SizeFormatter((ref double m, ref double i, ref double p, ref StringBuilder sb) => {
|
||||
foreach (var f in formatters)
|
||||
f(ref m, ref i, ref p, ref sb);
|
||||
});
|
||||
}
|
||||
|
||||
static _SizeFormatterNPS _get_formatter_nps(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter_nps(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter_nps(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
|
||||
SizeTokenFormatters.merged_notional_formatter_nps(price_display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static _SizeFormatter _get_formatter(
|
||||
MarketSizeDisplay.SizeFormattingElement fmt_element,
|
||||
TickDisplaySettingsImmutable display,
|
||||
TickDisplaySettingsImmutable price_display) =>
|
||||
fmt_element.type switch {
|
||||
MarketSizeDisplay.SizeFormattingElementType.Const =>
|
||||
SizeTokenFormatters.char_formatter(fmt_element.data ?? string.Empty),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Direct =>
|
||||
SizeTokenFormatters.direct_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Implied =>
|
||||
SizeTokenFormatters.implied_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.Merged =>
|
||||
SizeTokenFormatters.merged_formatter(display),
|
||||
MarketSizeDisplay.SizeFormattingElementType.MergedNotional =>
|
||||
SizeTokenFormatters.merged_notional_formatter(price_display),
|
||||
_ => throw ExceptionBuilder.Argument.unexpected_case(
|
||||
fmt_element.type, nameof(fmt_element.type)),
|
||||
};
|
||||
|
||||
static class SizeTokenFormatters {
|
||||
public static _SizeFormatter char_formatter(string data) {
|
||||
string local = data.ToString();
|
||||
return (ref double m, ref double i, ref double p, ref StringBuilder sb) =>
|
||||
sb.Append(local);
|
||||
}
|
||||
public static _SizeFormatter merged_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(m));
|
||||
|
||||
public static _SizeFormatter direct_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(m - i));
|
||||
|
||||
public static _SizeFormatter implied_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(i));
|
||||
|
||||
public static _SizeFormatter merged_notional_formatter(TickDisplaySettingsImmutable display) =>
|
||||
(ref double m, ref double i, ref double p, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(m * p));
|
||||
|
||||
public static _SizeFormatterNPS char_formatter_nps(string data) {
|
||||
string local = data.ToString();
|
||||
return new _SizeFormatterNPS((ref NativePriceSize _, ref StringBuilder sb) => sb.Append(local));
|
||||
}
|
||||
|
||||
public static _SizeFormatterNPS merged_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.size));
|
||||
|
||||
public static _SizeFormatterNPS direct_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.direct_size));
|
||||
|
||||
public static _SizeFormatterNPS implied_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format((double)nps.implied_size));
|
||||
|
||||
public static _SizeFormatterNPS merged_notional_formatter_nps(TickDisplaySettingsImmutable display) =>
|
||||
(ref NativePriceSize nps, ref StringBuilder sb) =>
|
||||
sb.Append(display.format(nps.notional_size));
|
||||
}
|
||||
}
|
||||
|
||||
class MarketSizeDisplayVM : ViewModelBase, IValueFactory<MarketSizeDisplay> {
|
||||
readonly IEqualityComparer<MarketSizeDisplay.SizeFormattingElement[]> _comp = EqualityComparer.Create<MarketSizeDisplay.SizeFormattingElement[]>((a, b) => EqualityComparer.safe_sequence_equals(a, b));
|
||||
readonly Property<string> _selected_format, _selected_alternate_implied_format;
|
||||
|
||||
public MarketSizeDisplayVM() {
|
||||
_selected_format = backing<string>(nameof(selected_format));
|
||||
_selected_alternate_implied_format = backing<string>(nameof(selected_alternate_implied_format));
|
||||
sample_items = new[] {
|
||||
new SampleItem(90, 0, this),
|
||||
new SampleItem(75, 25, this)
|
||||
};
|
||||
_selected_format.Changed += refresh_sample_items;
|
||||
_selected_alternate_implied_format.Changed += refresh_sample_items;
|
||||
void refresh_sample_items(string oldval, string newval) {
|
||||
foreach (var si in sample_items)
|
||||
si.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> format_options { get; } = new[] {
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.direct_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
|
||||
};
|
||||
|
||||
public IEnumerable<string> alternate_implied_format_options { get; } = new[] {
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_asterisk),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_direct_implied),
|
||||
string.Concat(MarketSizeDisplay.SizeFormattingElement.merged_notional),
|
||||
};
|
||||
|
||||
public string selected_format {
|
||||
get => _selected_format.Value;
|
||||
set => _selected_format.Value = value;
|
||||
}
|
||||
public string selected_alternate_implied_format {
|
||||
get => _selected_alternate_implied_format.Value;
|
||||
set => _selected_alternate_implied_format.Value = value;
|
||||
}
|
||||
|
||||
public IEnumerable<SampleItem> sample_items { get; }
|
||||
|
||||
public MarketSizeDisplay create() {
|
||||
var normal_fmt = selected_format;
|
||||
var alt_implied_fmt = selected_alternate_implied_format;
|
||||
if (!MarketSizeDisplay.TryParse(normal_fmt, out var normal_els)) {
|
||||
normal_els = new(MarketSizeDisplay.SizeFormattingElement.merged);
|
||||
}
|
||||
if (!MarketSizeDisplay.TryParse(alt_implied_fmt, out var alt_implied_els)) {
|
||||
alt_implied_els = new(normal_els);
|
||||
}
|
||||
return new MarketSizeDisplay(normal_els.ToArray(), alt_implied_els.ToArray());
|
||||
}
|
||||
|
||||
public void load_from(MarketSizeDisplay msd) {
|
||||
selected_format = string.Concat(msd.normal.empty_if_null());
|
||||
selected_alternate_implied_format = string.Concat(msd.implied_present.empty_if_null());
|
||||
}
|
||||
|
||||
public class SampleItem : ViewModelBase {
|
||||
readonly DelegateProperty<string> _display;
|
||||
|
||||
public SampleItem(uint direct, uint implied, MarketSizeDisplayVM owner) {
|
||||
this.direct = direct;
|
||||
this.implied = implied;
|
||||
_display = backing(nameof(display), fmt);
|
||||
|
||||
string fmt() {
|
||||
var impl = MDDisplayHelper.get_formatter(
|
||||
owner.create(), Security.Defaults.quantity_display_settingsi, Security.Defaults.price_display_settingsi);
|
||||
return impl(merged, implied, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public uint direct { get; }
|
||||
public uint implied { get; }
|
||||
public uint merged => direct + implied;
|
||||
public string display => _display.Value;
|
||||
public void refresh() => _display.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ namespace mbts.co2 {
|
||||
public Size size => _size;
|
||||
public Size direct_size => _direct_size;
|
||||
public Size implied_size => _size - _direct_size;
|
||||
public double notional_size => price * size;
|
||||
public double price => _price.price();
|
||||
public bool is_nan => _price.is_nan;
|
||||
|
||||
|
||||
@@ -10,58 +10,64 @@ using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using mbts;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
||||
namespace bts.api.core {
|
||||
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
Log.QBus.diag($"Created QuickbusClientChannel");
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
public override void Dispose() {
|
||||
Log.QBus.diag($"Disposed QuickbusClientChannel");
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
int _req_id = 0;
|
||||
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
|
||||
|
||||
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
//ApiNativeMethods.api_client_log_error($"Call {id}");
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
//ApiNativeMethods.api_client_log_error($"Reply {id}");
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.Callback, IntPtr.Zero);
|
||||
// Return a task that will free the handle once the task is completed
|
||||
return tcs.Task.ContinueWith(task => {
|
||||
cb_wrapper.Free(); // Ensure the GCHandle is freed when the callback is done
|
||||
return task.Result;
|
||||
});
|
||||
}
|
||||
|
||||
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
|
||||
@@ -206,11 +212,29 @@ namespace bts.api.core {
|
||||
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
|
||||
/// collected delegate of type...").
|
||||
/// </remarks>
|
||||
class PBCallbackWrapper {
|
||||
public readonly ApiNativeMethods.raw_pb_callback callback;
|
||||
class PBCallbackWrapper
|
||||
{
|
||||
public readonly ApiNativeMethods.raw_pb_callback Callback;
|
||||
private GCHandle _handle;
|
||||
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
|
||||
this.callback = callback;
|
||||
}
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback)
|
||||
{
|
||||
// Pin the callback to prevent garbage collection
|
||||
Log.QBus.diag($"Created PBCallbackWrapper");
|
||||
_handle = GCHandle.Alloc(callback);
|
||||
Callback = callback;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
// Free the GCHandle when the delegate is no longer needed
|
||||
Log.QBus.diag($"Deleting PBCallbackWrapper");
|
||||
|
||||
if (_handle.IsAllocated)
|
||||
{
|
||||
_handle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ namespace mbts.user.settings {
|
||||
public RegionalTickFactors()
|
||||
: this(new TickFactors(), new TickFactors(), double.MinValue) { }
|
||||
|
||||
public RegionalTickFactors(TickFactors tick)
|
||||
: this(tick, tick, double.PositiveInfinity) { }
|
||||
|
||||
public RegionalTickFactors(TickFactors low_tick, TickFactors high_tick, double low_tick_inclusive_end) {
|
||||
_low_tick = low_tick;
|
||||
_high_tick = high_tick;
|
||||
|
||||
@@ -78,14 +78,16 @@ namespace mbts.user.settings {
|
||||
bool? show_commas = null,
|
||||
string? nan_text = null,
|
||||
bool? show_decimal_part = null,
|
||||
bool? show_zero_integer = null) =>
|
||||
bool? show_zero_integer = null,
|
||||
TickFactors? tick_factors = null) =>
|
||||
new() {
|
||||
pad_with_leading_zeros = pad_with_leading_zeros ?? settings.pad_with_leading_zeros,
|
||||
display_multiplier = display_multiplier ?? settings.display_multiplier,
|
||||
show_commas = show_commas ?? settings.show_commas,
|
||||
nan_text = nan_text ?? settings.nan_text,
|
||||
show_decimal_part = show_decimal_part ?? settings.show_decimal_part,
|
||||
show_zero_integer = show_zero_integer ?? settings.show_zero_integer
|
||||
show_zero_integer = show_zero_integer ?? settings.show_zero_integer,
|
||||
tick_factors = tick_factors ?? settings.tick_factors,
|
||||
};
|
||||
|
||||
public static double to_display_value(this TickDisplaySettings settings, double value) =>
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace mbts {
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -63,7 +64,7 @@ namespace mbts {
|
||||
if (ticks_per_base.is_int()) {
|
||||
return true;
|
||||
}
|
||||
return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
|
||||
return tick.tick_unit > tick.tick_base && tick.tick_unit_frac().is_int();
|
||||
}
|
||||
|
||||
public static bool is_valid(this RegionalTickFactors regional_tick) =>
|
||||
@@ -95,7 +96,7 @@ namespace mbts {
|
||||
|
||||
public static double reduced_base(this TickFactors tick) => MathUtils.factor_out(tick.tick_base, 10);
|
||||
|
||||
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y) {
|
||||
public static bool tick_equals(this RegionalTickFactors regional_tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
if (x.is_nan()) {
|
||||
return y.is_nan();
|
||||
}
|
||||
@@ -107,15 +108,15 @@ namespace mbts {
|
||||
return xtick is not null
|
||||
&& ytick is not null
|
||||
&& xtick.Equals(ytick)
|
||||
&& xtick.tick_equals(x, y);
|
||||
&& xtick.tick_equals(x, y, rounding_mode);
|
||||
}
|
||||
|
||||
public static bool tick_equals(this TickFactors tick, double x, double y) {
|
||||
public static bool tick_equals(this TickFactors tick, double x, double y, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
if (x.is_nan()) {
|
||||
return y.is_nan();
|
||||
}
|
||||
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac());
|
||||
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac());
|
||||
var x_units = MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
|
||||
var y_units = MathUtils.round_fractional_units(y, tick.tick_unit_frac(), rounding_mode, TickUtils.precision_const<double>());
|
||||
return x_units == y_units;
|
||||
}
|
||||
|
||||
@@ -137,10 +138,10 @@ namespace mbts {
|
||||
x => NumericCastConverter<T, int>.convert(
|
||||
MathUtils.round_fractional_units(x, tick.tick_unit_frac<T>())));
|
||||
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick) =>
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this TickFactors tick, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
EqualityComparer.Create<double>(
|
||||
(a, b) => tick_equals(tick, a, b),
|
||||
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac()));
|
||||
(a, b) => tick_equals(tick, a, b, rounding_mode),
|
||||
x => (int)MathUtils.round_fractional_units(x, tick.tick_unit_frac(), rounding_mode));
|
||||
|
||||
public static TickFactors get_tick_for(this RegionalTickFactors rtick, double value) =>
|
||||
value > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
|
||||
@@ -179,10 +180,7 @@ namespace mbts {
|
||||
if (T.IsInfinity(value)) {
|
||||
return value.ToString();
|
||||
}
|
||||
return tick?.is_valid() ?? false ?
|
||||
_format_tick(tick, value, settings) :
|
||||
_format_no_tick(
|
||||
value, settings.display_multiplier.as_type<double, T>(), settings.show_commas);
|
||||
return _format_tick(tick, value, settings);
|
||||
}
|
||||
|
||||
public static string format<T>(
|
||||
|
||||
@@ -6,6 +6,7 @@ using bts.utils;
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -189,52 +190,58 @@ namespace mbts.spark {
|
||||
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) =>
|
||||
round_to_tick(spec.ticksize(value), value);
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick(spec.ticksize(value), value, rounding_mode);
|
||||
|
||||
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value) =>
|
||||
round_to_tick((decimal)spec.ticksize((double)value), value);
|
||||
public static decimal round_to_tick(this ContractSpecificationImmutable spec, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick((decimal)spec.ticksize((double)value), value, rounding_mode);
|
||||
|
||||
public static double round_to_tick(this RegionalTickFactors rtick, double value) =>
|
||||
round_to_tick(rtick.ticksize(value), value);
|
||||
public static double round_to_tick(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick(rtick.ticksize(value), value, rounding_mode);
|
||||
|
||||
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value) =>
|
||||
round_to_tick((decimal)rtick.ticksize((double)value), value);
|
||||
public static decimal round_to_tick(this RegionalTickFactors rtick, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) =>
|
||||
round_to_tick((decimal)rtick.ticksize((double)value), value, rounding_mode);
|
||||
|
||||
public static double round_to_tick(double tick_size, double value) {
|
||||
double intpart = (long)value;
|
||||
double floatpart = value % 1;
|
||||
floatpart = ((int)Math.Round(floatpart / tick_size)) * tick_size;
|
||||
return intpart + floatpart;
|
||||
public static double round_to_tick(double tick_size, double value, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
double n = rounding_mode switch {
|
||||
RoundingMode.Closest => Math.Round(value / tick_size),
|
||||
RoundingMode.Up => Math.Ceiling((value - _precision_const) / tick_size),
|
||||
RoundingMode.Down => Math.Floor((value + _precision_const) / tick_size),
|
||||
RoundingMode.AbsUp => value < 0
|
||||
? Math.Floor((value + _precision_const) / tick_size)
|
||||
: Math.Ceiling((value - _precision_const) / tick_size),
|
||||
RoundingMode.AbsDown => value < 0
|
||||
? Math.Ceiling((value - _precision_const) / tick_size)
|
||||
: Math.Floor((value + _precision_const) / tick_size),
|
||||
_ => double.NaN,
|
||||
};
|
||||
return n * tick_size;
|
||||
}
|
||||
|
||||
public static decimal round_to_tick(decimal tick_size, decimal value) {
|
||||
decimal intpart = (long)value;
|
||||
decimal floatpart = value % 1;
|
||||
floatpart = ((int)(floatpart / tick_size)) * tick_size;
|
||||
return intpart + floatpart;
|
||||
public static decimal round_to_tick(decimal tick_size, decimal value, RoundingMode rounding_mode = RoundingMode.Closest) {
|
||||
decimal n = rounding_mode switch {
|
||||
RoundingMode.Closest => Math.Round(value / tick_size),
|
||||
RoundingMode.Up => Math.Ceiling(value / tick_size),
|
||||
RoundingMode.Down => Math.Floor(value / tick_size),
|
||||
RoundingMode.AbsUp => value < 0
|
||||
? Math.Floor(value / tick_size)
|
||||
: Math.Ceiling(value / tick_size),
|
||||
RoundingMode.AbsDown => value < 0
|
||||
? Math.Ceiling(value / tick_size)
|
||||
: Math.Floor(value / tick_size),
|
||||
_ => throw new ArgumentException("Invalid rounding mode", nameof(rounding_mode)),
|
||||
};
|
||||
return n * tick_size;
|
||||
}
|
||||
|
||||
public static long? count_ticks(this RegionalTickFactors rtick, double value) {
|
||||
if (!rtick.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
public static long? count_ticks(this RegionalTickFactors rtick, double value, RoundingMode rounding_mode = RoundingMode.AbsDown) {
|
||||
var rounded_value = rtick.round_to_tick(value, rounding_mode);
|
||||
var n = _count_ticks(
|
||||
rtick.low_tick.tick_unit_frac(),
|
||||
rtick.low_tick_inclusive_end,
|
||||
rtick.high_tick.tick_unit_frac(),
|
||||
0,
|
||||
value);
|
||||
var orig_n = n;
|
||||
// Enforce proper rounding
|
||||
var rounded_val = rtick.round_to_tick(value);
|
||||
var half_tick = rtick.get_tick_for(value).tick_unit_frac() / 2;
|
||||
if (n < 0 && (value - rounded_val) > half_tick) {
|
||||
++n;
|
||||
} else if (n > 0 && (rounded_val - value) > half_tick) {
|
||||
--n;
|
||||
}
|
||||
Log.assert(n == orig_n);
|
||||
rounded_value);
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -253,71 +260,19 @@ namespace mbts.spark {
|
||||
return tick;
|
||||
}
|
||||
|
||||
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) {
|
||||
factor = _fit_ticks_per_row_to_dollars(spec, factor) ?? 1;
|
||||
return new(
|
||||
public static RegionalTickFactors multiply_tick_size(RegionalTickFactors spec, uint factor) =>
|
||||
new(
|
||||
new(spec.low_tick.tick_base, spec.low_tick.tick_unit * factor),
|
||||
new(spec.high_tick.tick_base, spec.high_tick.tick_unit * factor),
|
||||
spec.low_tick_inclusive_end);
|
||||
}
|
||||
|
||||
public static double normalize_price(RegionalTickFactors rtick, double price) {
|
||||
if (!price.is_real()) {
|
||||
var maybe_ticks = rtick.count_ticks(price);
|
||||
if (!maybe_ticks.HasValue) {
|
||||
return price;
|
||||
}
|
||||
var ticks = rtick.count_ticks(price);
|
||||
if (!ticks.HasValue) {
|
||||
return price;
|
||||
}
|
||||
var normalized = rtick.tick(0, ticks.Value);
|
||||
var tick = price > rtick.low_tick_inclusive_end ? rtick.high_tick : rtick.low_tick;
|
||||
// Diff should always be < 1/2 tick
|
||||
Log.assert((normalized - price).abs() < tick.tick_unit_frac() * 0.5);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static uint fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(sec, ticks_per_row) ?? 1;
|
||||
|
||||
public static uint fit_ticks_per_row_to_dollars(RegionalTickFactors rtick, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(rtick, ticks_per_row) ?? 1;
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(SecurityImmutable sec, uint ticks_per_row) =>
|
||||
_fit_ticks_per_row_to_dollars(sec.regional_ticks(), ticks_per_row);
|
||||
|
||||
static uint? _fit_ticks_per_row_to_dollars(RegionalTickFactors tf, uint ticks_per_row) {
|
||||
if (ticks_per_row == 0)
|
||||
return null;
|
||||
// it is assumed that low tick will be the same as high tick;
|
||||
// however, best to double check - return null if this isn't the case (let the caller decide what to do).
|
||||
if (!tf.low_tick.Equals(tf.high_tick) || !tf.is_valid() || tf.low_tick.is_integer())
|
||||
return null;
|
||||
var ticks_per_dollar = (uint)(tf.low_tick.tick_base / tf.low_tick.tick_unit);
|
||||
// if ticks_per_row is greater than ticks_per_dollar, round to a multiple of ticks_per_dollar
|
||||
if (ticks_per_row > ticks_per_dollar) {
|
||||
if (ticks_per_dollar == 0) {
|
||||
return null;
|
||||
}
|
||||
return (uint)(ticks_per_dollar * Math.Floor((double)(ticks_per_row / ticks_per_dollar)));
|
||||
} else if (ticks_per_row == ticks_per_dollar) {
|
||||
return ticks_per_row;
|
||||
} else {
|
||||
uint max_decrease_adj = ticks_per_row;
|
||||
uint max_increase_adj = ticks_per_dollar - ticks_per_row - 1;
|
||||
for (uint adj = 0; adj < Math.Max(max_decrease_adj, max_increase_adj); adj++) {
|
||||
if (adj < max_decrease_adj) {
|
||||
var result = ticks_per_row - adj;
|
||||
if (ticks_per_dollar % result == 0)
|
||||
return result;
|
||||
}
|
||||
if (adj < max_increase_adj) {
|
||||
var result = ticks_per_row + adj;
|
||||
if (ticks_per_dollar % result == 0)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
var ticks = maybe_ticks.Value;
|
||||
return rtick.tick(0, (long)ticks);
|
||||
}
|
||||
|
||||
static long? _count_ticks(
|
||||
@@ -367,8 +322,20 @@ namespace mbts.spark {
|
||||
|
||||
static long count_single_tick_sz(
|
||||
double tick_sz, double from, double to) {
|
||||
var from_n = (long)MathUtils.round_fractional_units(from, tick_sz);
|
||||
var to_n = (long)MathUtils.round_fractional_units(to, tick_sz);
|
||||
from = round_to_tick(tick_sz, from, RoundingMode.AbsDown);
|
||||
to = round_to_tick(tick_sz, to, RoundingMode.AbsDown);
|
||||
if (from < 0) {
|
||||
from -= _precision_const;
|
||||
} else {
|
||||
from += _precision_const;
|
||||
}
|
||||
if (to < 0) {
|
||||
to -= _precision_const;
|
||||
} else {
|
||||
to += _precision_const;
|
||||
}
|
||||
var from_n = (long)(from / tick_sz);
|
||||
var to_n = (long)(to / tick_sz);
|
||||
return to_n - from_n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
|
||||
static IEnumerable<ContractLegImmutable> _get_root_legs(
|
||||
ContractLegImmutable leg, double root_quantity) {
|
||||
_validate_root_quantity(root_quantity);
|
||||
if (leg.HasSecurity) {
|
||||
return _get_root_legs(leg.Security, leg.signed_quantity() * root_quantity);
|
||||
}
|
||||
@@ -54,10 +53,8 @@
|
||||
return new[] { new ContractLegImmutable(mleg) };
|
||||
}
|
||||
|
||||
static IEnumerable<ContractLegImmutable> _get_root_legs(
|
||||
SecurityImmutable security, double root_quantity) {
|
||||
_validate_root_quantity(root_quantity);
|
||||
return security.ContractCase switch {
|
||||
static IEnumerable<ContractLegImmutable> _get_root_legs(SecurityImmutable security, double root_quantity) =>
|
||||
security.ContractCase switch {
|
||||
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
|
||||
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => _get_root_legs(l.Security, root_quantity * l.signed_quantity())).ToArray(),
|
||||
_ => new[] {
|
||||
@@ -69,13 +66,5 @@
|
||||
})
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static void _validate_root_quantity(double root_quantity) {
|
||||
if (!root_quantity.is_real_non_zero()) {
|
||||
throw new ArgumentException(
|
||||
$"Expected real non-zero value (actually {root_quantity})", nameof(root_quantity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace bts.api {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
|
||||
@@ -36,6 +37,15 @@ namespace bts.api {
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<DateTime> normal_expirations(this SecurityImmutable security) =>
|
||||
security.ContractCase switch {
|
||||
Security.ContractOneofCase.Future => new[] { security.Future.expiration_date() },
|
||||
Security.ContractOneofCase.Option => new[] { security.Option.expiration_date() },
|
||||
Security.ContractOneofCase.Spread => security.Spread.Legs.SelectMany(l => l.Security.normal_expirations()),
|
||||
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.SelectMany(l => l.Security.normal_expirations()),
|
||||
_ => Enumerable.Empty<DateTime>()
|
||||
};
|
||||
|
||||
public static ProductImmutable product(this SecurityImmutable security) =>
|
||||
new(new() {
|
||||
Exchange = security.Exchange,
|
||||
@@ -59,13 +69,7 @@ namespace bts.api {
|
||||
new((int)future.ExpirationYear, (int)future.ExpirationMonth, (int)future.ExpirationDay);
|
||||
|
||||
internal static DateTime earliest_expiration(this SecurityImmutable security) =>
|
||||
security.ContractCase switch {
|
||||
Security.ContractOneofCase.Synthetic => security.Synthetic.Legs.First().Security.earliest_expiration(),
|
||||
Security.ContractOneofCase.Spread => security.Spread.Legs.First().Security.earliest_expiration(),
|
||||
Security.ContractOneofCase.Future => security.Future.expiration_date(),
|
||||
Security.ContractOneofCase.Option => security.Option.expiration_date(),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
security.normal_expirations().Min();
|
||||
|
||||
public static double strike(this SecurityImmutable security) =>
|
||||
security.HasOption ? security.Option.Strike : double.NaN;
|
||||
@@ -105,6 +109,7 @@ namespace bts.api {
|
||||
Security.ExchangeDataOneofCase.Deribit => security.Deribit.SecurityDesc,
|
||||
Security.ExchangeDataOneofCase.Ftx => security.Ftx.SecurityDesc,
|
||||
Security.ExchangeDataOneofCase.Binance => security.Binance.SecurityDesc,
|
||||
Security.ExchangeDataOneofCase.BinanceCoinFutures => security.BinanceCoinFutures.SecurityDesc,
|
||||
Security.ExchangeDataOneofCase.BinanceUs => security.BinanceUs.SecurityDesc,
|
||||
Security.ExchangeDataOneofCase.BinanceUsdsFutures => security.BinanceUsdsFutures.SecurityDesc,
|
||||
Security.ExchangeDataOneofCase.Coinbase => security.Coinbase.SecurityDesc,
|
||||
@@ -151,12 +156,13 @@ namespace bts.api {
|
||||
domain.Venue.Deribit => Venue.Deribit,
|
||||
domain.Venue.Ftx => Venue.Ftx,
|
||||
domain.Venue.Binance => Venue.Binance,
|
||||
domain.Venue.BinanceCoinFutures => Venue.BinanceCoinFutures,
|
||||
domain.Venue.BinanceUs => Venue.BinanceUs,
|
||||
domain.Venue.BinanceUsdsFutures => Venue.BinanceUsdsFutures,
|
||||
domain.Venue.Glbx => Venue.Glbx,
|
||||
domain.Venue.Coinbase => Venue.Coinbase,
|
||||
domain.Venue.Okx => Venue.Okx,
|
||||
domain.Venue.Cboe => Venue.Cboe,
|
||||
domain.Venue.Cboe => Venue.Cboe,
|
||||
domain.Venue.Taifex => Venue.Taifex,
|
||||
|
||||
|
||||
@@ -171,6 +177,7 @@ namespace bts.api {
|
||||
Venue.Deribit => domain.Venue.Deribit,
|
||||
Venue.Ftx => domain.Venue.Ftx,
|
||||
Venue.Binance => domain.Venue.Binance,
|
||||
Venue.BinanceCoinFutures => domain.Venue.BinanceCoinFutures,
|
||||
Venue.BinanceUs => domain.Venue.BinanceUs,
|
||||
Venue.BinanceUsdsFutures => domain.Venue.BinanceUsdsFutures,
|
||||
Venue.Coinbase => domain.Venue.Coinbase,
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
return leg.signed_quantity() switch {
|
||||
1.0 => sec_str,
|
||||
-1.0 => $"-{sec_str}",
|
||||
_ => ""
|
||||
_ => $"{leg.signed_quantity()}{sec_str}",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
return leg.signed_quantity() switch {
|
||||
1.0 => $"+{sec_str}",
|
||||
-1.0 => $"-{sec_str}",
|
||||
_ => $"{leg.signed_quantity():+0;-0}{sec_str}",
|
||||
_ => $"{leg.signed_quantity():+0.######;-0.######}{sec_str}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +640,9 @@ namespace System.Collections.Generic {
|
||||
public static List<T> ToListSorted<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> key_selector, IComparer<TKey> key_comparer) =>
|
||||
enumerable.ToListSorted(key_comparer.wrap(key_selector));
|
||||
|
||||
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable) where T : IComparable<T> =>
|
||||
enumerable.ToListSorted(Comparer<T>.Default);
|
||||
|
||||
public static List<T> ToListSorted<T>(this IEnumerable<T> enumerable, IComparer<T> comparer) {
|
||||
var list = new List<T>(enumerable);
|
||||
list.Sort(comparer);
|
||||
|
||||
+2
-1
@@ -73,7 +73,8 @@ namespace bts.collections {
|
||||
public IEnumerable<TRow> all_rows => _tree.all_rows;
|
||||
public int Count => _rows.Count;
|
||||
public int total_row_count => _tree.total_row_count;
|
||||
|
||||
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
|
||||
_tree.GetAllRows(excludeFiltered);
|
||||
public IDisposable defer_refresh() =>
|
||||
_refresh.defer();
|
||||
|
||||
|
||||
+2
@@ -61,6 +61,8 @@ namespace bts.collections {
|
||||
IEnumerable<INode<TRow>> _root_nodes =>
|
||||
_filter == null ? _tree.RootNodes : _cached_filtered ?? (_cached_filtered = _tree.RootNodes.filtered(_filter).sorted_hard(_comparer));
|
||||
|
||||
public IEnumerable<TRow> GetAllRows(bool excludeFiltered = false) =>
|
||||
excludeFiltered ? all_rows : _tree;
|
||||
// collapse
|
||||
public void collapse_row(TKey key) => _toggle_is_expanded(key, false);
|
||||
public void collapse_all() => _toggle_is_expanded(false);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Spark.Core.Math;
|
||||
|
||||
public enum RoundingMode {
|
||||
None,
|
||||
// Round to the closest integer (up if decimal part is >= 0.5, otherwise down)
|
||||
Closest,
|
||||
// Round up
|
||||
Up,
|
||||
// Round down
|
||||
Down,
|
||||
// Round positive numbers up, and negative numbers down
|
||||
AbsUp,
|
||||
// Round positive numbers down, and negative numbers up
|
||||
AbsDown
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#nullable enable
|
||||
namespace bts;
|
||||
|
||||
using System;
|
||||
|
||||
public class ThrottledAction {
|
||||
readonly TimeSpan interval;
|
||||
readonly bool measureEndToNextStart;
|
||||
DateTime lastInvokedTime = DateTime.MinValue;
|
||||
|
||||
public ThrottledAction(TimeSpan interval, bool measureEndToNextStart = true) {
|
||||
this.interval = interval;
|
||||
this.measureEndToNextStart = measureEndToNextStart;
|
||||
}
|
||||
|
||||
public bool Execute(Action action) {
|
||||
var now = DateTime.Now;
|
||||
var elapsed = now - lastInvokedTime;
|
||||
if (elapsed < interval) {
|
||||
return false;
|
||||
}
|
||||
if (measureEndToNextStart) {
|
||||
action();
|
||||
lastInvokedTime = DateTime.Now;
|
||||
} else {
|
||||
lastInvokedTime = now;
|
||||
action();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,143 +1,163 @@
|
||||
namespace System {
|
||||
using bts;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public static class ConvertUtils {
|
||||
public static object try_convert(Type type, object obj) {
|
||||
try_convert(type, obj, out object result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public static bool try_convert<T>(
|
||||
object obj,
|
||||
[NotNullWhen(true)]out T? result,
|
||||
LogLevel? log_level = null,
|
||||
LogSubsystem log_subsystem = LogSubsystem.gen,
|
||||
string? name = null) {
|
||||
if (obj is T t) {
|
||||
result = t;
|
||||
return true;
|
||||
}
|
||||
if (try_convert(typeof(T), obj, out var result_obj)) {
|
||||
result = (T)result_obj;
|
||||
return true;
|
||||
}
|
||||
if (log_level.HasValue) {
|
||||
name = name is null ? "obj" : $"'{name}'";
|
||||
var msg = $"Expected {name} to be of type '{typeof(T)}', actually " +
|
||||
$"'{obj?.GetType()?.ToString() ?? "null"}'.";
|
||||
Log.log(new(log_subsystem, log_level.Value, msg));
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
#nullable disable
|
||||
|
||||
public static bool try_convert(Type type, object obj, out object result) {
|
||||
if (obj == null) {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
} else if (type.IsPrimitive && obj is IConvertible) {
|
||||
result = Convert.ChangeType(obj, type);
|
||||
return true;
|
||||
} else if (type.IsAssignableFrom(obj.GetType())) {
|
||||
result = obj;
|
||||
return true;
|
||||
} else if (type.IsEnum && obj is string) {
|
||||
result = try_parse_enum(type, obj.ToString());
|
||||
return true;
|
||||
} else if (type.IsNullable()) {
|
||||
if (try_convert(type.GetGenericArguments()[0], obj, out result)) {
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
} else {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static T try_convert<T>(object obj) =>
|
||||
(T)try_convert(typeof(T), obj);
|
||||
|
||||
public static T try_convert<T>(object obj, T default_value) =>
|
||||
try_convert(obj, out T result) ? result : default_value;
|
||||
|
||||
public static bool try_convert<T>(object obj, out T result) {
|
||||
bool rv = try_convert(typeof(T), obj, out object r_obj);
|
||||
result = (T)r_obj;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static object get_default(Type type) =>
|
||||
type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
|
||||
public static T ValueOrDefault<T>(T? nullable) where T : struct => nullable.HasValue ? nullable.Value : default;
|
||||
|
||||
public static bool is_compatible<T>(object obj) { return obj is T || (obj == null && default(T) == null); }
|
||||
|
||||
public static bool try_convert_args<T1, T2>(object[] args, out T1 arg1, out T2 arg2) {
|
||||
if (args == null || args.Length != 2) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
|
||||
if (args == null || args.Length != 3) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
|
||||
if (args == null || args.Length != 4) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
arg4 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
rv |= try_convert(args[3], out arg4);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static object try_parse_enum(Type enum_type, string value, bool ignore_case = true) {
|
||||
if (!enum_type.IsEnum) throw new ArgumentException("Must an enum type.", nameof(enum_type));
|
||||
Array vals = Enum.GetValues(enum_type);
|
||||
var str_comp = ignore_case ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
|
||||
foreach (object v in vals) {
|
||||
if (string.Equals(v.ToString(), value, str_comp))
|
||||
return v;
|
||||
}
|
||||
return Activator.CreateInstance(enum_type);
|
||||
}
|
||||
|
||||
public static bool update_value_type<T>(ref T field, T newval) where T : struct, IEquatable<T> {
|
||||
bool rv = !field.Equals(newval);
|
||||
field = newval;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static class CastDelegate<T, TResult> {
|
||||
public static readonly Func<T, TResult> value =
|
||||
v => v is TResult r ? r : throw new InvalidCastException($"Failed to cast element in source collection from type {typeof(T)} to {typeof(TResult)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace System {
|
||||
using bts;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public static class ConvertUtils {
|
||||
public static object try_convert(Type type, object obj) {
|
||||
try_convert(type, obj, out object result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public static bool try_convert<T>(
|
||||
object obj,
|
||||
[NotNullWhen(true)]out T? result,
|
||||
LogLevel? log_level = null,
|
||||
LogSubsystem log_subsystem = LogSubsystem.gen,
|
||||
string? name = null) {
|
||||
if (obj is T t) {
|
||||
result = t;
|
||||
return true;
|
||||
}
|
||||
if (try_convert(typeof(T), obj, out var result_obj)) {
|
||||
result = (T)result_obj;
|
||||
return true;
|
||||
}
|
||||
if (log_level.HasValue) {
|
||||
name = name is null ? "obj" : $"'{name}'";
|
||||
var msg = $"Expected {name} to be of type '{typeof(T)}', actually " +
|
||||
$"'{obj?.GetType()?.ToString() ?? "null"}'.";
|
||||
Log.log(new(log_subsystem, log_level.Value, msg));
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
#nullable disable
|
||||
|
||||
public static bool try_convert(Type type, object obj, out object result) {
|
||||
if (obj == null) {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
} else if (type.IsPrimitive && obj is IConvertible) {
|
||||
result = Convert.ChangeType(obj, type);
|
||||
return true;
|
||||
} else if (type.IsAssignableFrom(obj.GetType())) {
|
||||
result = obj;
|
||||
return true;
|
||||
} else if (type.IsEnum && obj is string) {
|
||||
result = try_parse_enum(type, obj.ToString());
|
||||
return true;
|
||||
} else if (type.IsNullable()) {
|
||||
if (try_convert(type.GetGenericArguments()[0], obj, out result)) {
|
||||
return true;
|
||||
}
|
||||
result = null;
|
||||
return false;
|
||||
} else {
|
||||
result = get_default(type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static T try_convert<T>(object obj) =>
|
||||
(T)try_convert(typeof(T), obj);
|
||||
|
||||
public static T try_convert<T>(object obj, T default_value) =>
|
||||
try_convert(obj, out T result) ? result : default_value;
|
||||
|
||||
public static bool try_convert<T>(object obj, out T result) {
|
||||
bool rv = try_convert(typeof(T), obj, out object r_obj);
|
||||
result = (T)r_obj;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static object get_default(Type type) =>
|
||||
type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
|
||||
public static T ValueOrDefault<T>(T? nullable) where T : struct => nullable.HasValue ? nullable.Value : default;
|
||||
|
||||
public static bool is_compatible<T>(object obj) { return obj is T || (obj == null && default(T) == null); }
|
||||
|
||||
public static bool try_convert_args<T1, T2>(object[] args, out T1 arg1, out T2 arg2) {
|
||||
if (args == null || args.Length != 2) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3) {
|
||||
if (args == null || args.Length != 3) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool try_convert_args<T1, T2, T3, T4>(object[] args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4) {
|
||||
if (args == null || args.Length != 4) {
|
||||
arg1 = default;
|
||||
arg2 = default;
|
||||
arg3 = default;
|
||||
arg4 = default;
|
||||
return false;
|
||||
}
|
||||
bool rv = try_convert(args[0], out arg1);
|
||||
rv |= try_convert(args[1], out arg2);
|
||||
rv |= try_convert(args[2], out arg3);
|
||||
rv |= try_convert(args[3], out arg4);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static bool TryConvertArgs<T1, T2, T3, T4, T5>(IEnumerable<object> args, out T1 arg1, out T2 arg2, out T3 arg3, out T4 arg4, out T5 arg5) {
|
||||
using var en = args.GetEnumerator();
|
||||
bool success = true;
|
||||
convertNext(en, ref success, out arg1);
|
||||
convertNext(en, ref success, out arg2);
|
||||
convertNext(en, ref success, out arg3);
|
||||
convertNext(en, ref success, out arg4);
|
||||
convertNext(en, ref success, out arg5);
|
||||
return success;
|
||||
}
|
||||
|
||||
public static object try_parse_enum(Type enum_type, string value, bool ignore_case = true) {
|
||||
if (!enum_type.IsEnum) throw new ArgumentException("Must an enum type.", nameof(enum_type));
|
||||
Array vals = Enum.GetValues(enum_type);
|
||||
var str_comp = ignore_case ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
|
||||
foreach (object v in vals) {
|
||||
if (string.Equals(v.ToString(), value, str_comp))
|
||||
return v;
|
||||
}
|
||||
return Activator.CreateInstance(enum_type);
|
||||
}
|
||||
|
||||
public static bool update_value_type<T>(ref T field, T newval) where T : struct, IEquatable<T> {
|
||||
bool rv = !field.Equals(newval);
|
||||
field = newval;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void convertNext<T>(IEnumerator<object> enumerator, ref bool success, out T arg) {
|
||||
if (!success || !enumerator.MoveNext()) {
|
||||
arg = default;
|
||||
success = false;
|
||||
}
|
||||
success = try_convert(enumerator.Current, out arg);
|
||||
}
|
||||
|
||||
public static class CastDelegate<T, TResult> {
|
||||
public static readonly Func<T, TResult> value =
|
||||
v => v is TResult r ? r : throw new InvalidCastException($"Failed to cast element in source collection from type {typeof(T)} to {typeof(TResult)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace bts.utils {
|
||||
using Spark.Core;
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -50,6 +51,30 @@ namespace bts.utils {
|
||||
public static bool is_nan<T>(T value) where T : INumber<T> =>
|
||||
NormalNumericOps<T>.is_nan(value);
|
||||
|
||||
public static bool can_increment<T>(T value, T addend) where T : INumber<T>, IMinMaxValue<T> {
|
||||
if (value > T.Zero != addend > T.Zero) {
|
||||
return true;
|
||||
}
|
||||
if (value > T.Zero) {
|
||||
return addend < T.MaxValue - value;
|
||||
}
|
||||
if (value < T.Zero) {
|
||||
return addend > T.MinValue - value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool try_increment<T>(ref T value, T addend) where T : INumber<T>, IMinMaxValue<T> {
|
||||
if (can_increment(value, addend)) {
|
||||
value += addend;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool try_increment(ref int value, long addend) =>
|
||||
addend <= int.MaxValue && addend >= int.MinValue && try_increment(ref value, (int)addend);
|
||||
|
||||
public static float gcd(IEnumerable<float> nums) =>
|
||||
gcd<float, NumericCastConverter>(nums);
|
||||
|
||||
@@ -170,6 +195,22 @@ namespace bts.utils {
|
||||
public static T round_fractional_units<T>(T value, T fractional_unit) where T : IFloatingPoint<T> =>
|
||||
T.Round(value / fractional_unit);
|
||||
|
||||
public static T round_fractional_units<T>(T value, T fractional_unit, RoundingMode rounding_mode) where T : struct, IFloatingPoint<T> =>
|
||||
round_fractional_units(value, fractional_unit, rounding_mode, T.Zero);
|
||||
|
||||
public static T round_fractional_units<T>(T value, T fractional_unit, RoundingMode rounding_mode, T epsilon) where T : IFloatingPoint<T> =>
|
||||
rounding_mode switch {
|
||||
RoundingMode.Up => T.Ceiling((value - epsilon) / fractional_unit),
|
||||
RoundingMode.Down => T.Floor((value + epsilon) / fractional_unit),
|
||||
RoundingMode.AbsUp => value < T.Zero
|
||||
? T.Floor((value + epsilon) / fractional_unit)
|
||||
: T.Ceiling((value - epsilon) / fractional_unit),
|
||||
RoundingMode.AbsDown => value < T.Zero
|
||||
? T.Ceiling((value - epsilon) / fractional_unit)
|
||||
: T.Floor((value + epsilon) / fractional_unit),
|
||||
_ => T.Round(value / fractional_unit),
|
||||
};
|
||||
|
||||
public static double round_down_abs(double value) =>
|
||||
round_down_abs(value, EPSILON);
|
||||
|
||||
@@ -449,10 +490,12 @@ namespace bts.utils {
|
||||
}
|
||||
|
||||
static bool try_mk_floating_point_type([NotNullWhen(true)] out Type type) {
|
||||
try {
|
||||
type = typeof(IFloatingPoint<>).MakeGenericType(typeof(T));
|
||||
return type.IsAssignableFrom(typeof(T));
|
||||
} catch { }
|
||||
if (typeof(T).GetInterfaces().Any(iface => iface.IsGenericType && iface.GetGenericTypeDefinition().Equals(typeof(IFloatingPoint<>)))) {
|
||||
try {
|
||||
type = typeof(IFloatingPoint<>).MakeGenericType(typeof(T));
|
||||
return type.IsAssignableFrom(typeof(T));
|
||||
} catch { }
|
||||
}
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.user.settings;
|
||||
|
||||
using Spark.Core.Math;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -159,7 +161,7 @@ namespace bts.tests.security {
|
||||
var price_break = tick.low_tick_inclusive_end;
|
||||
foreach (var p in prices) {
|
||||
var expected_ticks_from_zero = TickTestHelper.count_ticks(tick, p);
|
||||
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p);
|
||||
var actual_ticks_from_zero = TickUtils.count_ticks(tick, p, RoundingMode.Closest);
|
||||
Assert.That(expected_ticks_from_zero == actual_ticks_from_zero,
|
||||
$"Expected ticks from zero to be '{expected_ticks_from_zero}', actually '{actual_ticks_from_zero}' " +
|
||||
$"(low/high/pricebreak: {tick.low_tick}/{tick.high_tick}/{price_break}, price: {p}).");
|
||||
@@ -177,24 +179,6 @@ namespace bts.tests.security {
|
||||
}
|
||||
}
|
||||
|
||||
void _test_tick_increment(ContractSpecificationImmutable spec, double from, double to) {
|
||||
if (!from.is_real() || !to.is_real())
|
||||
return;
|
||||
var (min, max) = MathUtils.get_outliers(from, to);
|
||||
if (spec.PriceBreak.is_real()) {
|
||||
if (min >= spec.PriceBreak) {
|
||||
//_test_single_tick(spec.HighTick, min, max);
|
||||
} else if (max <= spec.PriceBreak) {
|
||||
_test_tick_increment(spec, spec.LowTick, min, max);
|
||||
} else {
|
||||
//if (round_ticks(spec.LowTick, min, out var ))
|
||||
_test_tick_increment_across_break(spec, min, max);
|
||||
}
|
||||
} else {
|
||||
_test_tick_increment(spec, spec.LowTick, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, double from, double to) {
|
||||
if (!from.is_real() || !to.is_real())
|
||||
return;
|
||||
@@ -260,18 +244,6 @@ namespace bts.tests.security {
|
||||
Assert.That(MathUtils.marginal_equals(batch_tick_down_result, rounded_min, TickUtils.precision_const<double>()));
|
||||
}
|
||||
|
||||
void _test_tick_increment(ContractSpecificationImmutable spec, double tick_size, double min, double max) {
|
||||
// min round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
|
||||
var min_partial_tick = TickTestHelper.check_partial_tick(spec, min, rounded_min, true);
|
||||
// max round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, max, out var rounded_max, out var rounded_max_ticks));
|
||||
var max_partial_tick = TickTestHelper.check_partial_tick(spec, max, rounded_max, false);
|
||||
var full_ticks = rounded_max_ticks - rounded_min_ticks;
|
||||
Assert.That(MathUtils.marginal_equals(rounded_max, spec.tickup(min, full_ticks + min_partial_tick), TickUtils.precision_const<double>()));
|
||||
Assert.That(MathUtils.marginal_equals(rounded_min, spec.tickup(max, -(full_ticks + max_partial_tick)), TickUtils.precision_const<double>()));
|
||||
}
|
||||
|
||||
void _test_tick_increment(RegionalTickFactors tick, double tick_size, double min, double max) {
|
||||
// min round/partial tick
|
||||
Assert.That(TickTestHelper.round_ticks(tick_size, min, out var rounded_min, out var rounded_min_ticks));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(data_files)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/safe_dep_glob.cmake)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(libspark)
|
||||
set(CMAKE_SHARED_LIBRARY_PREFIX "")
|
||||
include(GNUInstallDirs)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "lib/api/domain/SecurityCriteria.proto";
|
||||
import "lib/api/core/RegexFormatting.proto";
|
||||
|
||||
package bts.api.md;
|
||||
|
||||
option csharp_namespace = "bts.api.md";
|
||||
option cc_enable_arenas = true;
|
||||
|
||||
message BinanceCoinFuturesMarketDataConfig {
|
||||
uint32 num_depth_levels = 1;
|
||||
uint32 depth_throttle_ms = 2;
|
||||
bts.api.domain.SecurityCriteria security_criteria = 3;
|
||||
// Market data isn't subscribed for securities listed here (even if they are
|
||||
// included by 'security_criteria'). There are some securities that don't
|
||||
// fit into our system just yet, such as 'BTCBIDR', which trades in prices
|
||||
// larger than our cpp 'domain::Price' class supports.
|
||||
repeated string excluded_securities = 4;
|
||||
|
||||
// The list of instruments at Binance is obtained via an 'exchangeInfo' http
|
||||
// rest request. In the corresponding response, each instrument has a 'pair'
|
||||
// field, which is analgous to a security desc in our system (a unique
|
||||
// human-readable string representation of the security). For some
|
||||
// instruments, the string representation used in the market depth request is
|
||||
// slightly different.
|
||||
//
|
||||
// 'depth_symbol_mappings' is a list of regex find/replace patterns to
|
||||
// apply (to the 'pair' field) when subscribing to depth.
|
||||
repeated bts.api.core.RegexFormatting security_desc_formattings = 5;
|
||||
|
||||
string url_rest_base = 6;
|
||||
string url_ws_base = 7;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "lib/api/domain/SecurityCriteria.proto";
|
||||
import "lib/api/core/RegexFormatting.proto";
|
||||
|
||||
package bts.api.md;
|
||||
|
||||
@@ -9,6 +8,13 @@ option csharp_namespace = "bts.api.md";
|
||||
option cc_enable_arenas = true;
|
||||
|
||||
message BinanceUsdsFuturesMarketDataConfig {
|
||||
message Subscription {
|
||||
string symbol = 1;
|
||||
string pair = 2;
|
||||
string contract_type = 3;
|
||||
string base_asset = 4;
|
||||
string quote_asset = 5;
|
||||
}
|
||||
uint32 num_depth_levels = 1;
|
||||
uint32 depth_throttle_ms = 2;
|
||||
bts.api.domain.SecurityCriteria security_criteria = 3;
|
||||
@@ -17,17 +23,7 @@ message BinanceUsdsFuturesMarketDataConfig {
|
||||
// fit into our system just yet, such as 'BTCBIDR', which trades in prices
|
||||
// larger than our cpp 'domain::Price' class supports.
|
||||
repeated string excluded_securities = 4;
|
||||
|
||||
// The list of instruments at Binance is obtained via an 'exchangeInfo' http
|
||||
// rest request. In the corresponding response, each instrument has a 'pair'
|
||||
// field, which is analgous to a security desc in our system (a unique
|
||||
// human-readable string representation of the security). For some
|
||||
// instruments, the string representation used in the market depth request is
|
||||
// slightly different.
|
||||
//
|
||||
// 'depth_symbol_mappings' is a list of regex find/replace patterns to
|
||||
// apply (to the 'pair' field) when subscribing to depth.
|
||||
repeated bts.api.core.RegexFormatting security_desc_formattings = 5;
|
||||
repeated Subscription subscriptions = 8;
|
||||
|
||||
string url_rest_base = 6;
|
||||
string url_ws_base = 7;
|
||||
|
||||
@@ -2,6 +2,7 @@ syntax = "proto3";
|
||||
|
||||
import "lib/api/md/FTXMarketDataConfig.proto";
|
||||
import "lib/api/md/BinanceMarketDataConfig.proto";
|
||||
import "lib/api/md/BinanceCoinFuturesMarketDataConfig.proto";
|
||||
import "lib/api/md/BinanceUsMarketDataConfig.proto";
|
||||
import "lib/api/md/CoinbaseMarketDataConfig.proto";
|
||||
import "lib/api/md/BinanceUsdsFuturesMarketDataConfig.proto";
|
||||
@@ -21,6 +22,7 @@ package bts.api.md;
|
||||
message MarketDataConfig {
|
||||
repeated FTXMarketDataConfig ftx = 1;
|
||||
repeated BinanceMarketDataConfig binance = 2;
|
||||
repeated BinanceCoinFuturesMarketDataConfig binance_coin_futures = 11;
|
||||
repeated BinanceUsdsFuturesMarketDataConfig binance_usds_futures = 3;
|
||||
repeated BinanceUsMarketDataConfig binance_us = 4;
|
||||
repeated DeribitMarketDataConfig deribit = 5;
|
||||
|
||||
@@ -9,7 +9,9 @@ option cc_enable_arenas = true;
|
||||
// Config to control the recording of the market data for an exchange.
|
||||
// This config is part of MarketDataConfig.proto
|
||||
message MarketDataRecordingConfig {
|
||||
uint32 num_of_seconds_to_run = 10;
|
||||
uint32 num_of_seconds_to_run_file_writer=12;
|
||||
uint32 num_of_seconds_to_run_db_writer=13;
|
||||
bts.api.Venue venue = 20;
|
||||
uint32 n_book_levels = 30;
|
||||
uint32 batch_size = 40;
|
||||
}
|
||||
@@ -13,4 +13,6 @@ option cc_enable_arenas = true;
|
||||
message MarketDataReplayConfig {
|
||||
bts.api.Venue venue = 10;
|
||||
double replay_speed = 20;
|
||||
// whether to replay books
|
||||
bool publish_boostrap_books_only = 30;
|
||||
}
|
||||
@@ -66,6 +66,7 @@ bool AdminServiceUtils::has_exchange(
|
||||
case api::Venue::VENUE_DERIBIT: // TODO
|
||||
case api::Venue::VENUE_FTX:
|
||||
case api::Venue::VENUE_BINANCE:
|
||||
case api::Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::Venue::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
case api::Venue::VENUE_BINANCE_USDS_FUTURES:
|
||||
@@ -185,6 +186,7 @@ AdminServiceUtils::resolve_connection(
|
||||
case api::Venue::VENUE_FTX:
|
||||
case api::Venue::VENUE_OKX:
|
||||
case api::Venue::VENUE_BINANCE:
|
||||
case api::Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::Venue::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
case api::Venue::VENUE_TAIFEX:
|
||||
@@ -454,6 +456,7 @@ std::string AdminServiceUtils::find_account_exchange_account(
|
||||
case api::Venue::VENUE_DERIBIT: // TODO
|
||||
case api::Venue::VENUE_FTX: // TODO
|
||||
case api::Venue::VENUE_BINANCE: // TODO
|
||||
case api::Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::Venue::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
case api::Venue::VENUE_BINANCE_USDS_FUTURES:
|
||||
@@ -580,6 +583,21 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_OKX: {
|
||||
rv.set_session("okx_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
rv.set_operator_id(user_ptr->name());
|
||||
// AdminServiceUtils::ExchangeSubAccountResolver resolver;
|
||||
// auto sub_acc_ptr = resolver.find_entry(&sec, account_ptr->name(), true);
|
||||
// if (!sub_acc_ptr) {
|
||||
// out_result.set_is_success(false);
|
||||
// out_result.set_error_text("Could not resolve sub account");
|
||||
// return rv;
|
||||
// }
|
||||
// rv.set_account_code(sub_acc_ptr->exchange_subaccount());
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::Venue::VENUE_DERIBIT: {
|
||||
rv.set_session("deribit_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
@@ -749,12 +767,24 @@ bts::api::orderman::RoutingRecord AdminServiceUtils::get_routing_record(
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_BINANCE_USDS_FUTURES: {
|
||||
rv.set_session("binance_usds_futures_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
rv.set_account(user_ptr->name());
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_BINANCE: {
|
||||
rv.set_session("binance_1");
|
||||
rv.set_account(account_ptr->name());
|
||||
rv.set_account(user_ptr->name());
|
||||
out_result.set_is_success(true);
|
||||
return rv;
|
||||
}
|
||||
case api::VENUE_FTX: // TODO
|
||||
case api::VENUE_BINANCE: // TODO
|
||||
case api::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
case api::VENUE_BINANCE_USDS_FUTURES:
|
||||
case api::VENUE_OKX:
|
||||
case api::Venue::VENUE_UNSET:
|
||||
case api::Venue::VENUE_BTS:
|
||||
case api::Venue::VENUE_CBOECFE:
|
||||
@@ -1145,6 +1175,7 @@ std::string AdminServiceUtils::find_user_exchange_id(
|
||||
case api::Venue::VENUE_DERIBIT: // TODO
|
||||
case api::Venue::VENUE_FTX: // TODO
|
||||
case api::Venue::VENUE_BINANCE: // TODO
|
||||
case api::Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::Venue::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
case api::Venue::VENUE_BINANCE_USDS_FUTURES:
|
||||
@@ -1180,6 +1211,7 @@ std::string AdminServiceUtils::find_user_company_id(
|
||||
case api::Venue::VENUE_DERIBIT:
|
||||
case api::Venue::VENUE_FTX: // TODO
|
||||
case api::Venue::VENUE_BINANCE: // TODO
|
||||
case api::Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
case api::Venue::VENUE_BINANCE_US:
|
||||
case api::Venue::VENUE_COINBASE:
|
||||
case api::Venue::VENUE_BINANCE_USDS_FUTURES:
|
||||
|
||||
@@ -41,6 +41,18 @@ class AdminServiceUtils {
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bts::config::toggles::ExchangeSubAccountUnit const *find_entry(
|
||||
const std::string &account, bool is_internal_account) {
|
||||
for (const auto &esa : exch_sub_accounts.accounts()) {
|
||||
const auto &a = is_internal_account ? esa.bts_account_name()
|
||||
: esa.exchange_subaccount();
|
||||
if (a == account) {
|
||||
return &esa;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
////////////////////////////////////////////
|
||||
// connection accessors ////////////////////
|
||||
|
||||
@@ -74,11 +74,6 @@ function(get_cpp_sources dest)
|
||||
expression/ParserExceptions.cpp
|
||||
expression/Value.cpp
|
||||
expression/tests/expression_parser_tests.cpp
|
||||
io/http/CurlHandle.cpp
|
||||
io/http/HTTPClient.cpp
|
||||
io/http/HTTPHeaders.cpp
|
||||
io/http/HTTPRequest.cpp
|
||||
io/http/HTTPResponse.cpp
|
||||
logging/LoggerHub.cpp
|
||||
logging/Sink.cpp
|
||||
logging/logging.cpp
|
||||
|
||||
@@ -4,6 +4,7 @@ SecurityMetadataCollection {
|
||||
scope {
|
||||
clauses {
|
||||
symbols: "BTC"
|
||||
symbols: "ETH"
|
||||
security_types: SECURITY_TYPE_FUTURE
|
||||
security_types: SECURITY_TYPE_SPREAD
|
||||
venues: VENUE_BINANCE
|
||||
@@ -13,11 +14,11 @@ SecurityMetadataCollection {
|
||||
value {
|
||||
quantity_display {
|
||||
tick_factors {
|
||||
tick_base: 1
|
||||
tick_unit: 10
|
||||
tick_base: 1000
|
||||
tick_unit: 1
|
||||
}
|
||||
display_multiplier: NaN
|
||||
show_decimal_part: false
|
||||
show_decimal_part: true
|
||||
pad_with_leading_zeros: true
|
||||
show_zero_integer: true
|
||||
nan_text: ""
|
||||
|
||||
@@ -4026,8 +4026,34 @@ JetFIX {
|
||||
connection: @JetFIXSimConnection.sim
|
||||
connection {
|
||||
name: "deribit_1"
|
||||
max_order_msgs_per_window: 300
|
||||
venue: VENUE_DERIBIT
|
||||
}
|
||||
connection {
|
||||
name: "okx_1"
|
||||
max_order_msgs_per_window: 300
|
||||
venue: VENUE_OKX
|
||||
}
|
||||
connection {
|
||||
name: "binance_usds_futures_1"
|
||||
max_order_msgs_per_window: 300
|
||||
venue: VENUE_BINANCE_USDS_FUTURES
|
||||
}
|
||||
connection {
|
||||
name: "binance_1"
|
||||
max_order_msgs_per_window: 300
|
||||
venue: VENUE_BINANCE
|
||||
}
|
||||
connection {
|
||||
name: "cboe_1"
|
||||
max_order_msgs_per_window: 300
|
||||
venue: VENUE_CBOE
|
||||
}
|
||||
connection {
|
||||
name: "taifex_1"
|
||||
max_order_msgs_per_window: 300
|
||||
venue: VENUE_TAIFEX
|
||||
}
|
||||
# CME EXECUTION NEEDS TO BE UPDATED, probably below
|
||||
# connection {
|
||||
# name: "onixs_sim"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Spreader {
|
||||
value {
|
||||
n_failed_iocs: 20
|
||||
qty_multiplier: 100000
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,11 @@ SymbolLists {
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
}
|
||||
list {
|
||||
key: "BINANCE_COIN_FUTURES"
|
||||
symbol: "BTC"
|
||||
symbol: "ETH"
|
||||
}
|
||||
list {
|
||||
key: "TAIFEX"
|
||||
symbol: "QFFH"
|
||||
@@ -140,6 +145,11 @@ Symbols {
|
||||
spot: @SymbolLists.list[BINANCE].symbol
|
||||
spread: @SymbolLists.list[BINANCE].symbol
|
||||
}
|
||||
venues {
|
||||
name: "VENUE_BINANCE_COIN_FUTURES"
|
||||
future: @SymbolLists.list[BINANCE_COIN_FUTURES].symbol
|
||||
spread: @SymbolLists.list[BINANCE_COIN_FUTURES].symbol
|
||||
}
|
||||
venues {
|
||||
name: "VENUE_BINANCE_US"
|
||||
spot: @SymbolLists.list[BINANCE_US].symbol
|
||||
|
||||
@@ -13,6 +13,19 @@ MarketDataConfig {
|
||||
}
|
||||
}
|
||||
|
||||
binance_coin_futures {
|
||||
num_depth_levels: 5
|
||||
url_rest_base: "https://testnet.binancefuture.com"
|
||||
url_ws_base: "wss://dstream.binance.com"
|
||||
security_criteria {
|
||||
clauses {
|
||||
security_types: SECURITY_TYPE_FUTURE
|
||||
symbols: "BTC"
|
||||
symbols: "ETH"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binance_us {
|
||||
num_depth_levels: 5
|
||||
url_rest_base: "https://api.binance.us"
|
||||
|
||||
@@ -13,6 +13,19 @@ MarketDataConfig {
|
||||
}
|
||||
}
|
||||
|
||||
binance_coin_futures {
|
||||
num_depth_levels: 5
|
||||
url_rest_base: "https://dapi.binance.com"
|
||||
url_ws_base: "wss://dstream.binance.com"
|
||||
security_criteria {
|
||||
clauses {
|
||||
security_types: SECURITY_TYPE_FUTURE
|
||||
symbols: "BTC"
|
||||
symbols: "ETH"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binance_usds_futures {
|
||||
num_depth_levels: 5
|
||||
url_rest_base: "https://dapi.binance.com"
|
||||
|
||||
@@ -4,6 +4,7 @@ package bts.config;
|
||||
|
||||
message Spreader {
|
||||
uint32 n_failed_iocs = 10;
|
||||
uint32 qty_multiplier = 15;
|
||||
|
||||
// if this is set to true, the spreader internally
|
||||
// generates fake fills to keep the cache (data and
|
||||
|
||||
@@ -28,6 +28,9 @@ class TimeSpan {
|
||||
static constexpr TimeSpan from_micros(std::int64_t value);
|
||||
static constexpr TimeSpan from_millis(std::int64_t value);
|
||||
static constexpr TimeSpan from_seconds(std::int64_t value);
|
||||
static constexpr TimeSpan from_minutes(std::int64_t value);
|
||||
static constexpr TimeSpan from_hours(std::int64_t value);
|
||||
static constexpr TimeSpan from_days(std::int64_t value);
|
||||
static TimeSpan from_posix_duration(boost::posix_time::time_duration t);
|
||||
static TimeSpan from_iso_string(const std::string &iso_string);
|
||||
|
||||
@@ -53,6 +56,8 @@ constexpr bool operator<(TimeSpan lhs, TimeSpan rhs) noexcept;
|
||||
constexpr bool operator<=(TimeSpan lhs, TimeSpan rhs) noexcept;
|
||||
constexpr bool operator>(TimeSpan lhs, TimeSpan rhs) noexcept;
|
||||
constexpr bool operator>=(TimeSpan lhs, TimeSpan rhs) noexcept;
|
||||
constexpr TimeSpan operator-(TimeSpan lhs, TimeSpan rhs) noexcept;
|
||||
constexpr TimeSpan operator+(TimeSpan lhs, TimeSpan rhs) noexcept;
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const TimeSpan &ts) noexcept;
|
||||
|
||||
@@ -83,6 +88,21 @@ constexpr TimeSpan TimeSpan::from_seconds(std::int64_t value) {
|
||||
return TimeSpan{value * nanos_per_second};
|
||||
}
|
||||
|
||||
constexpr TimeSpan TimeSpan::from_minutes(std::int64_t value) {
|
||||
constexpr const auto nanos_per_minute = 60'000'000'000;
|
||||
return TimeSpan{value * nanos_per_minute};
|
||||
}
|
||||
|
||||
constexpr TimeSpan TimeSpan::from_hours(std::int64_t value) {
|
||||
constexpr const auto nanos_per_hour = 3'600'000'000'000;
|
||||
return TimeSpan{value * nanos_per_hour};
|
||||
}
|
||||
|
||||
constexpr TimeSpan TimeSpan::from_days(std::int64_t value) {
|
||||
constexpr const auto nanos_per_day = 86'400'000'000'000;
|
||||
return TimeSpan{value * nanos_per_day};
|
||||
}
|
||||
|
||||
constexpr std::int64_t TimeSpan::nanos() const { return ticks_; }
|
||||
|
||||
constexpr std::int64_t TimeSpan::micros() const { return ticks_ / 1'000; }
|
||||
@@ -130,6 +150,14 @@ constexpr bool operator>=(TimeSpan lhs, TimeSpan rhs) noexcept {
|
||||
return lhs.nanos() >= rhs.nanos();
|
||||
}
|
||||
|
||||
constexpr TimeSpan operator-(TimeSpan lhs, TimeSpan rhs) noexcept {
|
||||
return TimeSpan{lhs.nanos() - rhs.nanos()};
|
||||
}
|
||||
|
||||
constexpr TimeSpan operator+(TimeSpan lhs, TimeSpan rhs) noexcept {
|
||||
return TimeSpan{lhs.nanos() + rhs.nanos()};
|
||||
}
|
||||
|
||||
} // namespace bts::core
|
||||
|
||||
FMT_IMPL(bts::core::TimeSpan, t, "{}", t.to_iso_string());
|
||||
@@ -5,12 +5,19 @@
|
||||
namespace bts::core {
|
||||
|
||||
using std::uint32_t;
|
||||
using std::uint8_t;
|
||||
|
||||
static bool is_valid_ymd(uint32_t year, uint32_t month, uint32_t day) {
|
||||
static bool is_valid_ymd(uint32_t year, uint8_t month, uint8_t day) {
|
||||
return year >= 1970 && month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
||||
}
|
||||
|
||||
TimeStamp::TimeStamp(uint32_t year, uint32_t month, uint32_t day) {
|
||||
TimeStamp::TimeStamp(
|
||||
uint32_t year,
|
||||
uint8_t month,
|
||||
uint8_t day,
|
||||
uint8_t hour,
|
||||
uint8_t minute,
|
||||
uint8_t second) {
|
||||
bts_assert_msgf(
|
||||
is_valid_ymd(year, month, day), "Invalid timestamp {}/{}/{} (y/m/d)",
|
||||
year, month, day);
|
||||
@@ -18,6 +25,9 @@ TimeStamp::TimeStamp(uint32_t year, uint32_t month, uint32_t day) {
|
||||
date.tm_year = year - 1900;
|
||||
date.tm_mon = month - 1;
|
||||
date.tm_mday = day;
|
||||
date.tm_hour = hour;
|
||||
date.tm_min = minute;
|
||||
date.tm_sec = second;
|
||||
auto time = std::chrono::system_clock::from_time_t(mktime(&date));
|
||||
auto duration = time.time_since_epoch();
|
||||
value_ =
|
||||
@@ -25,7 +35,7 @@ TimeStamp::TimeStamp(uint32_t year, uint32_t month, uint32_t day) {
|
||||
}
|
||||
|
||||
bool TimeStamp::from_ymd(
|
||||
std::uint32_t year, std::uint32_t month, std::uint32_t day, TimeStamp *result) {
|
||||
uint32_t year, uint8_t month, uint8_t day, TimeStamp *result) {
|
||||
if (!is_valid_ymd(year, month, day)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+103
-9
@@ -8,7 +8,6 @@
|
||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||
#include <boost/date_time/posix_time/ptime.hpp>
|
||||
#include <boost/date_time/time.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
@@ -16,6 +15,9 @@
|
||||
|
||||
namespace bts::core {
|
||||
|
||||
#define VALID_YMD(year, month, day) \
|
||||
(year >= 1970 && month >= 1 && month <= 12 && day >= 1 && day <= 31)
|
||||
|
||||
/**
|
||||
* @brief Represents time as nano-seconds since the unix epoch (Jan 1, 1970)
|
||||
*/
|
||||
@@ -23,8 +25,14 @@ struct TimeStamp {
|
||||
public:
|
||||
constexpr TimeStamp() = default;
|
||||
explicit constexpr TimeStamp(std::uint64_t value);
|
||||
// constructs a TimeStamp from a UTC date
|
||||
explicit TimeStamp(uint32_t year, uint32_t month, uint32_t day);
|
||||
TimeStamp(std::uint32_t year, std::uint8_t month, std::uint8_t day);
|
||||
TimeStamp(
|
||||
std::uint32_t year,
|
||||
std::uint8_t month,
|
||||
std::uint8_t day,
|
||||
std::uint8_t hour,
|
||||
std::uint8_t minute,
|
||||
std::uint8_t second);
|
||||
|
||||
static constexpr TimeStamp min();
|
||||
static constexpr TimeStamp max();
|
||||
@@ -36,10 +44,33 @@ struct TimeStamp {
|
||||
static constexpr TimeStamp from_seconds(std::int64_t value);
|
||||
static TimeStamp from_ptime(boost::posix_time::ptime t);
|
||||
static TimeStamp from_iso_string(const std::string &iso_string);
|
||||
|
||||
// Constructs a TimeStamp from a UTC date
|
||||
static TimeStamp from_ymd(
|
||||
std::uint32_t year, std::uint8_t month, std::uint8_t day);
|
||||
|
||||
static bool from_ymd(
|
||||
std::uint32_t year,
|
||||
std::uint32_t month,
|
||||
std::uint32_t day,
|
||||
std::uint8_t month,
|
||||
std::uint8_t day,
|
||||
TimeStamp *result);
|
||||
|
||||
static TimeStamp from_ymdhms(
|
||||
std::uint32_t year,
|
||||
std::uint8_t month,
|
||||
std::uint8_t day,
|
||||
std::uint8_t hour,
|
||||
std::uint8_t minute,
|
||||
std::uint8_t second);
|
||||
|
||||
// Constructs a TimeStamp from a UTC date
|
||||
static bool from_ymdhms(
|
||||
std::uint32_t year,
|
||||
std::uint8_t month,
|
||||
std::uint8_t day,
|
||||
std::uint8_t hour,
|
||||
std::uint8_t minute,
|
||||
std::uint8_t second,
|
||||
TimeStamp *result);
|
||||
|
||||
constexpr std::uint64_t nanos() const;
|
||||
@@ -50,8 +81,11 @@ struct TimeStamp {
|
||||
constexpr double total_millis() const;
|
||||
constexpr double total_seconds() const;
|
||||
|
||||
constexpr std::int64_t to_int64();
|
||||
inline void to_date(uint32_t &year, uint32_t &month, uint32_t &day);
|
||||
constexpr std::int64_t to_int64() const;
|
||||
std::int64_t to_date_int() const;
|
||||
void to_date(uint32_t &year, uint32_t &month, uint32_t &day) const;
|
||||
// Gets a TimeStamp representing this date at midnight.
|
||||
TimeStamp date() const;
|
||||
constexpr boost::posix_time::ptime to_ptime() const;
|
||||
std::string to_iso_string() const;
|
||||
// current epoch ns
|
||||
@@ -82,7 +116,9 @@ constexpr bool operator>=(TimeStamp lhs, TimeStamp rhs) noexcept;
|
||||
|
||||
constexpr TimeSpan operator-(TimeStamp lhs, TimeStamp rhs) noexcept;
|
||||
constexpr TimeStamp operator-(TimeStamp lhs, TimeSpan rhs) noexcept;
|
||||
TimeStamp &operator-=(TimeStamp &lhs, TimeSpan rhs) noexcept;
|
||||
constexpr TimeStamp operator+(TimeStamp lhs, TimeSpan rhs) noexcept;
|
||||
TimeStamp &operator+=(TimeStamp &lhs, TimeSpan rhs) noexcept;
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const TimeStamp &ts) noexcept;
|
||||
|
||||
@@ -92,6 +128,10 @@ std::ostream &operator<<(std::ostream &out, const TimeStamp &ts) noexcept;
|
||||
|
||||
constexpr TimeStamp::TimeStamp(std::uint64_t value) : value_(value) {}
|
||||
|
||||
inline TimeStamp::TimeStamp(
|
||||
std::uint32_t year, std::uint8_t month, std::uint8_t day)
|
||||
: TimeStamp(year, month, day, 0, 0, 0) {}
|
||||
|
||||
constexpr TimeStamp TimeStamp::min() { return TimeStamp{0}; }
|
||||
|
||||
constexpr TimeStamp TimeStamp::max() {
|
||||
@@ -144,13 +184,21 @@ constexpr double TimeStamp::total_seconds() const {
|
||||
return static_cast<double>(value_) / 1000'000'000;
|
||||
}
|
||||
|
||||
constexpr std::int64_t TimeStamp::to_int64() {
|
||||
constexpr std::int64_t TimeStamp::to_int64() const {
|
||||
bts_assert(
|
||||
value_ <=
|
||||
static_cast<std::int64_t>(std::numeric_limits<std::int64_t>::max()));
|
||||
return static_cast<std::int64_t>(value_);
|
||||
}
|
||||
void TimeStamp::to_date(uint32_t &year, uint32_t &month, uint32_t &day) {
|
||||
|
||||
inline std::int64_t TimeStamp::to_date_int() const {
|
||||
std::uint32_t y, m, d;
|
||||
to_date(y, m, d);
|
||||
return y << 6 | m << 4 | d;
|
||||
}
|
||||
|
||||
inline void TimeStamp::to_date(
|
||||
uint32_t &year, uint32_t &month, uint32_t &day) const {
|
||||
std::uint64_t epoch_secs = value_ / (1000 * 1000 * 1000);
|
||||
time_t ts_tt(epoch_secs);
|
||||
struct tm *ptm = gmtime(&ts_tt);
|
||||
@@ -159,6 +207,12 @@ void TimeStamp::to_date(uint32_t &year, uint32_t &month, uint32_t &day) {
|
||||
day = ptm->tm_mday;
|
||||
}
|
||||
|
||||
inline TimeStamp TimeStamp::date() const {
|
||||
std::uint32_t y, m, d;
|
||||
to_date(y, m, d);
|
||||
return from_ymd(y, m, d);
|
||||
}
|
||||
|
||||
constexpr boost::posix_time::ptime TimeStamp::to_ptime() const {
|
||||
constexpr const auto nanos_per_sec = 1'000'000'000;
|
||||
constexpr const auto nanos_per_micro = 1'000;
|
||||
@@ -170,6 +224,36 @@ constexpr boost::posix_time::ptime TimeStamp::to_ptime() const {
|
||||
boost::posix_time::microseconds(nanos_rem / nanos_per_micro);
|
||||
}
|
||||
|
||||
inline TimeStamp TimeStamp::from_ymd(
|
||||
std::uint32_t year, std::uint8_t month, std::uint8_t day) {
|
||||
return {year, month, day, 0, 0, 0};
|
||||
}
|
||||
|
||||
inline TimeStamp TimeStamp::from_ymdhms(
|
||||
std::uint32_t year,
|
||||
std::uint8_t month,
|
||||
std::uint8_t day,
|
||||
std::uint8_t hour,
|
||||
std::uint8_t minute,
|
||||
std::uint8_t second) {
|
||||
return {year, month, day, hour, minute, second};
|
||||
}
|
||||
|
||||
inline bool TimeStamp::from_ymdhms(
|
||||
std::uint32_t year,
|
||||
std::uint8_t month,
|
||||
std::uint8_t day,
|
||||
std::uint8_t hour,
|
||||
std::uint8_t minute,
|
||||
std::uint8_t second,
|
||||
TimeStamp *result) {
|
||||
if (!VALID_YMD(year, month, day)) {
|
||||
return false;
|
||||
}
|
||||
*result = TimeStamp{year, month, day, hour, minute, second};
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool operator==(TimeStamp lhs, TimeStamp rhs) noexcept {
|
||||
return lhs.nanos() == rhs.nanos();
|
||||
}
|
||||
@@ -202,10 +286,20 @@ constexpr TimeStamp operator-(TimeStamp lhs, TimeSpan rhs) noexcept {
|
||||
return TimeStamp{lhs.nanos() - rhs.nanos()};
|
||||
}
|
||||
|
||||
inline TimeStamp &operator-=(TimeStamp &lhs, TimeSpan rhs) noexcept {
|
||||
lhs = lhs - rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
constexpr TimeStamp operator+(TimeStamp lhs, TimeSpan rhs) noexcept {
|
||||
return TimeStamp{lhs.nanos() + rhs.nanos()};
|
||||
}
|
||||
|
||||
inline TimeStamp &operator+=(TimeStamp &lhs, TimeSpan rhs) noexcept {
|
||||
lhs = lhs + rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
} // namespace bts::core
|
||||
|
||||
FMT_IMPL(bts::core::TimeStamp, t, "{}", t.to_iso_string());
|
||||
@@ -43,8 +43,6 @@ class BookT {
|
||||
|
||||
void reset_merged_quantity();
|
||||
void reset_n_orders();
|
||||
template <class Container>
|
||||
void apply_changes(Side side, const Container &changes);
|
||||
|
||||
/**
|
||||
* Removes price levels of the non-dominant side that cross the dominant
|
||||
@@ -203,12 +201,6 @@ bool BookT<BS>::is_sorted() const {
|
||||
return Side::forboth([&](Side s) { return operator[](s).is_sorted(s); });
|
||||
}
|
||||
|
||||
template <class BS>
|
||||
template <class Container>
|
||||
void BookT<BS>::apply_changes(Side side, const Container &changes) {
|
||||
_books[static_cast<std::size_t>(side.value)].apply_changes(side, changes);
|
||||
}
|
||||
|
||||
template <class BS>
|
||||
void from_json(const nlohmann::json &j, BookT<BS> &book) {
|
||||
io::JsonReader jr{&j};
|
||||
|
||||
@@ -33,6 +33,8 @@ api::domain::Venue DomainSecurityUtils::convert(bts::api::Venue venue) {
|
||||
return DOMAIN_VENUE(SIM);
|
||||
case bts::api::Venue::VENUE_BINANCE:
|
||||
return DOMAIN_VENUE(BINANCE);
|
||||
case bts::api::Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
return DOMAIN_VENUE(BINANCE_COIN_FUTURES);
|
||||
case bts::api::Venue::VENUE_BINANCE_US:
|
||||
return DOMAIN_VENUE(BINANCE_US);
|
||||
case bts::api::VENUE_BINANCE_USDS_FUTURES:
|
||||
@@ -65,6 +67,8 @@ bts::api::Venue DomainSecurityUtils::convert(api::domain::Venue venue) {
|
||||
return api::Venue::VENUE_FTX;
|
||||
case DOMAIN_VENUE(BINANCE):
|
||||
return api::Venue::VENUE_BINANCE;
|
||||
case DOMAIN_VENUE(BINANCE_COIN_FUTURES):
|
||||
return api::Venue::VENUE_BINANCE_COIN_FUTURES;
|
||||
case DOMAIN_VENUE(BINANCE_US):
|
||||
return api::Venue::VENUE_BINANCE_US;
|
||||
case DOMAIN_VENUE(BINANCE_USDS_FUTURES):
|
||||
@@ -166,8 +170,6 @@ std::string DomainSecurityUtils::get_exchange_id(const bts::api::Security &s) {
|
||||
bool DomainSecurityUtils::get_security_desc(
|
||||
const bts::api::Security &security, std::string &desc) {
|
||||
switch (security.ExchangeData_case()) {
|
||||
case bts::api::Security::ExchangeDataCase::EXCHANGEDATA_NOT_SET:
|
||||
bts_assert(false);
|
||||
case bts::api::Security::ExchangeDataCase::kCboe:
|
||||
desc = security.cboe().security_desc();
|
||||
break;
|
||||
@@ -189,6 +191,9 @@ bool DomainSecurityUtils::get_security_desc(
|
||||
case bts::api::Security::ExchangeDataCase::kBinance:
|
||||
desc = security.binance().security_desc();
|
||||
break;
|
||||
case bts::api::Security::ExchangeDataCase::kBinanceCoinFutures:
|
||||
desc = security.binance_coin_futures().security_desc();
|
||||
break;
|
||||
case bts::api::Security::ExchangeDataCase::kBinanceUs:
|
||||
desc = security.binance_us().security_desc();
|
||||
break;
|
||||
@@ -205,6 +210,10 @@ bool DomainSecurityUtils::get_security_desc(
|
||||
desc = security.ftx().security_desc();
|
||||
break;
|
||||
default:
|
||||
if (security.has_synthetic()) {
|
||||
desc = security.synthetic().name();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
Loaded 100 of 303 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user