Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9deb8cc237 | ||
|
|
11ff46a056 | ||
|
|
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
+119
-13
@@ -31,7 +31,24 @@
|
||||
"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": [],
|
||||
@@ -110,20 +127,104 @@
|
||||
},
|
||||
{
|
||||
"name": "Sparkd",
|
||||
"type": "coreclr",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Sparkd (Debug | Clang)",
|
||||
"program": "",
|
||||
"linux": {
|
||||
"program": "sparkd",
|
||||
},
|
||||
"args": [
|
||||
"run"
|
||||
],
|
||||
"cwd": "build/sparkd-debug-clang/server",
|
||||
"stopAtEntry": false,
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
"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 +255,10 @@
|
||||
"description": "Config Server URL",
|
||||
"default": "http://localhost:8000/config",
|
||||
},
|
||||
{
|
||||
"id": "sparkdTest",
|
||||
"type": "promptString",
|
||||
"description": "Test to run",
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
+17
-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,8 @@ else()
|
||||
|
||||
# subdirs
|
||||
if (BUILD_SPARKD)
|
||||
add_subdirectory(src/lib_binance_execution)
|
||||
add_subdirectory(src/lib_deribit_emumba)
|
||||
add_subdirectory(src/server/sparkd)
|
||||
add_subdirectory(src/data_files)
|
||||
endif()
|
||||
@@ -145,11 +154,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()
|
||||
+2
-11
@@ -61,7 +61,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 +302,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()
|
||||
@@ -0,0 +1,25 @@
|
||||
include(ExternalProject)
|
||||
|
||||
if(NOT TARGET websocket-library_project)
|
||||
ExternalProject_Add(websocket-library_project
|
||||
GIT_REPOSITORY https://fintech.websocket:h_ubh-4dsWRB569wwKyg@gitlab.com/cryptoexchanges1/websocket-client-using-epoll.git
|
||||
GIT_TAG enhancement/config-ssl
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/websocket-library
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DLOG_LEVEL=INFO
|
||||
-DENABLE_ENCRYPTION=True
|
||||
)
|
||||
endif()
|
||||
|
||||
ExternalProject_Get_Property(websocket-library_project BINARY_DIR)
|
||||
ExternalProject_Get_Property(websocket-library_project SOURCE_DIR)
|
||||
|
||||
set(websocket-library_INCLUDE_DIR ${SOURCE_DIR}/include)
|
||||
set(websocket-library_LIB_DIR ${BINARY_DIR}/src)
|
||||
|
||||
add_library(websocket-library STATIC IMPORTED)
|
||||
|
||||
set_property(TARGET websocket-library PROPERTY IMPORTED_LOCATION ${websocket-library_LIB_DIR}/libemumba-websocket-library.a)
|
||||
add_dependencies(websocket-library websocket-library_project)
|
||||
install(FILES ${websocket-library_LIB_DIR}/libemumba-websocket-library.a DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
+1
-1
Submodule externals updated: ff5f02bbde...8069cc893f.
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -62,6 +62,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 {
|
||||
@@ -452,8 +453,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);
|
||||
|
||||
@@ -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) {
|
||||
@@ -254,8 +247,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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,18 @@ 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_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 +1169,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 +1205,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: ""
|
||||
|
||||
@@ -4028,6 +4028,10 @@ JetFIX {
|
||||
name: "deribit_1"
|
||||
venue: VENUE_DERIBIT
|
||||
}
|
||||
connection {
|
||||
name: "okx_1"
|
||||
venue: VENUE_OKX
|
||||
}
|
||||
# CME EXECUTION NEEDS TO BE UPDATED, probably below
|
||||
# connection {
|
||||
# name: "onixs_sim"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
+206
-6
@@ -3,6 +3,7 @@
|
||||
#include "Side.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/utils/bts_array_size.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -33,6 +34,22 @@
|
||||
|
||||
namespace bts::domain {
|
||||
|
||||
static const double powers_10[] = {
|
||||
1E-20, 1E-19, 1E-18, 1E-17, 1E-16, 1E-15, 1E-14, 1E-13, 1E-12, 1E-11, 1E-10,
|
||||
1E-9, 1E-8, 1E-7, 1E-6, 1E-5, 1E-4, 1E-3, 1E-2, 1E-1, 1E0, 1E1,
|
||||
1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12,
|
||||
1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19, 1E20};
|
||||
static const int max_abs_power_10 = BTS_ARRAYSIZE(powers_10) / 2;
|
||||
inline double pow10(int power) {
|
||||
const int offset = power + max_abs_power_10;
|
||||
const int powers_10_sz = BTS_ARRAYSIZE(powers_10);
|
||||
bts_assert_d(offset >= 0 && offset < powers_10_sz);
|
||||
if (offset >= 0 && offset < powers_10_sz) {
|
||||
return powers_10[offset];
|
||||
}
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
class Price {
|
||||
public:
|
||||
Price() = default;
|
||||
@@ -120,6 +137,40 @@ class Price {
|
||||
constexpr Price &operator*=(Price factor) noexcept;
|
||||
constexpr Price &operator/=(Price factor) noexcept;
|
||||
|
||||
constexpr bool operator==(double factor) const noexcept;
|
||||
constexpr bool operator!=(double factor) const noexcept;
|
||||
constexpr bool operator<(double factor) const noexcept;
|
||||
constexpr bool operator<=(double factor) const noexcept;
|
||||
constexpr bool operator>(double factor) const noexcept;
|
||||
constexpr bool operator>=(double factor) const noexcept;
|
||||
|
||||
constexpr Price operator+(double factor) const noexcept;
|
||||
constexpr Price operator-(double factor) const noexcept;
|
||||
constexpr Price operator*(double factor) const noexcept;
|
||||
constexpr Price operator/(double factor) const noexcept;
|
||||
|
||||
constexpr Price &operator+=(double factor) noexcept;
|
||||
constexpr Price &operator-=(double factor) noexcept;
|
||||
constexpr Price &operator*=(double factor) noexcept;
|
||||
constexpr Price &operator/=(double factor) noexcept;
|
||||
|
||||
constexpr bool operator==(std::int32_t factor) const noexcept;
|
||||
constexpr bool operator!=(std::int32_t factor) const noexcept;
|
||||
constexpr bool operator<(std::int32_t factor) const noexcept;
|
||||
constexpr bool operator<=(std::int32_t factor) const noexcept;
|
||||
constexpr bool operator>(std::int32_t factor) const noexcept;
|
||||
constexpr bool operator>=(std::int32_t factor) const noexcept;
|
||||
|
||||
constexpr Price operator+(std::int32_t factor) const noexcept;
|
||||
constexpr Price operator-(std::int32_t factor) const noexcept;
|
||||
constexpr Price operator*(std::int32_t factor) const noexcept;
|
||||
constexpr Price operator/(std::int32_t factor) const noexcept;
|
||||
|
||||
constexpr Price &operator+=(std::int32_t factor) noexcept;
|
||||
constexpr Price &operator-=(std::int32_t factor) noexcept;
|
||||
constexpr Price &operator*=(std::int32_t factor) noexcept;
|
||||
constexpr Price &operator/=(std::int32_t factor) noexcept;
|
||||
|
||||
constexpr bool operator==(std::int64_t factor) const noexcept;
|
||||
constexpr bool operator!=(std::int64_t factor) const noexcept;
|
||||
constexpr bool operator<(std::int64_t factor) const noexcept;
|
||||
@@ -431,7 +482,8 @@ inline void to_json(nlohmann::json &j, const Price &price) {
|
||||
if (is_nan() || factor.is_nan()) { \
|
||||
return nan(); \
|
||||
} \
|
||||
return Price{_mantissa op factor._mantissa}; \
|
||||
return Price{_mantissa op static_cast<std::int64_t>( \
|
||||
factor._mantissa * pow10(factor.exponent() - exponent()))}; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
@@ -440,7 +492,103 @@ inline void to_json(nlohmann::json &j, const Price &price) {
|
||||
if (factor.is_nan()) { \
|
||||
_mantissa = nan()._mantissa; \
|
||||
} else if (!is_nan()) { \
|
||||
_mantissa op factor._mantissa; \
|
||||
_mantissa op static_cast<std::int64_t>( \
|
||||
factor._mantissa * pow10(factor.exponent() - exponent())); \
|
||||
} \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_DOUBLE_COMPARISON(op, nan_part) \
|
||||
constexpr bool Price::operator op(double factor) const noexcept { \
|
||||
return nan_part _mantissa op factor; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_DOUBLE_BIN_OP(op) \
|
||||
constexpr Price Price::operator op(double factor) const noexcept { \
|
||||
if (is_nan() || std::isnan(factor)) { \
|
||||
return nan(); \
|
||||
} \
|
||||
return Price{static_cast<std::int64_t>(_mantissa op factor)}; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_DOUBLE_EXP_BIN_OP(op) \
|
||||
constexpr Price Price::operator op(double factor) const noexcept { \
|
||||
if (is_nan() || std::isnan(factor)) { \
|
||||
return nan(); \
|
||||
} \
|
||||
return Price{_mantissa op static_cast<std::int64_t>( \
|
||||
factor * pow10(-exponent()))}; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_DOUBLE_BIN_REF_OP(op) \
|
||||
constexpr Price &Price::operator op(double factor) noexcept { \
|
||||
if (!is_nan()) { \
|
||||
if (std::isnan(factor)) { \
|
||||
*this = nan(); \
|
||||
} else { \
|
||||
_mantissa op factor; \
|
||||
} \
|
||||
} \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_DOUBLE_EXP_BIN_REF_OP(op) \
|
||||
constexpr Price &Price::operator op(double factor) noexcept { \
|
||||
if (!is_nan()) { \
|
||||
if (std::isnan(factor)) { \
|
||||
*this = nan(); \
|
||||
} else { \
|
||||
_mantissa op factor *pow10(-exponent()); \
|
||||
} \
|
||||
} \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_INT32_COMPARISON(op, nan_part) \
|
||||
constexpr bool Price::operator op(std::int32_t factor) const noexcept { \
|
||||
return nan_part _mantissa op static_cast<std::int64_t>( \
|
||||
factor * pow10(-exponent())); \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_INT32_BIN_OP(op) \
|
||||
constexpr Price Price::operator op(std::int32_t factor) const noexcept { \
|
||||
if (is_nan()) { \
|
||||
return *this; \
|
||||
} \
|
||||
return Price{_mantissa op static_cast<std::int64_t>( \
|
||||
factor * pow10(-exponent()))}; \
|
||||
}
|
||||
|
||||
#define PRICE_INT32_EXP_BIN_OP(op) \
|
||||
constexpr Price Price::operator op(std::int32_t factor) const noexcept { \
|
||||
if (is_nan()) { \
|
||||
return *this; \
|
||||
} \
|
||||
return Price{_mantissa op static_cast<std::int64_t>( \
|
||||
factor * pow10(-exponent()))}; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_INT32_BIN_REF_OP(op) \
|
||||
constexpr Price &Price::operator op(std::int32_t factor) noexcept { \
|
||||
if (!is_nan()) { \
|
||||
_mantissa op factor; \
|
||||
} \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_INT32_EXP_BIN_REF_OP(op) \
|
||||
constexpr Price &Price::operator op(std::int32_t factor) noexcept { \
|
||||
if (!is_nan()) { \
|
||||
_mantissa op static_cast<std::int64_t>(factor * pow10(-exponent())); \
|
||||
} \
|
||||
return *this; \
|
||||
}
|
||||
@@ -460,6 +608,15 @@ inline void to_json(nlohmann::json &j, const Price &price) {
|
||||
return Price{_mantissa op factor}; \
|
||||
}
|
||||
|
||||
#define PRICE_INT64_EXP_BIN_OP(op) \
|
||||
constexpr Price Price::operator op(std::int64_t factor) const noexcept { \
|
||||
if (is_nan()) { \
|
||||
return *this; \
|
||||
} \
|
||||
return Price{_mantissa op( \
|
||||
factor * static_cast<std::int64_t>(pow10(-exponent())))}; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_INT64_BIN_REF_OP(op) \
|
||||
constexpr Price &Price::operator op(std::int64_t factor) noexcept { \
|
||||
@@ -469,6 +626,15 @@ inline void to_json(nlohmann::json &j, const Price &price) {
|
||||
return *this; \
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
#define PRICE_INT64_EXP_BIN_REF_OP(op) \
|
||||
constexpr Price &Price::operator op(std::int64_t factor) noexcept { \
|
||||
if (!is_nan()) { \
|
||||
_mantissa op static_cast<std::int64_t>(factor * pow10(-exponent())); \
|
||||
} \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
PRICE_PRICE_COMPARISON(!=, is_nan() ||)
|
||||
PRICE_PRICE_COMPARISON(==, !is_nan() &&)
|
||||
PRICE_PRICE_COMPARISON(<, !is_nan() &&)
|
||||
@@ -486,6 +652,40 @@ PRICE_PRICE_BIN_REF_OP(-=)
|
||||
PRICE_PRICE_BIN_REF_OP(*=)
|
||||
PRICE_PRICE_BIN_REF_OP(/=)
|
||||
|
||||
PRICE_DOUBLE_COMPARISON(!=, is_nan() ||)
|
||||
PRICE_DOUBLE_COMPARISON(==, !is_nan() &&)
|
||||
PRICE_DOUBLE_COMPARISON(<, !is_nan() &&)
|
||||
PRICE_DOUBLE_COMPARISON(<=, !is_nan() &&)
|
||||
PRICE_DOUBLE_COMPARISON(>, !is_nan() &&)
|
||||
PRICE_DOUBLE_COMPARISON(>=, !is_nan() &&)
|
||||
|
||||
PRICE_DOUBLE_EXP_BIN_OP(+)
|
||||
PRICE_DOUBLE_EXP_BIN_OP(-)
|
||||
PRICE_DOUBLE_BIN_OP(*)
|
||||
PRICE_DOUBLE_BIN_OP(/)
|
||||
|
||||
PRICE_DOUBLE_EXP_BIN_REF_OP(+=)
|
||||
PRICE_DOUBLE_EXP_BIN_REF_OP(-=)
|
||||
PRICE_DOUBLE_BIN_REF_OP(*=)
|
||||
PRICE_DOUBLE_BIN_REF_OP(/=)
|
||||
|
||||
PRICE_INT32_COMPARISON(!=, is_nan() ||)
|
||||
PRICE_INT32_COMPARISON(==, !is_nan() &&)
|
||||
PRICE_INT32_COMPARISON(<, !is_nan() &&)
|
||||
PRICE_INT32_COMPARISON(<=, !is_nan() &&)
|
||||
PRICE_INT32_COMPARISON(>, !is_nan() &&)
|
||||
PRICE_INT32_COMPARISON(>=, !is_nan() &&)
|
||||
|
||||
PRICE_INT32_EXP_BIN_OP(+)
|
||||
PRICE_INT32_EXP_BIN_OP(-)
|
||||
PRICE_INT32_BIN_OP(*)
|
||||
PRICE_INT32_BIN_OP(/)
|
||||
|
||||
PRICE_INT32_EXP_BIN_REF_OP(+=)
|
||||
PRICE_INT32_EXP_BIN_REF_OP(-=)
|
||||
PRICE_INT32_BIN_REF_OP(*=)
|
||||
PRICE_INT32_BIN_REF_OP(/=)
|
||||
|
||||
PRICE_INT64_COMPARISON(!=, is_nan() ||)
|
||||
PRICE_INT64_COMPARISON(==, !is_nan() &&)
|
||||
PRICE_INT64_COMPARISON(<, !is_nan() &&)
|
||||
@@ -493,13 +693,13 @@ PRICE_INT64_COMPARISON(<=, !is_nan() &&)
|
||||
PRICE_INT64_COMPARISON(>, !is_nan() &&)
|
||||
PRICE_INT64_COMPARISON(>=, !is_nan() &&)
|
||||
|
||||
PRICE_INT64_BIN_OP(+)
|
||||
PRICE_INT64_BIN_OP(-)
|
||||
PRICE_INT64_EXP_BIN_OP(+)
|
||||
PRICE_INT64_EXP_BIN_OP(-)
|
||||
PRICE_INT64_BIN_OP(*)
|
||||
PRICE_INT64_BIN_OP(/)
|
||||
|
||||
PRICE_INT64_BIN_REF_OP(+=)
|
||||
PRICE_INT64_BIN_REF_OP(-=)
|
||||
PRICE_INT64_EXP_BIN_REF_OP(+=)
|
||||
PRICE_INT64_EXP_BIN_REF_OP(-=)
|
||||
PRICE_INT64_BIN_REF_OP(*=)
|
||||
PRICE_INT64_BIN_REF_OP(/=)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace securities {
|
||||
|
||||
using bts::api::Security;
|
||||
using bts::api::Venue;
|
||||
using std::optional;
|
||||
|
||||
api::SecurityType security_type(api::Security const &security) {
|
||||
return security_type(security.Contract_case());
|
||||
@@ -37,7 +38,7 @@ api::SecurityType security_type(api::Security::ContractCase contract_case) {
|
||||
return api::SecurityType::SECURITYTYPE_UNSET;
|
||||
}
|
||||
|
||||
boost::gregorian::date smallest_leg(
|
||||
std::optional<core::TimeStamp> smallest_leg(
|
||||
::google::protobuf::RepeatedPtrField<::bts::api::ContractLeg> const &l) {
|
||||
bts_assert(l.size());
|
||||
auto smallest = expiration(l[0].security());
|
||||
@@ -50,49 +51,59 @@ boost::gregorian::date smallest_leg(
|
||||
return smallest;
|
||||
}
|
||||
|
||||
boost::gregorian::date expiration(api::Security const &security) {
|
||||
std::optional<core::TimeStamp> expiration(api::Security const &security) {
|
||||
switch (security.Contract_case()) {
|
||||
case api::Security::kFuture:
|
||||
return {
|
||||
boost::gregorian::greg_year(security.future().expiration_year()),
|
||||
boost::gregorian::greg_month(security.future().expiration_month()),
|
||||
boost::gregorian::greg_day(security.future().expiration_day())};
|
||||
case api::Security::kOption:
|
||||
return {
|
||||
boost::gregorian::greg_year(security.option().expiration_year()),
|
||||
boost::gregorian::greg_month(security.option().expiration_month()),
|
||||
boost::gregorian::greg_day(security.option().expiration_day())};
|
||||
case api::Security::kFuture: {
|
||||
const auto &fut = security.future();
|
||||
core::TimeStamp exp;
|
||||
if (core::TimeStamp::from_ymd(
|
||||
fut.expiration_year(), fut.expiration_month(),
|
||||
fut.expiration_day(), &exp)) {
|
||||
return exp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case api::Security::kOption: {
|
||||
const auto &opt = security.option();
|
||||
core::TimeStamp exp;
|
||||
if (core::TimeStamp::from_ymd(
|
||||
opt.expiration_year(), opt.expiration_month(),
|
||||
opt.expiration_day(), &exp)) {
|
||||
return exp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case api::Security::kSpread:
|
||||
return smallest_leg(security.spread().legs());
|
||||
case api::Security::kSynthetic:
|
||||
return smallest_leg(security.synthetic().legs());
|
||||
default:
|
||||
return {};
|
||||
break;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool is_expired(
|
||||
api::Security const &security,
|
||||
datetime::PTime const &now,
|
||||
core::TimeStamp now,
|
||||
uint32_t sod_hh,
|
||||
uint32_t sod_mm) {
|
||||
auto e = expiration(security);
|
||||
if (e == boost::gregorian::date())
|
||||
auto maybe_exp = expiration(security);
|
||||
if (!maybe_exp) {
|
||||
return false;
|
||||
else {
|
||||
auto etime = datetime::PTime{
|
||||
e, boost::posix_time::time_duration(sod_hh, sod_mm, 0)};
|
||||
return etime < now;
|
||||
}
|
||||
const auto exp = *maybe_exp;
|
||||
return exp < now && (exp.date() + core::TimeSpan::from_hours(sod_hh) +
|
||||
core::TimeSpan::from_minutes(sod_mm)) < now;
|
||||
}
|
||||
|
||||
bool is_expired(api::Security const &security, datetime::PTime const &now) {
|
||||
bool is_expired(api::Security const &security, core::TimeStamp now) {
|
||||
const auto &sod = config::get<config::SODTime>();
|
||||
return is_expired(security, now, sod.sod_hh(), sod.sod_mm());
|
||||
}
|
||||
|
||||
bool is_expired(api::Security const &security) {
|
||||
return is_expired(security, datetime::local_now());
|
||||
return is_expired(security, core::TimeStamp::now());
|
||||
}
|
||||
|
||||
void write_date_string(
|
||||
|
||||
@@ -237,13 +237,13 @@ round_to_quote_price(api::Security const &sec, Price in, api::Side side) {
|
||||
raw_round_to_quote_price(p, sec.contract_spec().low_tick(), side));
|
||||
}
|
||||
|
||||
boost::gregorian::date expiration(api::Security const &sec);
|
||||
std::optional<core::TimeStamp> expiration(api::Security const &sec);
|
||||
bool is_expired(api::Security const &security);
|
||||
// for testing
|
||||
bool is_expired(api::Security const &security, datetime::PTime const &now);
|
||||
bool is_expired(api::Security const &security, core::TimeStamp now);
|
||||
bool is_expired(
|
||||
api::Security const &security,
|
||||
datetime::PTime const &now,
|
||||
core::TimeStamp now,
|
||||
uint32_t sod_hh,
|
||||
uint32_t sod_mm);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ enum Venue {
|
||||
VENUE_DERIBIT = 400;
|
||||
VENUE_FTX = 410;
|
||||
VENUE_BINANCE = 420;
|
||||
VENUE_BINANCE_COIN_FUTURES = 425;
|
||||
VENUE_BINANCE_USDS_FUTURES = 430;
|
||||
VENUE_BINANCE_US = 440;
|
||||
VENUE_COINBASE = 450;
|
||||
@@ -106,6 +107,11 @@ message BinanceExchangeData {
|
||||
string security_desc = 2;
|
||||
}
|
||||
|
||||
message BinanceCoinFuturesExchangeData {
|
||||
int64 exchange_id = 1;
|
||||
string security_desc = 2;
|
||||
}
|
||||
|
||||
message BinanceUsdsFuturesExchangeData {
|
||||
int64 exchange_id = 1;
|
||||
string security_desc = 2;
|
||||
@@ -164,12 +170,20 @@ message OptionContract {
|
||||
message SpotContract{
|
||||
}
|
||||
|
||||
// his tells the relationshio of a leg with thr previous leg
|
||||
enum PriceCalc {
|
||||
ADD = 0;
|
||||
MULT = 1;
|
||||
Div = 2;
|
||||
}
|
||||
|
||||
message ContractLeg {
|
||||
Security security = 10;
|
||||
double quantity = 20;
|
||||
Side side = 30;
|
||||
double price_multiplier = 40;
|
||||
double leg_delta = 50;
|
||||
PriceCalc price_calc = 60;
|
||||
}
|
||||
|
||||
message SpreadContract {
|
||||
@@ -228,6 +242,7 @@ message Security {
|
||||
DeribitExchangeData deribit = 240;
|
||||
FTXExchangeData ftx = 250;
|
||||
BinanceExchangeData binance = 260;
|
||||
BinanceCoinFuturesExchangeData binance_coin_futures = 265;
|
||||
BinanceUsdsFuturesExchangeData binance_usds_futures = 270;
|
||||
BinanceUsExchangeData binance_us = 280;
|
||||
CoinbaseExchangeData coinbase = 290;
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/string_utils.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::api {
|
||||
|
||||
/**
|
||||
* Parses Venue from a string. Unlike `bts::api::SecurityType_Parse`, this implementation is case
|
||||
* insensitive, and makes the 'SECURITYTYPE_' prefix optional.
|
||||
*/
|
||||
bool security_type_parse_smart(const std::string &str, SecurityType *result);
|
||||
|
||||
PB_ENUM_FROM_JSON(SecurityType)
|
||||
PB_ENUM_TO_JSON(SecurityType)
|
||||
|
||||
@@ -19,6 +26,22 @@ std::ostream &operator<<(std::ostream &out, const SecurityType &type) noexcept;
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline bool security_type_parse_smart(
|
||||
const std::string &str, SecurityType *result) {
|
||||
if (SecurityType_Parse(str, result)) {
|
||||
return true;
|
||||
}
|
||||
std::string s = bts::system::string_utils::copy_to_upper(str);
|
||||
if (SecurityType_Parse(s, result)) {
|
||||
return true;
|
||||
}
|
||||
const char *prefix = "SECURITYTYPE_";
|
||||
if (!s.starts_with(prefix)) {
|
||||
s.insert(0, prefix);
|
||||
}
|
||||
return SecurityType_Parse(s, result);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out, const SecurityType &type) noexcept {
|
||||
out << SecurityType_Name(type);
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/io/JsonReader.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/string_utils.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::api {
|
||||
|
||||
/**
|
||||
* Parses Venue from a string. Unlike `bts::api::Venue_Parse`, this implementation is case
|
||||
* insensitive, and makes the 'VENUE_' prefix optional.
|
||||
*/
|
||||
bool venue_parse_smart(const std::string &str, Venue *result);
|
||||
|
||||
PB_ENUM_FROM_JSON(Venue)
|
||||
PB_ENUM_TO_JSON(Venue)
|
||||
|
||||
@@ -19,6 +26,21 @@ std::ostream &operator<<(std::ostream &out, Venue venue) noexcept;
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline bool venue_parse_smart(const std::string &str, Venue *result) {
|
||||
if (Venue_Parse(str, result)) {
|
||||
return true;
|
||||
}
|
||||
std::string s = bts::system::string_utils::copy_to_upper(str);
|
||||
if (Venue_Parse(s, result)) {
|
||||
return true;
|
||||
}
|
||||
const char *prefix = "VENUE_";
|
||||
if (!s.starts_with(prefix)) {
|
||||
s.insert(0, prefix);
|
||||
}
|
||||
return Venue_Parse(s, result);
|
||||
}
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &out, Venue venue) noexcept {
|
||||
out << Venue_Name(venue);
|
||||
return out;
|
||||
|
||||
@@ -116,4 +116,4 @@ using UserLogin = utils::ValueWrapper<struct UserLoginTag, std::string>;
|
||||
} // namespace bts
|
||||
|
||||
FMT_IMPL(bts::Price, p, "{}", p.value());
|
||||
FMT_IMPL(bts::Quantity, q, "{}", q.value());
|
||||
FMT_IMPL(bts::Quantity, q, "{}", q.full_value());
|
||||
@@ -210,37 +210,22 @@ auto fut_text = " id: 6\n"
|
||||
|
||||
TEST_CASE("expiration_tests", "[security]") {
|
||||
SECTION("test_expiration_retrieval") {
|
||||
|
||||
using namespace boost::gregorian;
|
||||
|
||||
auto spread = utils::pb::from_string<api::Security>(spread_txt);
|
||||
REQUIRE(
|
||||
securities::expiration(spread) ==
|
||||
date(greg_year(2019), greg_month(1), greg_day(20)));
|
||||
core::TimeStamp actual_exp = *securities::expiration(spread);
|
||||
REQUIRE((actual_exp == core::TimeStamp{2019, 1, 20}));
|
||||
auto &leg = spread.spread().legs(1).security();
|
||||
REQUIRE(
|
||||
securities::expiration(leg) ==
|
||||
date(greg_year(3020), greg_month(6), greg_day(19)));
|
||||
actual_exp = *securities::expiration(leg);
|
||||
REQUIRE((actual_exp == core::TimeStamp{3020, 6, 19}));
|
||||
REQUIRE(securities::is_expired(spread));
|
||||
REQUIRE(!securities::is_expired(leg));
|
||||
}
|
||||
|
||||
SECTION("test_expiration_sod_boundary") {
|
||||
|
||||
using namespace boost::gregorian;
|
||||
|
||||
auto fut = utils::pb::from_string<api::Security>(fut_text);
|
||||
REQUIRE(securities::is_expired(
|
||||
fut, datetime::make_ptime(2019, 1, 21, 0, 0, 0, 0), 16, 30));
|
||||
|
||||
REQUIRE(!securities::is_expired(
|
||||
fut, datetime::make_ptime(2019, 1, 19, 0, 0, 0, 0), 16, 30));
|
||||
|
||||
REQUIRE(!securities::is_expired(
|
||||
fut, datetime::make_ptime(2019, 1, 20, 16, 29, 0, 0), 16, 30));
|
||||
|
||||
REQUIRE(securities::is_expired(
|
||||
fut, datetime::make_ptime(2019, 1, 20, 16, 31, 0, 0), 16, 30));
|
||||
REQUIRE(securities::is_expired(fut, {2019, 1, 21}, 16, 30));
|
||||
REQUIRE(!securities::is_expired(fut, {2019, 1, 19}, 16, 30));
|
||||
REQUIRE(!securities::is_expired(fut, {2019, 1, 20, 16, 29, 0}, 16, 30));
|
||||
REQUIRE(securities::is_expired(fut, {2019, 1, 20, 16, 31, 0}, 16, 30));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#include "CurlHandle.h"
|
||||
#include <curl/curl.h>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
CurlHandle::CurlHandle() {
|
||||
const auto eCode = curl_global_init(CURL_GLOBAL_ALL);
|
||||
if (eCode != CURLE_OK) {
|
||||
throw std::runtime_error{"Error initializing libCURL"};
|
||||
}
|
||||
}
|
||||
|
||||
CurlHandle::~CurlHandle() { curl_global_cleanup(); }
|
||||
|
||||
CurlHandle &CurlHandle::instance() {
|
||||
static CurlHandle inst{};
|
||||
return inst;
|
||||
}
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
class CurlHandle {
|
||||
public:
|
||||
static CurlHandle &instance();
|
||||
|
||||
CurlHandle(CurlHandle const &) = delete;
|
||||
CurlHandle(CurlHandle &&) = delete;
|
||||
|
||||
CurlHandle &operator=(CurlHandle const &) = delete;
|
||||
CurlHandle &operator=(CurlHandle &&) = delete;
|
||||
|
||||
~CurlHandle();
|
||||
|
||||
private:
|
||||
CurlHandle();
|
||||
};
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -1,55 +0,0 @@
|
||||
#include "HTTPClient.h"
|
||||
#include "HTTPRequest.h"
|
||||
#include "HTTPResponse.h"
|
||||
#include "lib/io/URL.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include <curl/curl.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace bts::io::http {
|
||||
|
||||
static size_t write_data_(
|
||||
void *ptr, size_t size, size_t nmemb, std::string *data) {
|
||||
data->append((char *)ptr, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
HTTPResponse HTTPClient::get(const HTTPRequest &request) {
|
||||
auto *curl = curl_easy_init();
|
||||
auto url_str = request.url().to_string();
|
||||
// LOG(md, bts::Info, "do GET: {}", url_str);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url_str.c_str());
|
||||
|
||||
struct curl_slist *request_headers = NULL;
|
||||
for (const auto &[k, v] : request.headers()) {
|
||||
request_headers = curl_slist_append(
|
||||
request_headers, fmt::format("{}: {}", k, v).c_str());
|
||||
}
|
||||
|
||||
std::string response_string;
|
||||
std::string header_string;
|
||||
std::string response_header_string;
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data_);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_string);
|
||||
|
||||
char *url;
|
||||
long response_code;
|
||||
double elapsed;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);
|
||||
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
|
||||
// curl_easy_getinfo(curl, CURLINFO_HEADER_OUT, &response_header_string);
|
||||
|
||||
curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
curl = NULL;
|
||||
HTTPResponse response;
|
||||
response.headers() = HTTPHeaders(response_header_string);
|
||||
response.body() = std::move(response_string);
|
||||
response.code() = response_code;
|
||||
// LOG(md, bts::Info, "do GET: {}. Received response:\n{}", url_str, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace bts::io::http
|
||||
@@ -1,35 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstdlib>
|
||||
#include <cstring> // strerror, strlen, memcpy, strcpy
|
||||
#include <ctime>
|
||||
#include <curl/curl.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <stdarg.h> // va_start, etc.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::io::http {
|
||||
|
||||
class HTTPRequest;
|
||||
class HTTPResponse;
|
||||
|
||||
class HTTPClient {
|
||||
public:
|
||||
// HTTPClient(const std::string &url);
|
||||
HTTPResponse get(const HTTPRequest &request);
|
||||
};
|
||||
|
||||
} // namespace bts::io::http
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "HTTPRequest.h"
|
||||
|
||||
namespace bts::io::http {
|
||||
|
||||
io::URL &HTTPRequest::url() { return url_; }
|
||||
|
||||
const io::URL &HTTPRequest::url() const { return url_; }
|
||||
|
||||
HTTPHeaders &HTTPRequest::headers() { return headers_; }
|
||||
|
||||
const HTTPHeaders &HTTPRequest::headers() const { return headers_; }
|
||||
|
||||
std::string &HTTPRequest::body() { return body_; }
|
||||
|
||||
const std::string &HTTPRequest::body() const { return body_; }
|
||||
|
||||
} // namespace bts::io::http
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
#include "HTTPHeaders.h"
|
||||
#include "lib/io/URL.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bts::io::http {
|
||||
|
||||
class HTTPRequest {
|
||||
public:
|
||||
io::URL &url();
|
||||
const io::URL &url() const;
|
||||
|
||||
HTTPHeaders &headers();
|
||||
const HTTPHeaders &headers() const;
|
||||
|
||||
std::string &body();
|
||||
const std::string &body() const;
|
||||
|
||||
private:
|
||||
io::URL url_;
|
||||
HTTPHeaders headers_;
|
||||
std::string body_;
|
||||
};
|
||||
|
||||
} // namespace bts::io::http
|
||||
@@ -14,7 +14,9 @@ namespace bts::io::http {
|
||||
#define FOR_ALL_VALUES(per_value) \
|
||||
per_value(HttpMethod, NONE) \
|
||||
per_value(HttpMethod, GET) \
|
||||
per_value(HttpMethod, POST)
|
||||
per_value(HttpMethod, POST) \
|
||||
per_value(HttpMethod, PUT) \
|
||||
per_value(HttpMethod, DELETE)
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <curl/curl.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
class CurlHeaders {
|
||||
public:
|
||||
CurlHeaders() = default;
|
||||
|
||||
explicit CurlHeaders(
|
||||
const std::unordered_map<std::string, std::string> &items) {
|
||||
for (const auto &[k, v] : items) {
|
||||
append(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
~CurlHeaders() {
|
||||
if (data_) {
|
||||
curl_slist_free_all(data_);
|
||||
}
|
||||
}
|
||||
|
||||
void append(const std::string &key, const std::string &value) {
|
||||
const std::string str = key + ": " + value;
|
||||
data_ = curl_slist_append(data_, str.c_str());
|
||||
}
|
||||
|
||||
curl_slist *data() { return data_; }
|
||||
|
||||
private:
|
||||
curl_slist *data_ = NULL;
|
||||
};
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <curl/curl.h>
|
||||
#include <curl/easy.h>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
struct CurlSession {
|
||||
static inline std::atomic<int> ref_count = 0;
|
||||
CURL *curl = nullptr;
|
||||
|
||||
CurlSession() {
|
||||
if (++ref_count == 1) {
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
}
|
||||
curl = curl_easy_init();
|
||||
}
|
||||
|
||||
~CurlSession() {
|
||||
curl_easy_cleanup(curl);
|
||||
if (--ref_count == 0) {
|
||||
curl_global_cleanup();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "http_client.h"
|
||||
#include "lib/io/JsonDumper.h"
|
||||
#include "lib/io/http/HttpMethod.h"
|
||||
#include "lib/io/http/curl_headers.h"
|
||||
#include "lib/io/http/curl_session.h"
|
||||
#include "lib/io/http/http_request.h"
|
||||
#include "lib/io/http/http_response.h"
|
||||
#include "lib/io/http/i_http_response.h"
|
||||
#include "lib/io/http/query_params.h"
|
||||
#include "lib/logging/logging.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <curl/easy.h>
|
||||
#include <string>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
using bts::io::JsonDumper;
|
||||
using nlohmann::json;
|
||||
using std::exception;
|
||||
using std::int64_t;
|
||||
using std::pair;
|
||||
using std::string;
|
||||
using std::tuple;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
static size_t write_data(
|
||||
void *ptr, size_t size, size_t nmemb, IHttpResponse *data) {
|
||||
return data->write((char *)ptr, size, nmemb);
|
||||
}
|
||||
|
||||
static size_t write_headers(
|
||||
void *ptr, size_t size, size_t nmemb, IHttpResponse *data) {
|
||||
return data->write_headers((char *)ptr, size, nmemb);
|
||||
}
|
||||
|
||||
void HttpClient::get(
|
||||
string url,
|
||||
unordered_map<string, string> headers,
|
||||
IHttpResponse *response) {
|
||||
HttpRequest req;
|
||||
req.url = std::move(url);
|
||||
req.headers = std::move(headers);
|
||||
req.method = io::http::HttpMethod::GET;
|
||||
send(req, response);
|
||||
}
|
||||
|
||||
void HttpClient::post(
|
||||
string url,
|
||||
string body,
|
||||
unordered_map<string, string> headers,
|
||||
IHttpResponse *response) {
|
||||
HttpRequest req;
|
||||
req.url = std::move(url);
|
||||
req.body = std::move(body);
|
||||
req.headers = std::move(headers);
|
||||
req.method = io::http::HttpMethod::POST;
|
||||
send(req, response);
|
||||
}
|
||||
|
||||
void HttpClient::put(
|
||||
string url,
|
||||
string body,
|
||||
unordered_map<string, string> headers,
|
||||
IHttpResponse *response) {
|
||||
HttpRequest req;
|
||||
req.url = std::move(url);
|
||||
req.body = std::move(body);
|
||||
req.headers = std::move(headers);
|
||||
req.method = io::http::HttpMethod::PUT;
|
||||
send(req, response);
|
||||
}
|
||||
|
||||
void HttpClient::del(
|
||||
string url,
|
||||
string body,
|
||||
unordered_map<string, string> headers,
|
||||
IHttpResponse *response) {
|
||||
HttpRequest req;
|
||||
req.url = std::move(url);
|
||||
req.body = std::move(body);
|
||||
req.headers = std::move(headers);
|
||||
req.method = io::http::HttpMethod::DELETE;
|
||||
send(req, response);
|
||||
}
|
||||
|
||||
void HttpClient::send(const HttpRequest &req, IHttpResponse *response) {
|
||||
LOG(Http, Info, "HttpClient::send. {}", JsonDumper::dump(req));
|
||||
CurlSession session;
|
||||
CURL *curl = session.curl;
|
||||
CurlHeaders curl_headers{req.headers};
|
||||
// Prepare req
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers.data());
|
||||
if (response != nullptr) {
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, write_headers);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, response);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
|
||||
}
|
||||
switch (req.method.value) {
|
||||
case io::http::HttpMethod::GET:
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
|
||||
break;
|
||||
case io::http::HttpMethod::POST:
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req.body.size());
|
||||
break;
|
||||
case io::http::HttpMethod::PUT:
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
// curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, req.body.size());
|
||||
break;
|
||||
case io::http::HttpMethod::DELETE:
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.body.c_str());
|
||||
break;
|
||||
case io::http::HttpMethod::NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Send it
|
||||
CURLcode curl_code = curl_easy_perform(curl);
|
||||
long response_code;
|
||||
double elapsed;
|
||||
char *effective_url = NULL;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &elapsed);
|
||||
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
||||
const char *err = curl_easy_strerror(curl_code);
|
||||
try {
|
||||
response->write_result(response_code, string{err}, string{effective_url});
|
||||
} catch (const exception &ex) {
|
||||
LOG(Http, Error, "HttpClient::perform. IHttpResponse threw: {}",
|
||||
string{ex.what()});
|
||||
} catch (...) {
|
||||
LOG(Http, Error, "HttpClient::perform. IHttpResponse threw unknown ex.");
|
||||
}
|
||||
}
|
||||
|
||||
tuple<string, string, int64_t> HttpClient::get_oauth_token(
|
||||
const string &url, const string &client_id, const string &client_secret) {
|
||||
LOG(Gen, Info, "Requesting oauth token from {}", url);
|
||||
vector<pair<string, string>> query_params{
|
||||
{"grant_type", "client_credentials"},
|
||||
{"client_id", client_id},
|
||||
{"client_secret", client_secret}};
|
||||
HttpResponse res;
|
||||
HttpClient::post(url, query_params_string(query_params), {}, &res);
|
||||
LOG(Gen, Info, "Received oauth token:\n{}", JsonDumper::dump(res));
|
||||
try {
|
||||
const auto j = json::parse(res.body);
|
||||
return {
|
||||
j.at("access_token").get<string>(), j.at("token_type").get<string>(),
|
||||
j.at("expires_in").get<int64_t>()};
|
||||
} catch (const exception &e) {
|
||||
LOG(Gen, Error, "Failed to parse oauth response json.{}\n{}",
|
||||
string{e.what()}, res.body);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/io/http/http_request.h"
|
||||
#include "lib/io/http/i_http_response.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
class HttpClient {
|
||||
public:
|
||||
/**
|
||||
* Performs an http GET request
|
||||
*/
|
||||
static void get(
|
||||
std::string url,
|
||||
std::unordered_map<std::string, std::string> headers,
|
||||
IHttpResponse *response);
|
||||
|
||||
/**
|
||||
* Performs an http POST request
|
||||
*/
|
||||
static void post(
|
||||
std::string url,
|
||||
std::string body,
|
||||
std::unordered_map<std::string, std::string> headers,
|
||||
IHttpResponse *response);
|
||||
|
||||
/**
|
||||
* Performs an http PUT request
|
||||
*/
|
||||
static void put(
|
||||
std::string url,
|
||||
std::string body,
|
||||
std::unordered_map<std::string, std::string> headers,
|
||||
IHttpResponse *response);
|
||||
|
||||
/**
|
||||
* Performs an http DELETE request
|
||||
*/
|
||||
static void del(
|
||||
std::string url,
|
||||
std::string body,
|
||||
std::unordered_map<std::string, std::string> headers,
|
||||
IHttpResponse *response);
|
||||
|
||||
/**
|
||||
* Requests an oauth access token
|
||||
*
|
||||
* @return tuple of: access token, token type, and seconds until expiration
|
||||
*/
|
||||
static std::tuple<std::string, std::string, std::int64_t> get_oauth_token(
|
||||
const std::string &url,
|
||||
const std::string &client_id,
|
||||
const std::string &client_secret);
|
||||
|
||||
static void send(const HttpRequest &req, IHttpResponse *res);
|
||||
};
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/io/http/HttpMethod.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
class HttpRequest {
|
||||
public:
|
||||
std::string url;
|
||||
std::string body;
|
||||
io::http::HttpMethod method;
|
||||
std::unordered_map<std::string, std::string> headers;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline void HttpRequest::reset() {
|
||||
url.clear();
|
||||
body.clear();
|
||||
method = io::http::HttpMethod::NONE;
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json &j, const HttpRequest &req) {
|
||||
j["url"] = req.url;
|
||||
j["body"] = req.body;
|
||||
j["method"] = req.method.name();
|
||||
nlohmann::json hdrs;
|
||||
for (const auto &[k, v] : req.headers) {
|
||||
hdrs[k] = v;
|
||||
}
|
||||
j["headers"] = hdrs;
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json &j, HttpRequest &req) {
|
||||
j.at("url").get_to(req.url);
|
||||
j.at("body").get_to(req.body);
|
||||
j.at("method").get_to(req.method);
|
||||
const auto &hdrs = j.at("headers");
|
||||
for (auto it = hdrs.begin(); it != hdrs.end(); it++) {
|
||||
it->get_to(req.headers[it.key()]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/io/http/i_http_response.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
/**
|
||||
* Basic implementation of `IHttpResponse`, with some useful state.
|
||||
*/
|
||||
class HttpResponse : public IHttpResponse {
|
||||
public:
|
||||
std::string url;
|
||||
std::unordered_map<std::string, std::string> headers;
|
||||
std::string body;
|
||||
std::int64_t code;
|
||||
std::string errors;
|
||||
|
||||
std::size_t write(char *cstr, std::size_t sz, std::size_t n) override;
|
||||
std::size_t write_headers(
|
||||
char *cstr, std::size_t sz, std::size_t n) override;
|
||||
void write_result(
|
||||
std::int64_t code,
|
||||
std::string &&errors,
|
||||
std::string &&effective_url) override;
|
||||
constexpr bool ok() const;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json &j, const HttpResponse &res);
|
||||
void from_json(const nlohmann::json &j, HttpResponse &res);
|
||||
|
||||
std::ostream &operator<<(
|
||||
std::ostream &out, const HttpResponse &response) noexcept;
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline std::size_t HttpResponse::write(
|
||||
char *cstr, std::size_t sz, std::size_t n) {
|
||||
body.append(cstr, sz * n);
|
||||
return sz * n;
|
||||
}
|
||||
|
||||
inline std::size_t HttpResponse::write_headers(
|
||||
char *cstr, std::size_t sz, std::size_t n) {
|
||||
std::string str{cstr, sz * n};
|
||||
const auto colon_idx = str.find(':');
|
||||
if (colon_idx >= str.size()) {
|
||||
headers[str] = {};
|
||||
} else {
|
||||
headers[str.substr(0, colon_idx)] =
|
||||
str.substr(colon_idx + 1, str.size() - colon_idx - 1);
|
||||
}
|
||||
return sz * n;
|
||||
}
|
||||
|
||||
inline void HttpResponse::write_result(
|
||||
std::int64_t code, std::string &&errors, std::string &&effective_url) {
|
||||
this->errors = errors;
|
||||
this->code = code;
|
||||
this->url = effective_url;
|
||||
}
|
||||
|
||||
constexpr bool HttpResponse::ok() const { return code >= 200 && code < 300; }
|
||||
|
||||
inline void HttpResponse::reset() {
|
||||
url.clear();
|
||||
headers.clear();
|
||||
body.clear();
|
||||
errors.clear();
|
||||
code = 0;
|
||||
}
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
|
||||
HttpResponse, url, headers, body, code, errors);
|
||||
|
||||
inline std::ostream &operator<<(
|
||||
std::ostream &out, const HttpResponse &res) noexcept {
|
||||
return out << nlohmann::json{res}.dump();
|
||||
}
|
||||
|
||||
} // namespace bts::http
|
||||
|
||||
FMT_IMPL(bts::http::HttpResponse, r, "{}", nlohmann::json{r}.dump());
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
/**
|
||||
* Simple http response interface, modeled after cURL callbacks
|
||||
*/
|
||||
class IHttpResponse {
|
||||
public:
|
||||
virtual ~IHttpResponse() = default;
|
||||
|
||||
virtual std::size_t write(char *cstr, std::size_t sz, std::size_t n) = 0;
|
||||
virtual std::size_t write_headers(char *cstr, std::size_t sz, std::size_t n);
|
||||
virtual void write_result(
|
||||
std::int64_t code, std::string &&errors, std::string &&effective_url);
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Definition
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
inline std::size_t IHttpResponse::write_headers(
|
||||
char * /*cstr*/, std::size_t sz, std::size_t n) {
|
||||
return sz * n;
|
||||
}
|
||||
|
||||
inline void IHttpResponse::write_result(
|
||||
std::int64_t /*code*/,
|
||||
std::string && /*errors*/,
|
||||
std::string && /*effective_url*/) {}
|
||||
|
||||
} // namespace bts::http
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace bts::http {
|
||||
|
||||
template <class Iter>
|
||||
inline std::string query_params_string(Iter first, Iter last) {
|
||||
std::string rv;
|
||||
for (auto it = first; it != last; it++) {
|
||||
if (it->first.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (!rv.empty()) {
|
||||
rv.append("&");
|
||||
}
|
||||
rv.append(it->first);
|
||||
if (!it->second.empty()) {
|
||||
rv.append("=");
|
||||
rv.append(it->second);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
inline std::string query_params_string(Range &&range) {
|
||||
return query_params_string(range.begin(), range.end());
|
||||
}
|
||||
|
||||
class QueryParams : public std::unordered_map<std::string, std::string> {
|
||||
public:
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
inline std::string QueryParams::to_string() const {
|
||||
std::string rv;
|
||||
auto it = begin();
|
||||
if (it == end()) {
|
||||
return "";
|
||||
}
|
||||
rv.append(it->first + "=" + it->second);
|
||||
while (++it != end()) {
|
||||
rv.append("&" + it->first + "=" + it->second);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
inline bool operator==(const QueryParams &lhs, const QueryParams &rhs) {
|
||||
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
|
||||
}
|
||||
|
||||
inline bool operator!=(const QueryParams &lhs, const QueryParams &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace bts::http
|
||||
|
||||
FMT_IMPL(bts::http::QueryParams, p, "{}", p.to_string());
|
||||
@@ -0,0 +1,410 @@
|
||||
#include "websocket_client.h"
|
||||
|
||||
#include "lib/core/TimeSpan.h"
|
||||
#include "lib/core/TimeStamp.h"
|
||||
#include "lib/io/websocket/websocket_client_config.h"
|
||||
#include "lib/io/websocket/websocket_client_state.h"
|
||||
#include "lib/io/websocket/websocket_close_status.h"
|
||||
#include "lib/io/websocket/websocket_error_code.h"
|
||||
#include "lib/io/websocket/websocketpp_exception_formatter.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/formatters/exception_formatter.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <fmt/format.h>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <websocketpp/close.hpp>
|
||||
#include <websocketpp/common/connection_hdl.hpp>
|
||||
#include <websocketpp/common/memory.hpp>
|
||||
#include <websocketpp/common/system_error.hpp>
|
||||
#include <websocketpp/common/thread.hpp>
|
||||
#include <websocketpp/config/asio_client.hpp>
|
||||
#include <websocketpp/config/core_client.hpp>
|
||||
#include <websocketpp/roles/client_endpoint.hpp>
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
struct WebsocketClientSnapshot {
|
||||
int client_id;
|
||||
WebsocketClientState state;
|
||||
int version;
|
||||
};
|
||||
|
||||
} // namespace bts::websocket
|
||||
|
||||
FMT_IMPL(
|
||||
bts::websocket::WebsocketClientSnapshot,
|
||||
t,
|
||||
"id: {}; state: {}; version: {}",
|
||||
t.client_id,
|
||||
t.state,
|
||||
t.version);
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using bts::core::TimeSpan;
|
||||
using bts::core::TimeStamp;
|
||||
using nlohmann::json;
|
||||
using std::lock_guard;
|
||||
using std::map;
|
||||
using std::mutex;
|
||||
using std::optional;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
using std::thread;
|
||||
using std::uint32_t;
|
||||
using websocketpp::config::asio_tls_client;
|
||||
|
||||
using WebsocketppClient =
|
||||
websocketpp::client<websocketpp::config::asio_tls_client>;
|
||||
using ConnectionHdl = websocketpp::connection_hdl;
|
||||
using ConnectionPtr = WebsocketppClient::connection_ptr;
|
||||
using MessageTypePtr = websocketpp::config::core_client::message_type::ptr;
|
||||
using SslContext = boost::asio::ssl::context;
|
||||
using State = WebsocketClientState;
|
||||
|
||||
// ws endpoint http://markets.api.uat.cmegroup.com/marketdatastream/v1
|
||||
|
||||
#define _ASSERT_MSGF(condition, ...) \
|
||||
bts_assert_msgf(condition, "WebsocketClient::" __VA_ARGS__)
|
||||
|
||||
#define DIAG(...) LOG(Http, Diag, __VA_ARGS__)
|
||||
#define DIAG_FN(...) LOG(Http, Diag, "WebsocketClient::" __VA_ARGS__)
|
||||
#define INFO(...) LOG(Http, Info, __VA_ARGS__)
|
||||
#define INFO_FN(...) LOG(Http, Info, "WebsocketClient::" __VA_ARGS__)
|
||||
#define WARN(...) LOG(Http, Warn, __VA_ARGS__)
|
||||
#define WARN_FN(...) LOG(Http, Warn, "WebsocketClient::" __VA_ARGS__)
|
||||
#define ERROR(...) LOG(Http, Error, __VA_ARGS__)
|
||||
#define ERROR_FN(...) LOG(Http, Error, "WebsocketClient::" __VA_ARGS__)
|
||||
|
||||
struct WebsocketClient::PImpl {
|
||||
WebsocketClientConfig config;
|
||||
WebsocketppClient client{};
|
||||
WebsocketppClient::connection_ptr con;
|
||||
optional<OpenHandler> open_handler;
|
||||
optional<MessageHandler> message_handler;
|
||||
optional<CloseHandler> close_handler;
|
||||
size_t sub_id_counter{0};
|
||||
string server_name;
|
||||
optional<thread> io_thread;
|
||||
int client_id{client_id_counter++};
|
||||
mutex instance_mx;
|
||||
State state;
|
||||
int version = 0;
|
||||
|
||||
~PImpl() {
|
||||
if (io_thread) {
|
||||
io_thread->join();
|
||||
}
|
||||
}
|
||||
|
||||
int set_state(State state) {
|
||||
if (this->state == state) {
|
||||
return version;
|
||||
}
|
||||
version++;
|
||||
INFO(
|
||||
"State changing from {} -> {}. id: {}; version: {}", this->state,
|
||||
state, client_id, version);
|
||||
this->state = state;
|
||||
return version;
|
||||
}
|
||||
|
||||
constexpr WebsocketClientSnapshot snapshot() const {
|
||||
return {client_id, state, version};
|
||||
}
|
||||
|
||||
void close(websocketpp::close::status::value code, std::string reason) {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
INFO_FN(
|
||||
"close. {}; code: {}; reason: {}", snapshot(),
|
||||
WebsocketCloseStatus{code}, reason);
|
||||
switch (state.value) {
|
||||
case State::OPENING:
|
||||
WARN_FN(
|
||||
"close. Cannot close while websocket is still opening. {}",
|
||||
snapshot());
|
||||
// Wait until we get the 'open' callback, then initiate the close
|
||||
set_state(State::WAITING_TO_CLOSE);
|
||||
return;
|
||||
case State::OPEN:
|
||||
set_state(State::CLOSING);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
websocketpp::lib::error_code ec;
|
||||
client.close(con->get_handle(), code, reason, ec);
|
||||
if (ec) {
|
||||
ERROR_FN(
|
||||
"close. {}; code: {}; reason: {}; error: {}", snapshot(),
|
||||
WebsocketCloseStatus{code}, reason, ec);
|
||||
}
|
||||
}
|
||||
|
||||
websocketpp::lib::error_code send_text(const string &text) {
|
||||
INFO_FN("send_text. {}; text: {}", snapshot(), text);
|
||||
websocketpp::lib::error_code ec;
|
||||
client.send(con, text, websocketpp::frame::opcode::value::TEXT, ec);
|
||||
if (ec) {
|
||||
ERROR_FN("send_text. {}; text: {}; err: {}", snapshot(), text, ec);
|
||||
}
|
||||
return ec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the websocket server
|
||||
*/
|
||||
void connect(
|
||||
const map<string, string> &headers, bool run_in_background = true) {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
INFO_FN("connect. {}", snapshot());
|
||||
switch (state.value) {
|
||||
case State::IDLE:
|
||||
break;
|
||||
default:
|
||||
WARN_FN("connect. Unexpected state. {}", snapshot());
|
||||
return;
|
||||
}
|
||||
set_state(State::OPENING);
|
||||
try {
|
||||
init_logging();
|
||||
init_handlers();
|
||||
// Get connection
|
||||
websocketpp::lib::error_code ec;
|
||||
con = client.get_connection(config.url, ec);
|
||||
if (ec) {
|
||||
ERROR_FN("connect_no_timeout. {}; err: {}", snapshot(), ec);
|
||||
return;
|
||||
}
|
||||
// Add headers
|
||||
for (const auto &[k, v] : headers) {
|
||||
con->append_header(k, v);
|
||||
}
|
||||
// Connect
|
||||
client.connect(con);
|
||||
if (run_in_background) {
|
||||
bts_assert_msgf(
|
||||
!io_thread, "Websocket io_thread already instantiated. {}",
|
||||
snapshot());
|
||||
io_thread = std::thread{[this]() { run(); }};
|
||||
if (config.connect_timeout > core::TimeSpan::zero()) {
|
||||
wait_for_connect(config.connect_timeout);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN(
|
||||
"connect_no_timeout. Failed to connect. {}; err: {}", snapshot(),
|
||||
ex);
|
||||
set_state(State::IDLE);
|
||||
// Don't wait for connect
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void wait_for_connect(const TimeSpan timeout) {
|
||||
INFO_FN("wait_for_connect. {}; timeout: {}", snapshot(), timeout);
|
||||
const auto start = TimeStamp::now();
|
||||
while (state == State::OPENING) {
|
||||
const auto elapsed = TimeStamp::now() - start;
|
||||
const auto rem = timeout - elapsed;
|
||||
if (rem < TimeSpan::zero()) {
|
||||
throw std::runtime_error{fmt::format(
|
||||
"Timed out waiting for connection after {} secs. {}",
|
||||
elapsed.total_seconds(), snapshot())};
|
||||
}
|
||||
WARN_FN(
|
||||
"wait_for_connect. Still waiting. {}; timeout: {}; rem: {}",
|
||||
snapshot(), timeout, rem);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
INFO("Websocket io thread begin. {}", snapshot());
|
||||
try {
|
||||
client.run();
|
||||
} catch (const websocketpp::exception &ex) {
|
||||
ERROR_FN(
|
||||
"io_thread. Websocket io threw websocketpp::exception. {}; "
|
||||
"err: {}",
|
||||
snapshot(), ex);
|
||||
} catch (const std::exception &ex) {
|
||||
ERROR_FN(
|
||||
"io_thread. Websocket io threw std::exception. {}; err: {}",
|
||||
snapshot(), ex);
|
||||
} catch (...) {
|
||||
bts_assert_msgf(
|
||||
false,
|
||||
"io_thread. Websocket io threw unknown exception. Could "
|
||||
"this have been thrown by a callback delegate? {}",
|
||||
snapshot());
|
||||
}
|
||||
INFO("Websocket 'run' thread end. {}", snapshot());
|
||||
}
|
||||
|
||||
private:
|
||||
static std::atomic<int> client_id_counter;
|
||||
|
||||
auto on_tls_init(ConnectionHdl /*hdl*/) {
|
||||
INFO_FN("on_tls_init. state: {}; version: {}", state, version);
|
||||
auto result =
|
||||
websocketpp::lib::make_shared<SslContext>(SslContext::sslv23_client);
|
||||
result->set_verify_mode(boost::asio::ssl::verify_none);
|
||||
return result;
|
||||
}
|
||||
|
||||
void init_handlers() {
|
||||
INFO_FN("init_handlers. state: {}; version: {}", state, version);
|
||||
bts_assert(!io_thread);
|
||||
client.init_asio();
|
||||
client.set_message_handler(
|
||||
[this](auto hdl, auto msg) { on_message(hdl, msg); });
|
||||
client.set_open_handler([this](auto hdl) { on_open(hdl); });
|
||||
client.set_ping_handler([](auto /*hdl*/, auto /*msg*/) {
|
||||
LOG(Http, Info, "Received ping");
|
||||
return true;
|
||||
});
|
||||
client.set_pong_timeout(0);
|
||||
client.set_pong_handler([](auto /*hdl*/, auto /*msg*/) {
|
||||
LOG(Http, Info, "Received pong");
|
||||
return true;
|
||||
});
|
||||
client.set_tls_init_handler(
|
||||
[this](auto hdl) { return on_tls_init(hdl); });
|
||||
client.set_close_handler([this](auto hdl) { on_close(hdl); });
|
||||
client.set_fail_handler([this](auto hdl) {
|
||||
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
|
||||
ERROR_FN("fail_handler. {}; err: {}", snapshot(), ec.message());
|
||||
});
|
||||
}
|
||||
|
||||
void init_logging() {
|
||||
INFO_FN("init_logging. {}", snapshot());
|
||||
client.set_access_channels(websocketpp::log::alevel::all);
|
||||
// client.clear_access_channels(websocketpp::log::alevel::frame_payload);
|
||||
client.set_error_channels(websocketpp::log::elevel::all);
|
||||
}
|
||||
|
||||
void on_open(ConnectionHdl hdl) {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
INFO_FN("on_open. {}", snapshot());
|
||||
const auto ec = client.get_con_from_hdl(hdl)->get_ec();
|
||||
if (ec) {
|
||||
ERROR_FN("on_open. {}; err: {}", snapshot(), ec.message());
|
||||
}
|
||||
switch (state.value) {
|
||||
case State::OPENING:
|
||||
break;
|
||||
default:
|
||||
WARN_FN("on_open. Unexpected state. {}", snapshot());
|
||||
return;
|
||||
}
|
||||
set_state(State::OPEN);
|
||||
if (open_handler) {
|
||||
(*open_handler)(ec);
|
||||
}
|
||||
}
|
||||
|
||||
void on_message(ConnectionHdl /*hdl*/, MessageTypePtr msg) {
|
||||
DIAG("on_message. {}", snapshot());
|
||||
const auto &payload = msg->get_payload();
|
||||
if (message_handler) {
|
||||
(*message_handler)(payload);
|
||||
return;
|
||||
}
|
||||
ERROR_FN(
|
||||
"on_message. Message handler is null. {}; payload: \n{}", snapshot(),
|
||||
payload);
|
||||
}
|
||||
|
||||
void on_close(ConnectionHdl hdl) {
|
||||
lock_guard<mutex> lock{instance_mx};
|
||||
INFO_FN("on_close. {}", snapshot());
|
||||
switch (state.value) {
|
||||
case State::CLOSING:
|
||||
break;
|
||||
default:
|
||||
WARN_FN("on_close. Unexpected state. {}", snapshot());
|
||||
break;
|
||||
}
|
||||
set_state(State::IDLE);
|
||||
const auto con = client.get_con_from_hdl(hdl);
|
||||
const auto ec = con->get_ec();
|
||||
if (ec) {
|
||||
ERROR_FN(
|
||||
"on_close. {}; local_code: {}; "
|
||||
"local_reason: {}; remote_code: {}; remote_reason: {}; err: {}",
|
||||
snapshot(), con->get_local_close_code(),
|
||||
con->get_local_close_reason(), con->get_remote_close_code(),
|
||||
con->get_remote_close_reason(), ec.message());
|
||||
}
|
||||
if (close_handler) {
|
||||
(*close_handler)(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebsocketClient::WebsocketClient(WebsocketClientConfig config)
|
||||
: me_(std::make_unique<PImpl>(std::move(config))) {}
|
||||
|
||||
WebsocketClient::~WebsocketClient() {
|
||||
me_->close(websocketpp::close::status::going_away, "");
|
||||
}
|
||||
|
||||
std::atomic<int> WebsocketClient::PImpl::client_id_counter = 0;
|
||||
|
||||
WebsocketClient::CloseHandler &WebsocketClient::open_handler() {
|
||||
if (!me_->open_handler) {
|
||||
me_->open_handler.emplace();
|
||||
}
|
||||
return *me_->open_handler;
|
||||
}
|
||||
|
||||
WebsocketClient::MessageHandler &WebsocketClient::message_handler() {
|
||||
if (!me_->message_handler) {
|
||||
me_->message_handler.emplace();
|
||||
}
|
||||
return *me_->message_handler;
|
||||
}
|
||||
|
||||
WebsocketClient::CloseHandler &WebsocketClient::close_handler() {
|
||||
if (!me_->close_handler) {
|
||||
me_->close_handler.emplace();
|
||||
}
|
||||
return *me_->close_handler;
|
||||
}
|
||||
|
||||
void WebsocketClient::connect(
|
||||
const std::map<std::string, std::string> &headers, bool run_in_background) {
|
||||
me_->connect(headers, run_in_background);
|
||||
}
|
||||
|
||||
void WebsocketClient::run() { me_->run(); }
|
||||
|
||||
websocketpp::lib::error_code WebsocketClient::send_text(const string &text) {
|
||||
return me_->send_text(text);
|
||||
}
|
||||
|
||||
void WebsocketClient::disconnect() {
|
||||
me_->close(
|
||||
websocketpp::close::status::going_away,
|
||||
"WebsocketClient::disconnect was called");
|
||||
}
|
||||
|
||||
bool WebsocketClient::is_connected() const {
|
||||
lock_guard<mutex> lock{me_->instance_mx};
|
||||
return me_->state == State::OPEN;
|
||||
}
|
||||
|
||||
void WebsocketClient::ping(const string &payload) { me_->con->ping(payload); }
|
||||
|
||||
} // namespace bts::websocket
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/io/websocket/websocket_client_config.h"
|
||||
#include "lib/utils/Delegate.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <websocketpp/config/core_client.hpp>
|
||||
#include <websocketpp/message_buffer/message.hpp>
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
class WebsocketClient {
|
||||
public:
|
||||
explicit WebsocketClient(WebsocketClientConfig config);
|
||||
~WebsocketClient();
|
||||
|
||||
using OpenHandler = bts::core::Delegate<void(const std::error_code &)>;
|
||||
using MessageHandler = bts::core::Delegate<void(const std::string &)>;
|
||||
using CloseHandler = bts::core::Delegate<void(const std::error_code &)>;
|
||||
|
||||
void connect(
|
||||
const std::map<std::string, std::string> &headers = {},
|
||||
bool run_in_background = true);
|
||||
void disconnect();
|
||||
bool is_connected() const;
|
||||
void run();
|
||||
CloseHandler &open_handler();
|
||||
MessageHandler &message_handler();
|
||||
CloseHandler &close_handler();
|
||||
websocketpp::lib::error_code send_text(const std::string &text);
|
||||
void ping(const std::string &payload = "");
|
||||
|
||||
private:
|
||||
struct PImpl;
|
||||
std::unique_ptr<PImpl> me_;
|
||||
};
|
||||
|
||||
} // namespace bts::websocket
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/TimeSpan.h"
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
struct WebsocketClientConfig {
|
||||
std::string url;
|
||||
core::TimeSpan connect_timeout;
|
||||
core::TimeSpan pong_timeout;
|
||||
};
|
||||
|
||||
} // namespace bts::websocket
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/EnumMacros.h"
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
// clang-format off
|
||||
#define FOR_ALL_VALUES(op) \
|
||||
op(WebsocketClientState, IDLE) \
|
||||
op(WebsocketClientState, OPENING) \
|
||||
op(WebsocketClientState, OPEN) \
|
||||
op(WebsocketClientState, WAITING_TO_CLOSE) \
|
||||
op(WebsocketClientState, CLOSING)
|
||||
// clang-format on
|
||||
|
||||
ENUM_CLASS_DEF(WebsocketClientState, FOR_ALL_VALUES);
|
||||
|
||||
#undef FOR_ALL_VALUES
|
||||
|
||||
} // namespace bts::websocket
|
||||
|
||||
FMT_IMPL(bts::websocket::WebsocketClientState, s, "{}", s.name());
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
#include "lib/utils/ValueWrapper.h"
|
||||
|
||||
#include <websocketpp/close.hpp>
|
||||
|
||||
namespace bts::websocket {
|
||||
|
||||
/**
|
||||
* Simple wrapper to provide a means implement a custom formatter for
|
||||
* `websocketpp::close::status::value` (an alias for a primitive numeric type)
|
||||
*/
|
||||
using WebsocketCloseStatus = utils::ValueWrapper<
|
||||
struct WebsocketCloseStatusTag,
|
||||
websocketpp::close::status::value>;
|
||||
|
||||
} // namespace bts::websocket
|
||||
|
||||
FMT_IMPL(
|
||||
bts::websocket::WebsocketCloseStatus,
|
||||
s,
|
||||
"{}",
|
||||
websocketpp::close::status::get_string(*s));
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <websocketpp/common/system_error.hpp>
|
||||
|
||||
FMT_IMPL(websocketpp::lib::error_code, ec, "{}", ec.message());
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/core/FMT_IMPL.h"
|
||||
|
||||
#include <websocketpp/error.hpp>
|
||||
|
||||
FMT_IMPL(websocketpp::exception, e, "{}", std::string{e.what()});
|
||||
@@ -78,12 +78,20 @@ class IntPrice {
|
||||
_exponent = temp._exponent;
|
||||
return *this;
|
||||
}
|
||||
IntPrice &operator+=(double o) {
|
||||
_mantissa += o * pow10(-_exponent);
|
||||
return *this;
|
||||
}
|
||||
IntPrice &operator-=(const IntPrice &o) {
|
||||
auto temp = *this - o;
|
||||
_mantissa = temp._mantissa;
|
||||
_exponent = temp._exponent;
|
||||
return *this;
|
||||
}
|
||||
IntPrice &operator-=(double o) {
|
||||
_mantissa -= o * pow10(-_exponent);
|
||||
return *this;
|
||||
}
|
||||
IntPrice operator-() { return IntPrice(-_mantissa, _exponent); }
|
||||
IntPrice operator*(const IntPrice &other) const {
|
||||
return op(other, [](int64_t m1, int64_t m2) { return m1 * m2; });
|
||||
@@ -91,6 +99,9 @@ class IntPrice {
|
||||
IntPrice operator*(const int &other) const {
|
||||
return IntPrice(_mantissa * other, _exponent);
|
||||
}
|
||||
IntPrice operator*(double v) const {
|
||||
return IntPrice((int64_t)(_mantissa * v), _exponent);
|
||||
}
|
||||
IntPrice &operator*=(const int &other) {
|
||||
_mantissa *= other;
|
||||
return *this;
|
||||
|
||||
+17
-12
@@ -439,6 +439,7 @@ class SyntheticConsumerInst {
|
||||
void add_security(const api::Security &sec) {
|
||||
Mutex::scoped_lock lock(_workmutex);
|
||||
if (_data.find(sec.id()) == _data.end()) {
|
||||
LOG(md, Info, "MDConsumer::add_security. {}", sec);
|
||||
auto ptr = to_strategy(make_shared<api::Security>(sec));
|
||||
vector<IncompleteStrategy> vec;
|
||||
vec.push_back(ptr);
|
||||
@@ -503,19 +504,23 @@ class SyntheticConsumerInst {
|
||||
if (iter != real_handles.end()) {
|
||||
// real handle
|
||||
legs.push_back(iter->second);
|
||||
} else {
|
||||
auto iter2 = existing.find(leg_key);
|
||||
if (iter2 != existing.end()) {
|
||||
// existing synthetic handle
|
||||
legs.push_back(MDConsumerHandle(iter2->second));
|
||||
} else {
|
||||
auto iter3 = result.find(leg_key);
|
||||
if (iter3 != result.end()) {
|
||||
// new synthetic handle?
|
||||
legs.push_back(MDConsumerHandle(iter3->second));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto iter2 = existing.find(leg_key);
|
||||
if (iter2 != existing.end()) {
|
||||
// existing synthetic handle
|
||||
legs.push_back(MDConsumerHandle(iter2->second));
|
||||
continue;
|
||||
}
|
||||
auto iter3 = result.find(leg_key);
|
||||
if (iter3 != result.end()) {
|
||||
// new synthetic handle?
|
||||
legs.push_back(MDConsumerHandle(iter3->second));
|
||||
continue;
|
||||
}
|
||||
LOG(md, Warn,
|
||||
"MDConsumer::get_synthetic_handles. Unresolved leg. id: {}",
|
||||
leg_key);
|
||||
}
|
||||
// If we found all the legs
|
||||
if (legs.size() ==
|
||||
|
||||
@@ -124,7 +124,6 @@ struct DepthTraversal {
|
||||
levels[x].size = depth[x].size();
|
||||
}
|
||||
}
|
||||
#ifdef PRECISE_SYNTHETICS
|
||||
double available_size() const {
|
||||
double available = 0.0;
|
||||
auto cur = head;
|
||||
@@ -160,38 +159,6 @@ struct DepthTraversal {
|
||||
}
|
||||
return price / size;
|
||||
}
|
||||
#else
|
||||
double available_size() const {
|
||||
double available = 0.0;
|
||||
auto cur = head;
|
||||
while (cur < levels.size() && available < 1) {
|
||||
available += levels[cur].size / qty;
|
||||
++cur;
|
||||
}
|
||||
return available;
|
||||
}
|
||||
double consume(double size) {
|
||||
bts_assert_d(size <= available_size());
|
||||
double take = size * qty;
|
||||
double price = 0.0;
|
||||
while (take > 0) {
|
||||
if (levels[head].size >= take) {
|
||||
price = levels[head].price * price_mult;
|
||||
levels[head].size -= take;
|
||||
if (levels[head].size <= 0)
|
||||
++head;
|
||||
break;
|
||||
} else {
|
||||
double available = levels[head].size;
|
||||
price = levels[head].price * price_mult;
|
||||
levels[head].size = 0;
|
||||
++head;
|
||||
take -= available;
|
||||
}
|
||||
}
|
||||
return price;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
void copy_synthetic_side(
|
||||
|
||||
+196
-52
@@ -1,7 +1,10 @@
|
||||
#include "SecurityCache.h"
|
||||
#include "SecurityDB.h"
|
||||
#include "lib/domain_model/Security.h"
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/security_type_index.h"
|
||||
#include "lib/md/venue_index.h"
|
||||
#include "lib/utils/Dispatcher.h"
|
||||
#include "lib/utils/LockBox.h"
|
||||
|
||||
@@ -13,9 +16,152 @@
|
||||
namespace bts {
|
||||
namespace md {
|
||||
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
|
||||
struct SecurityCacheIndex {
|
||||
btree::map<SecurityKey, SecurityPtr> cache;
|
||||
btree::map<ExchangeSecurityKey, SecurityPtr> ex_cache;
|
||||
VenueIndex<SecurityTypeIndex<unordered_map<string, uint32_t>>>
|
||||
venue_type_ids;
|
||||
|
||||
const bts::api::Security *get_security(
|
||||
api::Venue venue, const std::string &desc) {
|
||||
auto id = get_id_(venue, desc);
|
||||
auto sec_it = cache.find(SecurityKey{id});
|
||||
if (sec_it == cache.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return sec_it->second.get();
|
||||
}
|
||||
|
||||
const bts::api::Security *get_security(
|
||||
api::Venue venue, api::SecurityType type, const std::string &desc) {
|
||||
auto id = get_id_(venue, type, desc);
|
||||
auto sec_it = cache.find(SecurityKey{id});
|
||||
if (sec_it == cache.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return sec_it->second.get();
|
||||
}
|
||||
|
||||
const Security *try_get_security(SecurityKey key) const {
|
||||
auto iter = cache.find(key);
|
||||
if (iter != cache.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return (const api::Security *)nullptr;
|
||||
}
|
||||
|
||||
const Security *try_get_security(ExchangeSecurityKey key) const {
|
||||
auto iter = ex_cache.find(key);
|
||||
if (iter != ex_cache.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return (const api::Security *)nullptr;
|
||||
}
|
||||
|
||||
std::vector<const Security *> get_securities(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name) {
|
||||
std::vector<api::Security const *> result;
|
||||
const bool all_types = type == api::SECURITYTYPE_UNSET;
|
||||
const bool all_venues = venue == api::VENUE_UNSET;
|
||||
const bool all_names = name.empty();
|
||||
for (auto const &p : cache) {
|
||||
const auto &s = *p.second;
|
||||
if ((all_types ||
|
||||
type == securities::security_type(s.Contract_case())) &&
|
||||
(all_venues || venue == s.exchange()) &&
|
||||
(all_names || name == securities::descriptor(s))) {
|
||||
result.push_back(p.second.get());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<const Security *> get_securities(
|
||||
std::function<bool(const Security &)> include) {
|
||||
std::vector<api::Security const *> result;
|
||||
for (auto const &p : cache) {
|
||||
auto &s = *p.second;
|
||||
if (include(s)) {
|
||||
result.push_back(&s);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool add_live_security(SecurityPtr security) {
|
||||
bts_assert(security);
|
||||
if (security->has_glbx()) {
|
||||
ExchangeSecurityKey key(security->id());
|
||||
ex_cache.insert(std::make_pair(key, security));
|
||||
}
|
||||
SecurityKey key(security->id());
|
||||
// Insert but don't overwrite existing
|
||||
auto result =
|
||||
cache.insert(std::make_pair(SecurityKey(security->id()), security));
|
||||
return result.second;
|
||||
}
|
||||
|
||||
const Security *add_db_security(SecurityPtr security) {
|
||||
bts_assert(security);
|
||||
if (security->has_glbx()) {
|
||||
ExchangeSecurityKey key(security->id());
|
||||
ex_cache.insert(std::make_pair(key, security));
|
||||
}
|
||||
SecurityKey key(security->id());
|
||||
auto it = cache.insert(std::make_pair(key, security));
|
||||
const Security *active_sec = it.first->second.get();
|
||||
// If the security was just added, active_sec is equal to security.
|
||||
// If it was already present, it must be a recently inserted 'live'
|
||||
// security and we should use it (instead of the db security).
|
||||
venue_type_ids.get_or_add(security->exchange())
|
||||
.get_or_add(bts::securities::security_type(*active_sec))
|
||||
.try_emplace(
|
||||
bts::securities::descriptor(*active_sec), active_sec->id());
|
||||
return active_sec;
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint32_t get_id_(api::Venue venue, const std::string &desc) {
|
||||
auto id = get_id_(venue, api::SECURITYTYPE_SPOT, desc);
|
||||
if (id != 0) {
|
||||
return id;
|
||||
}
|
||||
id = get_id_(venue, api::SECURITYTYPE_FUTURE, desc);
|
||||
if (id != 0) {
|
||||
return id;
|
||||
}
|
||||
id = get_id_(venue, api::SECURITYTYPE_OPTION, desc);
|
||||
if (id != 0) {
|
||||
return id;
|
||||
}
|
||||
id = get_id_(venue, api::SECURITYTYPE_SPREAD, desc);
|
||||
if (id != 0) {
|
||||
return id;
|
||||
}
|
||||
id = get_id_(venue, api::SECURITYTYPE_SYNTHETIC, desc);
|
||||
if (id != 0) {
|
||||
return id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint32_t get_id_(
|
||||
api::Venue venue, api::SecurityType type, const std::string &desc) {
|
||||
auto &type_store = venue_type_ids.get_or_add(venue);
|
||||
auto &ids = type_store.get_or_add(type);
|
||||
auto it = ids.find(desc);
|
||||
if (it != ids.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct SecurityCacheImpl {
|
||||
utils::LockBox<btree::map<SecurityKey, SecurityPtr>> cache;
|
||||
utils::LockBox<btree::map<ExchangeSecurityKey, SecurityPtr>> ex_cache;
|
||||
utils::LockBox<SecurityCacheIndex> cache;
|
||||
tbb::concurrent_queue<SecurityPtr> update_queue;
|
||||
void enqueue_update_db(const SecurityPtr &);
|
||||
void flush();
|
||||
@@ -52,23 +198,13 @@ SecurityCache::SecurityCache() : _me(std::make_shared<SecurityCacheImpl>()) {}
|
||||
SecurityCache::~SecurityCache() {}
|
||||
|
||||
const Security *SecurityCache::try_get_security(SecurityKey key) const {
|
||||
return _me->cache.lock([key](btree::map<SecurityKey, SecurityPtr> &t) {
|
||||
auto iter = t.find(key);
|
||||
if (iter != t.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return (const api::Security *)nullptr;
|
||||
});
|
||||
return _me->cache.lock(
|
||||
[key](SecurityCacheIndex &t) { return t.try_get_security(key); });
|
||||
}
|
||||
|
||||
const Security *SecurityCache::try_get_security(ExchangeSecurityKey key) const {
|
||||
return _me->ex_cache.lock(
|
||||
[key](btree::map<ExchangeSecurityKey, SecurityPtr> &t) {
|
||||
auto iter = t.find(key);
|
||||
if (iter != t.end()) {
|
||||
return iter->second.get();
|
||||
}
|
||||
return (const api::Security *)nullptr;
|
||||
});
|
||||
return _me->cache.lock(
|
||||
[key](SecurityCacheIndex &t) { return t.try_get_security(key); });
|
||||
}
|
||||
|
||||
const Security *SecurityCache::get_security(SecurityKey key) {
|
||||
@@ -79,62 +215,70 @@ const Security *SecurityCache::get_security(SecurityKey key) {
|
||||
if (security) {
|
||||
LOG(md, Info, "Loaded security from database: {} {}",
|
||||
security->symbol(), security->id());
|
||||
return add_db_security(security);
|
||||
return _me->cache.lock([&security](SecurityCacheIndex &cache) {
|
||||
return cache.add_db_security(security);
|
||||
});
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const Security *SecurityCache::get_security(
|
||||
api::Venue venue, const std::string &name) {
|
||||
return _me->cache.lock([venue, name](SecurityCacheIndex &cache) {
|
||||
return cache.get_security(venue, name);
|
||||
});
|
||||
}
|
||||
|
||||
const Security *SecurityCache::get_security(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name) {
|
||||
return _me->cache.lock([venue, type, name](SecurityCacheIndex &cache) {
|
||||
return cache.get_security(venue, type, name);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<const Security *> SecurityCache::get_securities(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name) {
|
||||
return _me->cache.lock([venue, type, &name](SecurityCacheIndex &cache) {
|
||||
return cache.get_securities(venue, type, name);
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<const Security *> SecurityCache::get_securities(
|
||||
std::function<bool(const Security &)> include) {
|
||||
std::vector<api::Security const *> result;
|
||||
_me->cache.lock([&result, include](SecurityCacheIndex &cache) {
|
||||
for (auto const &p : cache.cache) {
|
||||
auto &s = *p.second;
|
||||
if (include(s)) {
|
||||
result.push_back(&s);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<api::Security const *> SecurityCache::get_securities() {
|
||||
std::vector<api::Security const *> result;
|
||||
result.reserve(100000);
|
||||
|
||||
_me->cache.lock([&result](auto &cache) {
|
||||
for (auto const &p : cache) {
|
||||
_me->cache.lock([&result](SecurityCacheIndex &cache) {
|
||||
for (const auto &p : cache.cache) {
|
||||
result.push_back(p.second.get());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const Security *SecurityCache::add_db_security(const SecurityPtr &security) {
|
||||
bts_assert(security);
|
||||
LOG(md, Info, "SecurityCache::add_db_security security: {}",
|
||||
bts::securities::descriptor(*security));
|
||||
if (security->has_glbx()) {
|
||||
_me->ex_cache.lock([&security](auto &cache) {
|
||||
ExchangeSecurityKey key(security->id());
|
||||
cache.insert(std::make_pair(key, security));
|
||||
});
|
||||
}
|
||||
return _me->cache.lock([&security](auto &cache) {
|
||||
SecurityKey key(security->id());
|
||||
auto result = cache.insert(std::make_pair(key, security));
|
||||
// If result was inserted result.first->second is equal to security
|
||||
// If the value was already present it must be a recently inserted 'live'
|
||||
// security and we should use it
|
||||
return result.first->second.get();
|
||||
});
|
||||
}
|
||||
|
||||
bool SecurityCache::add_live_security(const SecurityPtr &security) {
|
||||
bts_assert(security);
|
||||
LOG(md, Diag, "SecurityCache::add_live_security security: {}",
|
||||
bts::securities::descriptor(*security));
|
||||
if (security->has_glbx()) {
|
||||
_me->ex_cache.lock([&security](auto &cache) {
|
||||
ExchangeSecurityKey key(security->id());
|
||||
cache.insert(std::make_pair(key, security));
|
||||
});
|
||||
}
|
||||
return _me->cache.lock([&](auto &cache) {
|
||||
SecurityKey key(security->id());
|
||||
// Insert but don't overwrite existing
|
||||
auto result =
|
||||
cache.insert(std::make_pair(SecurityKey(security->id()), security));
|
||||
return _me->cache.lock([&](SecurityCacheIndex &cache) {
|
||||
const bool is_new_sec = cache.add_live_security(security);
|
||||
_me->enqueue_update_db(security);
|
||||
return result.second;
|
||||
return is_new_sec;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,32 @@ class SecurityCache {
|
||||
// Load security from cache with fallback to database
|
||||
const Security *get_security(SecurityKey key);
|
||||
|
||||
/**
|
||||
* Finds the first security with matching @p venue and @p name. Params that are
|
||||
* empty/unset are ignored.
|
||||
*/
|
||||
const Security *get_security(api::Venue venue, const std::string &name);
|
||||
|
||||
/**
|
||||
* Finds the first security with matching @p venue @p type and @p name. Params that
|
||||
* are empty/unset are ignored.
|
||||
*/
|
||||
const Security *get_security(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name);
|
||||
|
||||
/**
|
||||
* Finds all securities with matching @p venue @p type and @p name. Params that
|
||||
* are empty/unset are ignored.
|
||||
*/
|
||||
std::vector<const Security *> get_securities(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name);
|
||||
|
||||
/**
|
||||
* Finds all securities where @p include returns true.
|
||||
*/
|
||||
std::vector<const Security *> get_securities(
|
||||
std::function<bool(const Security &)> include);
|
||||
|
||||
// Load all securities from cache, no fallback to database
|
||||
std::vector<api::Security const *> get_securities();
|
||||
|
||||
@@ -33,7 +59,6 @@ class SecurityCache {
|
||||
|
||||
// returns true if the cache was updated
|
||||
bool add_live_security(const SecurityPtr &security);
|
||||
const Security *add_db_security(const SecurityPtr &security);
|
||||
|
||||
void flush_to_disk();
|
||||
};
|
||||
|
||||
+13
-14
@@ -53,12 +53,10 @@ std::unique_ptr<SQLite::Database> open_db() {
|
||||
return db;
|
||||
}
|
||||
|
||||
datetime::Date sec_expiration_cut_off() {
|
||||
boost::gregorian::date_duration max_exp_age(
|
||||
config::get<SecurityExpirationCutoff>().max_days_after_expiration());
|
||||
|
||||
auto pt = (datetime::now() - max_exp_age);
|
||||
return datetime::Date(pt.date().year(), pt.date().month(), pt.date().day());
|
||||
core::TimeStamp sec_expiration_cut_off() {
|
||||
return core::TimeStamp::now().date() -
|
||||
core::TimeSpan::from_days(config::get<SecurityExpirationCutoff>()
|
||||
.max_days_after_expiration());
|
||||
}
|
||||
|
||||
size_t load_securities(
|
||||
@@ -72,7 +70,8 @@ size_t load_securities(
|
||||
SQLite::Statement select_stmt(
|
||||
*db, "select key,symbol,venue,type,security_blob from securities where "
|
||||
"expiration > :exp");
|
||||
select_stmt.bind(":exp", sec_expiration_cut_off().to_short());
|
||||
select_stmt.bind(
|
||||
":exp", (long long)(sec_expiration_cut_off().to_date_int()));
|
||||
|
||||
Security security;
|
||||
while (select_stmt.tryExecuteStep() == SQLITE_ROW) {
|
||||
@@ -156,14 +155,14 @@ Result insert_or_replace(const std::vector<SecurityPtr> &securities) {
|
||||
insert.bind(3, psecurity->exchange());
|
||||
insert.bind(4, securitytype_of(*psecurity));
|
||||
insert.bind(5, buffer);
|
||||
auto greg_date = securities::expiration(*psecurity);
|
||||
if (greg_date == boost::gregorian::date()) {
|
||||
insert.bind(6, datetime::Date(9999, 1, 1).to_short());
|
||||
auto exp = securities::expiration(*psecurity);
|
||||
if (!exp) {
|
||||
const long long exp_int =
|
||||
std::numeric_limits<std::int64_t>::max();
|
||||
insert.bind(6, exp_int);
|
||||
} else {
|
||||
ushort year = greg_date.year();
|
||||
ushort month = greg_date.month();
|
||||
ushort day = greg_date.day();
|
||||
insert.bind(6, datetime::Date(year, month, day).to_short());
|
||||
const long long exp_int = exp->to_date_int();
|
||||
insert.bind(6, exp_int);
|
||||
}
|
||||
insert.exec();
|
||||
insert.reset();
|
||||
|
||||
@@ -22,8 +22,6 @@ class Security;
|
||||
namespace md {
|
||||
namespace db {
|
||||
|
||||
struct SecurityDB;
|
||||
|
||||
// Loads securities matching the given filter for producers to pre-populate
|
||||
// security lists note: possible to use this to optimize initial memory layout?
|
||||
// sort by expiration?
|
||||
|
||||
@@ -55,7 +55,9 @@ static persistence::StoreRow get_row(
|
||||
|
||||
static persistence::StoreRow get_row(
|
||||
const api::Security &s, const std::string &prefix = "") {
|
||||
return get_row(s.exchange(), bts::securities::security_type(s), prefix);
|
||||
return get_row(
|
||||
s.exchange(), bts::securities::security_type(s),
|
||||
securities::descriptor(s), prefix);
|
||||
}
|
||||
|
||||
struct SecurityIdRequest {
|
||||
|
||||
@@ -17,10 +17,30 @@ const api::Security *SecurityOracle::get_security(
|
||||
return _cache.try_get_security(key);
|
||||
}
|
||||
|
||||
const api::Security *SecurityOracle::get_security(
|
||||
api::Venue venue, const std::string &name) const {
|
||||
return _cache.get_security(venue, name);
|
||||
}
|
||||
|
||||
const api::Security *SecurityOracle::get_security(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name) const {
|
||||
return _cache.get_security(venue, type, name);
|
||||
}
|
||||
|
||||
std::vector<api::Security const *> SecurityOracle::get_securities() const {
|
||||
return _cache.get_securities();
|
||||
}
|
||||
|
||||
std::vector<api::Security const *> SecurityOracle::get_securities(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name) const {
|
||||
return _cache.get_securities(venue, type, name);
|
||||
}
|
||||
|
||||
std::vector<api::Security const *> SecurityOracle::get_securities(
|
||||
std::function<bool(const api::Security &)> include) const {
|
||||
return _cache.get_securities(include);
|
||||
}
|
||||
|
||||
void SecurityOracle::troubleshoot_log_all_securities() const {
|
||||
size_t sec_no = 0;
|
||||
for (const auto s : get_securities()) {
|
||||
|
||||
@@ -20,6 +20,16 @@ class SecurityOracle {
|
||||
~SecurityOracle();
|
||||
const api::Security *get_security(SecurityKey key) const;
|
||||
const api::Security *get_security(ExchangeSecurityKey key) const;
|
||||
const api::Security *get_security(api::Venue venue, const std::string &name) const;
|
||||
const api::Security *get_security(api::Venue venue, api::SecurityType type, const std::string &name) const;
|
||||
/**
|
||||
* Finds all securities where @p include returns true.
|
||||
*/
|
||||
std::vector<const Security *> get_securities(
|
||||
std::function<bool(const Security &)> include) const;
|
||||
std::vector<api::Security const *> get_securities(
|
||||
api::Venue venue, api::SecurityType type, const std::string &name) const;
|
||||
|
||||
std::vector<api::Security const *> get_securities() const;
|
||||
// this iterates through all securities and simply logs them. Use it
|
||||
// ONLY for troubleshooting
|
||||
|
||||
@@ -129,6 +129,11 @@ void convert_security(const FixedSecurity &source, Security *presult) {
|
||||
dest.mutable_binance()->set_security_desc(source.security_desc());
|
||||
dest.mutable_binance()->set_exchange_id(source.numeric_id);
|
||||
break;
|
||||
case Venue::VENUE_BINANCE_COIN_FUTURES:
|
||||
dest.mutable_binance_coin_futures()->set_security_desc(
|
||||
source.security_desc());
|
||||
dest.mutable_binance_coin_futures()->set_exchange_id(source.numeric_id);
|
||||
break;
|
||||
case Venue::VENUE_BINANCE_US:
|
||||
dest.mutable_binance_us()->set_security_desc(source.security_desc());
|
||||
dest.mutable_binance_us()->set_exchange_id(source.numeric_id);
|
||||
@@ -248,6 +253,8 @@ uint64_t exchange_id(const Security &security) {
|
||||
return security.ftx().exchange_id();
|
||||
case Security::ExchangeDataCase::kBinance:
|
||||
return security.binance().exchange_id();
|
||||
case Security::ExchangeDataCase::kBinanceCoinFutures:
|
||||
return security.binance_coin_futures().exchange_id();
|
||||
case Security::ExchangeDataCase::kBinanceUs:
|
||||
return security.binance_us().exchange_id();
|
||||
case Security::ExchangeDataCase::kCoinbase:
|
||||
@@ -324,6 +331,10 @@ void convert_security(
|
||||
dest.numeric_id = src.binance().exchange_id();
|
||||
dest.security_desc(src.binance().security_desc());
|
||||
break;
|
||||
case Security::ExchangeDataCase::kBinanceCoinFutures:
|
||||
dest.numeric_id = src.binance_coin_futures().exchange_id();
|
||||
dest.security_desc(src.binance_coin_futures().security_desc());
|
||||
break;
|
||||
case Security::ExchangeDataCase::kBinanceUs:
|
||||
dest.numeric_id = src.binance_us().exchange_id();
|
||||
dest.security_desc(src.binance_us().security_desc());
|
||||
|
||||
@@ -59,6 +59,10 @@ constexpr bool SymbolLoader::venue_has_synths(Venue venue) {
|
||||
case VENUE(SIMICE):
|
||||
case VENUE(ICE):
|
||||
case VENUE(GLBX):
|
||||
case VENUE(BINANCE):
|
||||
case VENUE(BINANCE_USDS_FUTURES):
|
||||
case VENUE(DERIBIT):
|
||||
case VENUE(OKX):
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -133,6 +133,9 @@ std::string to_str(const std::vector<T> &vec) {
|
||||
}
|
||||
const vector<CMEChannel> parse_channels(const vector<UserSymbol> &symbols) {
|
||||
LOG(md, Info, "parse_channels symbols: {}", FMT_JOIN(symbols, ", "));
|
||||
if (symbols.empty()) {
|
||||
return {};
|
||||
}
|
||||
await_cme_config();
|
||||
vector<CMEChannel> result;
|
||||
bool skip_options = config::get<PublisherOptions>().skip_options();
|
||||
|
||||
@@ -179,6 +179,9 @@ struct MDConsumerGroup {
|
||||
case api::VENUE_BINANCE:
|
||||
this->mic = "BINANCE";
|
||||
break;
|
||||
case api::VENUE_BINANCE_COIN_FUTURES:
|
||||
this->mic = "BINANCE_COIN_FUTURES";
|
||||
break;
|
||||
case api::VENUE_BINANCE_US:
|
||||
this->mic = "BINANCE_US";
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
template <class T>
|
||||
class SecurityTypeIndex {
|
||||
std::optional<T> spot;
|
||||
std::optional<T> future;
|
||||
std::optional<T> option;
|
||||
std::optional<T> spread;
|
||||
std::optional<T> synthetic;
|
||||
|
||||
public:
|
||||
std::optional<T> &get(api::SecurityType type) { return get_(*this, type); }
|
||||
|
||||
const std::optional<T> &get(api::SecurityType type) const {
|
||||
return get_(*this, type);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
T &get_or_add(api::SecurityType type, Args... args) {
|
||||
std::optional<T> &val = get_(*this, type);
|
||||
if (!val) {
|
||||
val.emplace(args...);
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename U, class... Args>
|
||||
static auto &get_(U &self, api::SecurityType type) {
|
||||
switch (type) {
|
||||
case api::SECURITYTYPE_SPOT:
|
||||
return self.spot;
|
||||
case api::SECURITYTYPE_OPTION:
|
||||
return self.option;
|
||||
case api::SECURITYTYPE_FUTURE:
|
||||
return self.future;
|
||||
case api::SECURITYTYPE_SPREAD:
|
||||
return self.spread;
|
||||
case api::SECURITYTYPE_SYNTHETIC:
|
||||
return self.synthetic;
|
||||
case api::SECURITYTYPE_UNSET:
|
||||
case api::SecurityType_INT_MIN_SENTINEL_DO_NOT_USE_:
|
||||
case api::SecurityType_INT_MAX_SENTINEL_DO_NOT_USE_:
|
||||
default:
|
||||
bts_assert_msgf(
|
||||
false, "SecurityType '{}' not present in VenueIndex",
|
||||
api::SecurityType_Name(type));
|
||||
static std::optional<T> default_rv;
|
||||
return default_rv;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bts::md
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "lib/domain_model/Security.pb.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace bts::md {
|
||||
|
||||
template <class T>
|
||||
class VenueIndex {
|
||||
std::optional<T> binance;
|
||||
std::optional<T> binance_coin_futures;
|
||||
std::optional<T> binance_us;
|
||||
std::optional<T> binance_usds_futures;
|
||||
std::optional<T> bts;
|
||||
std::optional<T> okx;
|
||||
std::optional<T> deribit;
|
||||
std::optional<T> cboe;
|
||||
std::optional<T> cboecfe;
|
||||
std::optional<T> glbx;
|
||||
std::optional<T> ice;
|
||||
std::optional<T> taifex;
|
||||
std::optional<T> coinbase;
|
||||
|
||||
public:
|
||||
std::optional<T> &get(api::Venue venue) { return get_(*this, venue); }
|
||||
|
||||
const std::optional<T> &get(api::Venue venue) const {
|
||||
return get_(*this, venue);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
T &get_or_add(api::Venue venue, Args... args) {
|
||||
std::optional<T> &val = get_(*this, venue);
|
||||
if (!val) {
|
||||
val.emplace(args...);
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename U, class... Args>
|
||||
static auto &get_(U &self, api::Venue venue) {
|
||||
switch (venue) {
|
||||
case api::VENUE_CBOE:
|
||||
return self.cboe;
|
||||
case api::VENUE_CBOECFE:
|
||||
return self.cboecfe;
|
||||
case api::VENUE_GLBX:
|
||||
return self.glbx;
|
||||
case api::VENUE_ICE:
|
||||
return self.ice;
|
||||
case api::VENUE_DERIBIT:
|
||||
return self.deribit;
|
||||
case api::VENUE_BINANCE:
|
||||
return self.binance;
|
||||
case api::VENUE_BINANCE_COIN_FUTURES:
|
||||
return self.binance_coin_futures;
|
||||
case api::VENUE_BINANCE_USDS_FUTURES:
|
||||
return self.binance_usds_futures;
|
||||
case api::VENUE_BINANCE_US:
|
||||
return self.binance_us;
|
||||
case api::VENUE_COINBASE:
|
||||
return self.coinbase;
|
||||
case api::VENUE_OKX:
|
||||
return self.okx;
|
||||
case api::VENUE_TAIFEX:
|
||||
return self.taifex;
|
||||
case api::VENUE_BTS:
|
||||
return self.bts;
|
||||
case api::VENUE_FTX:
|
||||
case api::VENUE_SIM:
|
||||
case api::VENUE_SIMICE:
|
||||
case api::VENUE_UNSET:
|
||||
case api::Venue_INT_MIN_SENTINEL_DO_NOT_USE_:
|
||||
case api::Venue_INT_MAX_SENTINEL_DO_NOT_USE_:
|
||||
default:
|
||||
bts_assert_msgf(
|
||||
false, "Venue '{}' not present in VenueIndex",
|
||||
api::Venue_Name(venue));
|
||||
static std::optional<T> default_rv;
|
||||
return default_rv;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bts::md
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/MDClient.h"
|
||||
#include "lib/md/MDConsumer.h"
|
||||
#include "lib/md/UserSymbol.h"
|
||||
#include "lib/mercury/actors/Timer.h"
|
||||
#include "lib/mercury/executor/Executor.h"
|
||||
#include "lib/metrics/metrics.h"
|
||||
@@ -24,16 +25,23 @@
|
||||
#include "lib/orderman/actors/SecuritySrc.h"
|
||||
#include "lib/orderman/actors/ServiceBridge.h"
|
||||
#include "lib/orderman/actors/order_manager/OrderManager.h"
|
||||
#include "lib/orderman/actors/order_manager/OrderServicesData.h"
|
||||
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
|
||||
#include "lib/orderman/events/ExchangePositionEvent.h"
|
||||
#include "lib/orderman/events/OrderOpResultEvent.h"
|
||||
#include "lib/orderman/gateways/execution_gateway_provider.h"
|
||||
#include "lib/orderman/gateways/i_execution_gateway_provider.h"
|
||||
#include "lib/orderman/order_factory/DynamicOrderRegistry.h"
|
||||
#include "lib/orderman/orderman_utils/ExchangeOpenOrders.h"
|
||||
#include "lib/orderman/orders/Order.h"
|
||||
#include "lib/os/spark_data_directory.h"
|
||||
#include "lib/plugin/SparkServices.h"
|
||||
#include "lib/plugin/plugin.h"
|
||||
#include "lib/utils/PBUtils.h"
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
#include "lib/utils/thread_manager.h"
|
||||
#include "lib/utils/type_name.h"
|
||||
#include "lib_binance_execution/binance_execution/execution_gateway_provider.h"
|
||||
|
||||
#include <bits/stdint-uintn.h>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
@@ -43,6 +51,8 @@
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
using std::unique_ptr;
|
||||
using std::vector;
|
||||
|
||||
struct ActorThread {
|
||||
ActorThread(
|
||||
@@ -126,10 +136,13 @@ struct OrderServiceServer::PImpl {
|
||||
const Exchange_open_ordersStatefulPublisher &open_orders_publisher,
|
||||
md::MDClient &mdc,
|
||||
DynamicOrderRegistry const &d,
|
||||
std::vector<std::unique_ptr<IExecutionGatewayProvider>>
|
||||
gateway_providers,
|
||||
metrics::MetricsContainer &stats,
|
||||
trace::PerfLog &log)
|
||||
: context(ctx), service_bridge(), service_ready(false), known_orders(),
|
||||
known_fills(), publish_orders(), publish_fills(),
|
||||
: context(ctx), service_bridge(),
|
||||
gateway_providers(std::move(gateway_providers)), service_ready(false),
|
||||
known_orders(), known_fills(), publish_orders(), publish_fills(),
|
||||
exchange_pos_publisher(exchange_pos_publisher),
|
||||
open_orders_publisher(open_orders_publisher),
|
||||
admin_service(ctx.locate<api::admin::AdminService>()),
|
||||
@@ -137,11 +150,15 @@ struct OrderServiceServer::PImpl {
|
||||
margin_service(ctx.locate<api::position::MarginService>()),
|
||||
md_consumer(mdc.current_consumer()), stats(stats),
|
||||
dyn_order_registry(d), log(log), in_flight_order_ops(),
|
||||
current_req_op_id(0) {}
|
||||
current_req_op_id(0) {
|
||||
LOG(OrderMan, Info, "OrderServiceServer ctr. n_gateway_providers: {}",
|
||||
this->gateway_providers.size());
|
||||
}
|
||||
|
||||
bts::api::ServiceContext &context;
|
||||
ServiceBridge *service_bridge;
|
||||
|
||||
std::vector<std::unique_ptr<IExecutionGatewayProvider>> gateway_providers;
|
||||
std::vector<std::unique_ptr<ActorThread>> actor_threads;
|
||||
|
||||
std::atomic<bool> service_ready;
|
||||
@@ -169,6 +186,7 @@ OrderServiceServer::OrderServiceServer(
|
||||
bts::api::ServiceContext &ctx,
|
||||
md::MDClient &mdc,
|
||||
DynamicOrderRegistry const &d,
|
||||
std::vector<std::unique_ptr<IExecutionGatewayProvider>> gateway_providers,
|
||||
metrics::MetricsContainer &stats,
|
||||
trace::PerfLog &log,
|
||||
md::TradeSummarySignalDispatcher &tssd)
|
||||
@@ -179,6 +197,7 @@ OrderServiceServer::OrderServiceServer(
|
||||
make_exchange_open_orders_stateful_publisher(),
|
||||
mdc,
|
||||
d,
|
||||
std::move(gateway_providers),
|
||||
stats,
|
||||
log)) {
|
||||
LOG(md, Info, "OrderServiceServer cons t_id: {} t_name: {}",
|
||||
@@ -209,7 +228,10 @@ OrderServiceServer::OrderServiceServer(
|
||||
bts_assert_msg(
|
||||
thread, "Could not find a configured thread for OrderManager. See "
|
||||
"the OrdermanCfg key");
|
||||
thread->executor.spawn<OrderManager>(d, me->log, mdc.oracle());
|
||||
{
|
||||
thread->executor.spawn<OrderManager>(
|
||||
d, me->log, mdc.oracle(), std::move(me->gateway_providers));
|
||||
}
|
||||
|
||||
// Timer
|
||||
thread = find_thread_for_actor("Timer", me->actor_threads);
|
||||
@@ -404,7 +426,6 @@ void OrderServiceServer::create_order(
|
||||
void OrderServiceServer::cancel_order(
|
||||
const api::orderman::Cancel &cancel,
|
||||
const api::Response<api::orderman::Response> &r) {
|
||||
|
||||
if (!me->service_ready) {
|
||||
api::orderman::Response response;
|
||||
response.set_order_key(cancel.order_key());
|
||||
@@ -435,7 +456,6 @@ void OrderServiceServer::update_order(
|
||||
void OrderServiceServer::detach_order(
|
||||
const api::orderman::Detach &detach,
|
||||
const api::Response<api::orderman::Response> &r) {
|
||||
|
||||
if (!me->service_ready) {
|
||||
api::orderman::Response response;
|
||||
response.set_order_key(detach.order_key());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "lib/md/MDClient.h"
|
||||
#include "lib/mercury/actors/IPollableActor.h"
|
||||
#include "lib/orderman/events/ExchangeOpenOrdersEvent.h"
|
||||
#include "lib/orderman/gateways/i_execution_gateway_provider.h"
|
||||
#include <memory>
|
||||
|
||||
namespace bts {
|
||||
@@ -28,6 +29,8 @@ class OrderServiceServer : public api::orderman::OrderServiceBase {
|
||||
bts::api::ServiceContext &context,
|
||||
md::MDClient &,
|
||||
DynamicOrderRegistry const &,
|
||||
std::vector<std::unique_ptr<IExecutionGatewayProvider>>
|
||||
gateway_providers,
|
||||
metrics::MetricsContainer &,
|
||||
trace::PerfLog &,
|
||||
md::TradeSummarySignalDispatcher &);
|
||||
|
||||
@@ -190,7 +190,7 @@ SessionServices &OrderServices::get_session_services(
|
||||
for (auto &s : _sessions)
|
||||
if (s.session_name() == for_session)
|
||||
return s;
|
||||
bts_assert(false);
|
||||
bts_assert_msgf(false, "No session named '{}'", for_session);
|
||||
}
|
||||
|
||||
void OrderServices::unsubscribe_from_order_creation_reply(
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#include "OrderManager.h"
|
||||
|
||||
#include "lib/config/protos/jetfix.pb.h"
|
||||
#include "lib/core/FMT.h"
|
||||
#include "lib/jetfix/common/OutFIX.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/md/SecurityOracle.h"
|
||||
#include "lib/mercury/events/TimerEvent.h"
|
||||
#include "lib/orderman/OrderServices.h"
|
||||
#include "lib/orderman/actors/order_manager/OrderServicesData.h"
|
||||
#include "lib/orderman/actors/order_manager/order_gateway_definition.h"
|
||||
#include "lib/orderman/actors/order_manager/session_services_data.h"
|
||||
#include "lib/orderman/events/AccountPnLUpdateEvent.h"
|
||||
#include "lib/orderman/events/ExcessLiquidityUpdateEvent.h"
|
||||
#include "lib/orderman/events/ExchangeOpenOrdersEvent.h"
|
||||
@@ -17,10 +22,13 @@
|
||||
#include "lib/orderman/events/StoredOrderEvent.h"
|
||||
#include "lib/orderman/events/TheoEvent.h"
|
||||
#include "lib/orderman/gateways/IExecutionGateway.h"
|
||||
#include "lib/orderman/gateways/i_execution_gateway_provider.h"
|
||||
#include "lib/orderman/order_factory/OrderFactory.h"
|
||||
#include "lib/orderman/orderman_utils/ExchangePosition.h"
|
||||
#include "lib/orderman/orders/Order.h"
|
||||
#include "lib/orderman/securities/ISecurityDB.h"
|
||||
#include "lib/os/spark_data_directory.h"
|
||||
#include "lib/tracing/PerfLog.h"
|
||||
#include "lib/utils/bts_assert.h"
|
||||
#include "lib/utils/inplace_function.h"
|
||||
#include "lib/utils/thread_manager.h"
|
||||
@@ -42,13 +50,27 @@ enum class OrderManager::State {
|
||||
READY
|
||||
};
|
||||
|
||||
static std::vector<OrderGatewayDefinition> create_gateways(
|
||||
std::vector<std::unique_ptr<IExecutionGatewayProvider>> providers) {
|
||||
std::vector<OrderGatewayDefinition> gws;
|
||||
for (auto &p : providers) {
|
||||
auto defs = p->create();
|
||||
for (auto &def : defs) {
|
||||
gws.push_back(std::move(def));
|
||||
}
|
||||
}
|
||||
return gws;
|
||||
}
|
||||
|
||||
struct OrderManager::PImpl {
|
||||
PImpl(
|
||||
OrderManager &owner,
|
||||
DynamicOrderRegistry const &b,
|
||||
trace::PerfLog &l,
|
||||
md::SecurityOracle o)
|
||||
: state(State::WAIT_ADMIN), services_data(owner, b, l, o) {}
|
||||
md::SecurityOracle o,
|
||||
std::vector<OrderGatewayDefinition> gateways)
|
||||
: state(State::WAIT_ADMIN),
|
||||
services_data(owner, b, l, o, std::move(gateways)) {}
|
||||
|
||||
State state;
|
||||
std::unordered_map<OrderKey, Order *> orders;
|
||||
@@ -61,8 +83,12 @@ struct OrderManager::PImpl {
|
||||
};
|
||||
|
||||
OrderManager::OrderManager(
|
||||
DynamicOrderRegistry const &b, trace::PerfLog &l, md::SecurityOracle o)
|
||||
: me(new PImpl(*this, b, l, o)) {
|
||||
DynamicOrderRegistry const &b,
|
||||
trace::PerfLog &l,
|
||||
md::SecurityOracle o,
|
||||
std::vector<std::unique_ptr<IExecutionGatewayProvider>> gw_providers)
|
||||
: me(std::make_unique<PImpl>(
|
||||
*this, b, l, o, create_gateways(std::move(gw_providers)))) {
|
||||
LOG(OrderMan, Info, "OrderManager cons t_id: {} t_name: {}",
|
||||
os::get_thread_id(), os::get_thread_name());
|
||||
auto id_gen = &me->services_data.id_gen;
|
||||
@@ -437,6 +463,14 @@ std::vector<jetfix::OutFIXPtr> &OrderManager::free_buffers(
|
||||
|
||||
void OrderManager::init_order_services() {
|
||||
auto &log = me->services_data.log;
|
||||
const std::string session_names = fmt::to_string(fmt::join(
|
||||
me->services_data.sessions |
|
||||
ranges::views::transform(
|
||||
[](const std::unique_ptr<SessionServicesData> &s) {
|
||||
return s->session;
|
||||
}),
|
||||
", "));
|
||||
LOG(OrderMan, Info, "init_order_services. sessions: {}", session_names);
|
||||
|
||||
for (auto &s : me->services_data.sessions) {
|
||||
auto gw_name = GatewayName(s->session);
|
||||
|
||||
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user