Compare commits

...
Author SHA1 Message Date
John Colvin 55dba0f50a build: Upgrade (Windows) c++ to stdlib 20 2023-12-23 13:23:52 -05:00
John Colvin ec7457b04c fix build: Remove a few RequiresPreviewFeatures attrs
Features which are now standard in .Net
2023-12-23 13:23:33 -05:00
John Colvin 822aa051d8 Add vcpkg submodule to update fmt to v10 (build still broken) 2023-12-23 13:23:29 -05:00
John Colvin a84f29ba38 build: Stop tracking CMakeDeps files
Not sure why these files were tracked in the first place, but cmake
automatically detects changes to the source file glob. Perhaps they were
originally added because it didn't work on some older version of CMake.
2023-12-23 13:12:30 -05:00
John Colvin a4f8c331e6 cleanup: Get rid of old custom optional.h
It's was integrated into the standard lib on windows some time ago
2023-12-23 13:12:30 -05:00
John Colvin d5e6b00534 Port a few common utils from development branch
This is a prereq to cherry-picking the commit to upgrade fmt to v10.0.0
2023-12-23 13:12:30 -05:00
John Colvin d24becd052 fix: Ensure LadderGridHost's price_comparer isn't null
Initialize it w/ reasonable default when LadderGridHost is constructed.

Bugs (fixed):
  * Sometimes the OrderBook's ladder panel throws a null ref ex when the
    orders collection changes

  Cause: The orders collection changed callback references the ladder's
  price_comparer, which may be null since it isn't initialized until the
  DataContextChanged event fires (the relative order of these events is
  non-deterministic)
2023-11-02 14:15:58 -04:00
John Colvin 13251f5c06 fix: Add special-case tick logic for [non-perp] futs at Deribit
Unlike any other securities supported by our system, these use
TickFactors of 25/10 (e.g., 10.0, 12.5, 15.0, etc.)
2023-11-02 13:55:36 -04:00
John Colvin 03b9aaa0e8 fix: Dispose BBOVM's md poll loop
Bugs (fixed):
  * Memory Leak! The md poll loop (instance var) in BBOVM is never
    disposed
2023-11-02 12:47:58 -04:00
Mohammad Arham 32d127fd84 added log to chec user input 2023-10-18 09:34:00 -07:00
John Colvin 3cb69c7520 fix build: Pass 'shell=true' to subprocess.run
Otherwise it doesn't evaluate the command string properly
2023-10-17 13:50:38 -04:00
John Colvin 47e897d038 feat: Resurrect VolSurfaceAdvancedSettings 2023-10-17 13:50:38 -04:00
John Colvin 126ec169ea refactor: Remove useless param 2023-10-17 13:49:13 -04:00
John Colvin 84953df12c dev: Copy docker/sparkd dir from development branch
So we can build prod in devcontainer
2023-10-17 13:34:23 -04:00
John Colvin e8322183ec Update externals to get emumba-deribit deb w/out LICENSE check 2023-10-17 13:33:01 -04:00
Mohammad Arham 30ba7ecd19 added logs for _default_abs_moves 2023-10-10 15:14:28 -07:00
Mohammad Arham db6b34c6a8 changd default_abs_moves from hard coded to config 2023-10-05 23:17:02 -07:00
Taha Afzal 4dbafcab91 TaskingEye can shoot deep in the book as well 2023-08-18 05:44:12 +00:00
Taha Afzal 9fd7f8a0a6 Adding security list for TaskingEye Algo 2023-08-17 21:33:40 +00:00
126 changed files with 3874 additions and 419 deletions

No files matched your search

+2
View File
@@ -51,3 +51,5 @@ trace_data/
/.venv*/*
.cache/
src/web/api/tsconfig.*.g.json
**/CMakeDeps.cmake
**/CMakeDeps_win.cmake
+3
View File
@@ -7,3 +7,6 @@
[submodule "docker/windows"]
path = docker/windows
url = git@bitbucket.org:bluetradesys/windows_docker.git
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg.git
+4 -1
View File
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.12)
project(spark)
include("${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake")
set(CMAKE_CXX_STANDARD 20)
if(WIN32)
set(platform "windows")
else()
@@ -35,7 +38,7 @@ if(WIN32)
/D_WINSOCK_DEPRECATED_NO_WARNINGS \
/D__TBB_NO_IMPLICIT_LINKAGE \
/DBOOST_ALL_NO_LIB \
/std:c++17 \
/std:c++20 \
/D_SCL_SECURE_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS /D_WIN32_WINNT=0x0600")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_DEBUG} /MT")
-1
View File
@@ -1 +0,0 @@
set(sources /home/taha/spark/doc/user/build.sh;content/_index.md;content/bts_spark/Glossary/_index.md;content/bts_spark/Help/_index.md;content/bts_spark/Help/about_spark.png;content/bts_spark/Help/image39.png;content/bts_spark/Help/image58.png;content/bts_spark/Launcher/_index.md;content/bts_spark/Launcher/image38.png;content/bts_spark/Launcher/image68.png;content/bts_spark/Logging-In-to-the-System/_index.md;content/bts_spark/Logging-In-to-the-System/image3.png;content/bts_spark/Logging-In-to-the-System/image64.png;content/bts_spark/Market/_index.md;content/bts_spark/Market/change_securities.png;content/bts_spark/Market/image11.png;content/bts_spark/Market/image14.png;content/bts_spark/Market/image20.png;content/bts_spark/Market/image21.png;content/bts_spark/Market/image23.png;content/bts_spark/Market/image24.png;content/bts_spark/Market/image34.png;content/bts_spark/Market/image49.png;content/bts_spark/Market/image53.png;content/bts_spark/Market/image66.png;content/bts_spark/Market/image69.png;content/bts_spark/Market/market_view.png;content/bts_spark/Market/settings_icon.png;content/bts_spark/Market/time_and_sales.png;content/bts_spark/My-Windows/_index.md;content/bts_spark/My-Windows/image15.png;content/bts_spark/My-Windows/image16.png;content/bts_spark/My-Windows/image37.png;content/bts_spark/Settings/_index.md;content/bts_spark/Settings/image51.png;content/bts_spark/Settings/image9.png;content/bts_spark/Spreader/_index.md;content/bts_spark/Spreader/image10.png;content/bts_spark/Spreader/image13.png;content/bts_spark/Spreader/image18.png;content/bts_spark/Spreader/image19.png;content/bts_spark/Spreader/image28.png;content/bts_spark/Spreader/image29.png;content/bts_spark/Spreader/image31.png;content/bts_spark/Spreader/image35.png;content/bts_spark/Spreader/image40.png;content/bts_spark/Spreader/image43.png;content/bts_spark/Spreader/image54.png;content/bts_spark/Spreader/image55.png;content/bts_spark/Spreader/image56.png;content/bts_spark/Spreader/image6.png;content/bts_spark/Spreader/image7.png;content/bts_spark/Spreader/image70.png;content/bts_spark/Spreader/image72.png;content/bts_spark/Spreader/image8.png;content/bts_spark/Trading/OrderTypes/index.md;content/bts_spark/Trading/_index.md;content/bts_spark/Trading/algo_studio.png;content/bts_spark/Trading/collapse_all_icon.png;content/bts_spark/Trading/cover_algo_control.png;content/bts_spark/Trading/expand_all_icon.png;content/bts_spark/Trading/image1.png;content/bts_spark/Trading/image17.png;content/bts_spark/Trading/image2.png;content/bts_spark/Trading/image25.png;content/bts_spark/Trading/image27.png;content/bts_spark/Trading/image33.png;content/bts_spark/Trading/image42.png;content/bts_spark/Trading/image44.png;content/bts_spark/Trading/image47.png;content/bts_spark/Trading/image48.png;content/bts_spark/Trading/image5.png;content/bts_spark/Trading/image50.png;content/bts_spark/Trading/image52.png;content/bts_spark/Trading/image57.png;content/bts_spark/Trading/image59.png;content/bts_spark/Trading/image60.png;content/bts_spark/Trading/image61.png;content/bts_spark/Trading/image62.png;content/bts_spark/Trading/image73.png;content/bts_spark/Trading/image74.png;content/bts_spark/Trading/join_algo_control.png;content/bts_spark/Trading/ladder.png;content/bts_spark/Trading/ladder_badges.png;content/bts_spark/Trading/ladder_main_part.png;content/bts_spark/Trading/lean_algo_control.png;content/bts_spark/Trading/mini_orderbook.png;content/bts_spark/Trading/oco_algo_control.png;content/bts_spark/Trading/offset_algo_control.png;content/bts_spark/Trading/pnl_calculation.png;content/bts_spark/Trading/pnl_window.png;content/bts_spark/Trading/recreate_icon.png;content/bts_spark/Trading/settings_icon.png;content/bts_spark/Trading/spark_menu_order_book.png;content/bts_spark/Trading/top_algo_control.png;content/bts_spark/Trading/trigger_algo_control.png;content/bts_spark/_index.md;content/release_notes/_index.md;content/system_requirements/_index.md;static/book.js;static/favicon.ico;static/image.js;templates/index.html;templates/page.html;templates/section.html;templates/shortcodes/back_button.html)
+135
View File
@@ -0,0 +1,135 @@
FROM ubuntu:latest
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=America/New_York
# light deps (should be quick to download/install)
RUN apt-get update && apt-get install -y \
ca-certificates \
curl \
gnupg \
liblttng-ust-dev \
libpcap0.8-dev \
locales \
ninja-build \
software-properties-common \
sqlite3 \
tzdata
# clang
RUN apt-get update && apt-get install -y \
clang-13 \
clang-format-13 \
clangd-13 \
clang-15 \
clang-format-15 \
clangd-15
# g++
RUN apt-get update && apt-get install -y \
g++-11 \
g++-12
# python
RUN apt-get update && apt-get install -y \
python3-pip \
python3-venv \
&& python3 -m pip install --upgrade pip
# dotnet
RUN apt-get update && apt-get install -y \
dotnet7
# ssh
RUN apt-get update && apt-get install -y \
libssh2-1-dev \
openssh-server
# cmake
RUN \
curl -fsSL https://apt.kitware.com/keys/kitware-archive-latest.asc 2> /dev/null \
| gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg > /dev/null \
&& apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" \
&& apt-get update \
&& apt-get install -y cmake
# node
ARG node_major=18
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$node_major.x nodistro main" \
| tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y nodejs
# ccache is done here so that ccache sees clang++ installed, not clang-10
RUN apt-get update && apt-get install -y \
ccache
# emumba (light deps)
RUN apt-get update && apt-get install -y \
build-essential \
doxygen \
python3-sphinx \
graphviz \
libgtest-dev \
libssl-dev \
rapidjson-dev \
zlib1g-dev \
pkg-config
# emumba (boost)
RUN apt-get update && apt-get install -y \
libboost-filesystem-dev \
libboost-program-options-dev \
libboost-test-dev \
libboost-date-time-dev
# dev utils
RUN apt-get update && apt-get install -y \
fish \
git-all \
sudo \
vim \
wget
# set up default executables (update-alternatives)
RUN \
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-13 100; \
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-13 100; \
update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-13 100; \
update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-13 100; \
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100; \
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
# Add compiler symlinks for ccache
RUN \
ln -s /usr/bin/ccache /usr/local/bin/clang; \
ln -s /usr/bin/ccache /usr/local/bin/clang++; \
ln -s /usr/bin/ccache /usr/local/bin/clang-13; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-13; \
ln -s /usr/bin/ccache /usr/local/bin/clang-15; \
ln -s /usr/bin/ccache /usr/local/bin/clang++-15; \
ln -s /usr/bin/ccache /usr/local/bin/gcc; \
ln -s /usr/bin/ccache /usr/local/bin/g++; \
ln -s /usr/bin/ccache /usr/local/bin/gcc-11; \
ln -s /usr/bin/ccache /usr/local/bin/g++-11; \
ln -s /usr/bin/ccache /usr/local/bin/gcc-12; \
ln -s /usr/bin/ccache /usr/local/bin/g++-12
RUN locale-gen en_US.UTF-8
ARG docker_user="bts"
ARG docker_uid="1000"
ARG docker_pass="bts"
RUN \
groupadd -g "$docker_uid" "$docker_user" \
&& useradd \
-m \
-u "$docker_uid" \
-g "$docker_uid" \
-G sudo \
--password "$(openssl passwd -1 "$docker_pass")" \
"$docker_user"
+64
View File
@@ -0,0 +1,64 @@
[dev containers]: https://code.visualstudio.com/docs/devcontainers/containers
[dev containers ext]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
[docker repo sparkd-dev]: https://hub.docker.com/repository/docker/bluetradingsystems/sparkd-dev/general
# Sparkd Dev Container
## How to create a VS Code [Dev Container][dev containers]
1. Make sure the **[Dev Containers][dev containers ext]** extension is installed
1. Make sure your Docker image is up to date
```
docker pull bluetradingsystems/sparkd-dev:latest
```
1. Copy **devcontainer.json** from this folder to **$workspaceFolder/.devcontainer/**.
1. In the VS Code Command Palette, run **Dev Containers: Reopen in Container**
---
## How to create a Sparkd Container in the terminal (without VS Code)
1. Make sure your Docker image is up to date
```
docker pull bluetradingsystems/sparkd-dev:latest
```
2. Start the container
```
# Replace <cont_name> with a name for the container. The container's
# "$HOME" dir will be mounted to "$HOME/.sparkd-dev/mounts/<cont_name> on
# the host
./start_docker.sh --container <cont_name> --mount-existing
```
* *Replace <cont_name> with a name for the container*
* *The container's `$HOME` dir will be mounted to
`$HOME/.sparkd-dev/mounts<cont_name>` on the host*<br>
* *Run: `./start_docker.sh --help` to see additional options*
3. *(Optional) Attach VS Code to the running container.*<br>
Run **Dev Containers: Attach to Running Container** from the command palette,
then select your container in the dropdown
---
## How to update the Docker image
1. Change the image by modifying the Dockerfile (don't tag arbitrary
images on your machine and push them to our repo on Docker Hub)
1. After modifying the Dockerfile, (re)build the image
```
# Run in this dir
docker build -t bluetradingsystems/sparkd-dev:latest .
```
***Note:** Your docker login must have write permissions to push to the
[sparkd-dev][docker repo sparkd-dev] repository*
2. Commit the changes to Dockerfile in git
3. Push the new image to Docker Hub
```
docker push bluetradingsystems/sparkd-dev:latest
```
***Note:** The image doesn't have to be on Docker Hub, we can simply build
it from scratch on our local machines (with the Dockerfile). However, this
takes time. It is significantly quicker to download a prebuilt image, so we
store it on a remote repository to save time when setting up a new dev
machine.*
+123
View File
@@ -0,0 +1,123 @@
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
case $- in
*i*) ;;
*) return;;
esac
# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth
# append to the history file, don't overwrite it
shopt -s histappend
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# If set, the pattern "**" used in a pathname expansion context will
# match all files and zero or more directories and subdirectories.
#shopt -s globstar
# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color|*-256color) color_prompt=yes;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# colored GCC warnings and errors
#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Add an "alert" alias for long running commands. Use like so:
# sleep 10; alert
alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
if [ -f /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi
if [[ ! -v XDG_CONFIG_HOME ]]; then
export XDG_CONFIG_HOME="$HOME/.config"
fi
export PATH="$HOME/.local/bin:$PATH"
+25
View File
@@ -0,0 +1,25 @@
if ! set -q XDG_CONFIG_HOME
set -g XDG_CONFIG_HOME "$HOME/.config"
end
set fish_config_home $XDG_CONFIG_HOME/fish
if test -f "$fish_config_home/aliases.fish"
source "$fish_config_home/aliases.fish"
end
# enable general colors and case insensitive search in 'less'
set -x LESS "$LESS -R -Q -I"
set -x LC_ALL en_US.UTF-8
# enable syntax highlighting in 'less'
# set highlight color scheme in 'less' (fg)(bg)
set -g LESS_TERMCAP_md (tput bold)(tput setaf 36)
set -g LESS_TERMCAP_me (tput sgr0)
set -g LESS_TERMCAP_so (tput setaf 232)(tput setab 49)
set -q TERM
if test $status != 0; or test $TERM = 'xterm'
set -g TERM 'xterm-256color'
end
# Fix impossible-to-see navy blue command text color
set fish_color_command F8F8F2
set -g PATH "$HOME/.local/bin" "$PATH"
+32
View File
@@ -0,0 +1,32 @@
{
"image": "bluetradingsystems/sparkd-dev:latest",
"initializeCommand": "${localWorkspaceFolder}/docker/sparkd/dev/devcontainer_initialize.sh",
"hostRequirements": {
"memory": "4gb",
"storage": "6gb"
},
"postCreateCommand": "/home/bts/spark/docker/sparkd/dev/devcontainer_post_create.sh",
"containerUser": "bts",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bts/spark,type=bind",
"workspaceFolder": "/home/bts/spark",
"updateRemoteUserUID": true,
"shutdownAction": "none",
// Note: If running multiple containers on the same host, each must use a
// unique set of ports. Usually we just add 1000 to each port as needed. For
// example, if 8000-8003 is taken, go with 9000-9003, then 10000-10003, etc.
"runArgs": [
"--shm-size",
"2gb",
"-p",
"8000:8000",
"-p",
"8001:8001",
"-p",
"8002:8002",
"-p",
"8003:8003"
],
"mounts": [
"source=${localEnv:HOME}/.sparkd-dev/mounts/sparkd-dev,target=/home/bts,type=bind"
]
}
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
mkdir -p "$HOME/.sparkd-dev/mounts/sparkd-dev"
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
this_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
repo_dir="$(cd "$this_dir" && git rev-parse --show-toplevel)"
tools_dir="$repo_dir/tools"
gen_tag="GENERATED BY $(basename "${BASH_SOURCE[0]}")"
function copy_if_not_exist() {
src="$1"
dest="$2"
if [[ ! -e "$src" ]]; then
echo "Unable to copy $src to $dest. Source file doesn't exist"
exit 1
fi
if [[ ! -e "$dest" ]]; then
cp "$src" "$dest"
fi
}
# Init bashrc
echo "Initializing bashrc"
copy_if_not_exist "$this_dir/bashrc" "$HOME/.bashrc"
# Init fish config
echo "Initializing fish config"
fish_cfg_dir="$HOME/.config/fish/"
mkdir -p "$fish_cfg_dir"
exec "$tools_dir/write_tag_block.sh" \
--tag "$gen_tag" \
--replace \
--in-place \
--file "$fish_cfg_dir/config.fish" \
--text "$(cat "$this_dir/config.fish")"
+250
View File
@@ -0,0 +1,250 @@
#!/usr/bin/env bash
function _exit() {
if [[ "$#" -gt 1 ]]; then
# When there are multiple args, the last arg is the exit code.
echo "${@:1:($# - 1)}"
exit "${@: -1}"
fi
echo "$1"
exit 0
}
help_text="\
Add or update a user's uid
This script is intended to be run in a Docker container as a means to obtain
file permissions in mounted directories owned by the specified UID.
Usage: set_user.sh [OPTIONS] USER UID
Positionals:
USER The name of the user
Options:
--user USER The name of the user (defaults to 'bts')
--uid UID The new uid to give USER (defaults to '1000')
--group GROUP The primary group to give USER (defaults to USER)
--gid GID The primary gid to give USER (defaults to UID)
--pass PASSWORD The password to give USER (defaults to USER, only applies
if USER hasn't been created yet)
--modify-existing Update UID/GID even if USER already exists
-h,--help Print this help message and exit
"
function _add_group() {
if [[ "$#" -ne 2 ]]; then
_exit "'_add_group' requires exactly 2 args (group, gid). Received $#." 1
fi
local group="$1"
local gid="$2"
# Check if gid already exists
local existing_group=""
existing_group="$(getent group "$gid" | cut -d : -f 1)"
if [[ -n "$existing_group" ]]; then
if [[ "$existing_group" != "$group" ]]; then
echo "\
The gid '$gid' is already in use by group '$existing_group' (will not rename \
the group)"
fi
return
fi
# Check if group with same name already exists
local existing_gid=""
existing_gid="$(getent group "$group" | cut -d : -f 3)"
if [[ -n "$existing_gid" ]]; then
# The group already exists. Only modify if --modify-existing was specified.
if [[ "$modify_existing" -eq 0 ]]; then
_exit "\
The group '$group' already exists with gid '$existing_gid'.
Specify --modify-existing to change the gid anyways." 1
fi
echo "Changing gid for group '$group' from '$existing_gid' to '$gid'"
groupmod -g "$gid" "$group"
else
# The group doesn't exist. Add it
echo "Adding group '$group:$gid'"
groupadd -g "$gid" "$group"
fi
}
function _add_user() {
local user="$1"
local uid="$2"
local group="$3"
local gid="$4"
local pass="$5"
if [[ "$#" -ne 5 ]]; then
_exit "'_add_user' requires exactly 5 args (user, uid, group, gid, pass). \
Received $#." 1
fi
# Check if uid is being used by another user
local existing_user=""
existing_user="$(getent passwd "$uid" | cut -d : -f 1)"
if [[ -n "$existing_user" ]]; then
if [[ "$existing_user" != "$user" ]]; then
_exit "The uid '$uid' is already in use by user '$existing_user'." 1
fi
echo "The user '$user' already has the specified uid '$uid'."
# Modify gid if necessary
local existing_gid=""
existing_gid="$(id -g "$user" 2> /dev/null)"
if [[ "$existing_gid" = "$gid" ]]; then
echo "The user '$user' already has the specified gid '$gid'."
return
fi
if [[ "$modify_existing" -eq 0 ]]; then
_exit "\
The user '$user' is associated with the primary group '$existing_gid'.
Specify --modify-existing to change the gid anyways." 1
fi
echo "Changing primary group for '$user' from '$existing_gid' to '$gid'."
usermod -g "$gid" "$user"
return
fi
# Check if user with same name already exists
local existing_uid=""
existing_uid="$(id -u "$user" 2> /dev/null)"
if [[ -n "$existing_uid" ]]; then
if [[ "$existing_uid" = "$uid" ]]; then
_exit "The user '$user' already has the specified uid '$uid'" 0
fi
if [[ "$modify_existing" -eq 0 ]]; then
_exit "\
User '$user' already exists with uid '$existing_uid'.
Specify --modify-existing to change the uid anyways." 1
fi
echo "Changing uid for user '$user' from '$existing_uid' to '$uid'"
if ! usermod -u "$uid" -g "$gid" "$user"; then
_exit "usermod exited with code $?" 1
fi
return
fi
# The user doesn't exist. Add it
echo "Adding user '$user:$uid' (group='$group:$gid', password='$pass')"
useradd -m -u "$uid" -g "$gid" -G sudo --password "$(openssl passwd -1 "$pass")" "$user"
}
function _update_fish_dir_perms() {
local user="$1"
local uid="$2"
local gid="$3"
# When a fish shell session starts, a temp dir named '/tmp/fish.<user>' is
# created with the user's current uid/gid. If such a dir exists for the
# specified USER, make sure the uid/gid match the new values.
fish_dir="/tmp/fish.$user"
if [[ -d "$fish_dir" ]]; then
perms="$uid:$gid"
existing_perms="$(stat -c %u:%g "$fish_dir")"
if [[ "$existing_perms" != "$perms" ]]; then
echo "Changing uid:gid for dir '$fish_dir' from '$existing_perms' to '$perms'"
chown -R "$perms" "$fish_dir"
fi
fi
}
user=""
group=""
pass=""
declare -i uid=0
declare -i gid=0
declare -i modify_existing=0
while [[ $# -gt 0 ]]; do
case "$1" in
--user)
if [[ -z "$2" ]]; then
_exit "USER (--user) must be non-empty" 1
elif [[ "$2" = "root" ]]; then
_exit "USER (--user) cannot be 'root'" 1
fi
user="$2"
shift 2
;;
--group)
if [[ -z "$2" ]]; then
_exit "GROUP (--group) must be non-empty" 1
elif [[ "$2" = "root" ]]; then
_exit "GROUP (--group) cannot be 'root'" 1
fi
group="$2"
shift 2
;;
--uid)
if [[ -z "$2" ]]; then
_exit "UID (--uid) must be non-empty" 1
fi
uid="$2"
shift 2
;;
--gid)
if [[ -z "$2" ]]; then
_exit "GID (--gid) must be non-empty" 1
fi
gid="$2"
shift 2
;;
--pass)
if [[ -z "$2" ]]; then
_exit "PASS (--pass) must be non-empty" 1
fi
pass="$2"
shift 2
;;
--modify-existing)
modify_existing=1
shift 1
;;
-h)
;&
--help)
_exit "$help_text" 0
;;
*)
if [[ -z "$1" ]]; then
_exit "Invalid positional argument '$1'" 1
elif [[ -z "$user" ]]; then
user="$1"
elif [[ -z "$uid" ]]; then
uid="$1"
else
_exit "Unexpected argument '$1'" 1
fi
shift 1
;;
esac
done
if [[ -z "$user" ]]; then
user="bts"
fi
if [[ $uid -eq 0 ]]; then
uid=1000
fi
# In general, a user's primary group/gid matches the user's name/uid.
# If group/gid args weren't specified, set them to name/uid.
if [[ -z "$group" ]]; then
group="$user"
fi
if [[ $gid -eq 0 ]]; then
gid=$uid
fi
if [[ -z "$pass" ]]; then
pass="$user"
fi
_add_group "$group" "$gid"
_add_user "$user" "$uid" "$group" "$gid" "$pass"
_update_fish_dir_perms "$user" "$uid" "$gid"
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/env bash
function _exit() {
if [[ "$#" -gt 1 ]]; then
# When there are multiple args, the last arg is the exit code.
echo "${@:1:($# - 1)}"
exit "${@: -1}"
fi
echo "$1"
exit 0
}
help_text="\
Start a docker dev container
Usage: start_docker.sh [OPTIONS] image container
Positionals:
image The image to instantiate
container The name for the container
Options:
-u,--user USER The user to add in the container (defaults to 'bts')
-p,--pass PASS The password for USER in the container (defaults to USER)
-i,--image IMAGE The image to instantiate (defaults to 'bluetradingsystems/sparkd-dev:latest')
-c,--container CONT The name for the container
-m,--mount MOUNT The name for the folder (outside the container) to create/mount to the home
directory in the container. Mount folders are added here: $mount_prefix
--mount-existing Mount the specified dir even if it already exists
--port <cont_port>:<host_port> OR [<cont_port>:<host_port>,...] Port mapping(s). May be
specified multiple times.
-h,--help Print this help message and exit
"
mount_prefix="$HOME/.sparkd-dev/mounts"
declare docker_user=""
declare docker_pass=""
declare image_name=""
declare cont_name=""
declare mount_name=""
declare -i mount_existing=0
declare -a port_mappings=()
while [[ $# -gt 0 ]]; do
case "$1" in
-u)
;&
--user)
if [[ -z "$2" ]]; then
_exit "User (-u,--user) must be non-empty" 1
fi
docker_user="$2"
shift 2
;;
-p)
;&
--pass)
if [[ -z "$2" ]]; then
_exit "Password (-p,--pass) must be non-empty" 1
fi
docker_pass="$2"
shift 2
;;
-i)
;&
--image)
if [[ -n "$image_name" ]]; then
_exit "Unexpected argument '$1'" 1
elif [[ -z "$2" ]]; then
_exit "Image (-i,--image) must be non-empty" 1
fi
image_name="$2"
shift 2
;;
-c)
;&
--container)
if [[ -n "$cont_name" ]]; then
_exit "Unexpected argument '$1'" 1
elif [[ -z "$2" ]]; then
_exit "Container (-c,--container) must be non-empty" 1
fi
cont_name="$2"
shift 2
;;
--port)
# Parse port(s) arg, and append the result to 'port_mappings'
# Note: port(s) may be specified in bracketed notation:
# [a:b,c:d,...,y:z]
next_ports="$(echo "$2" | sed -e 's/\[//g' -e 's/\]//g' -e 's/,/ /g')"
if [[ "${#port_mappings[@]}" -gt 0 ]]; then
next_ports=" $next_ports"
fi
port_mappings+=("$next_ports")
shift 2
;;
-m)
;&
--mount)
if [[ -z "$2" ]]; then
echo "Mount (-m,--mount) must be non-empty"
fi
mount_name="$2"
shift 2
;;
--mount-existing)
mount_existing=1
shift 1
;;
-h)
;&
--help)
_exit "$help_text" 0
;;
*)
if [[ -z "$1" ]]; then
_exit "Invalid positional argument '$1'" "$help_text" 1
elif [[ -z "$image_name" ]]; then
image_name="$1"
elif [[ -z "$cont_name" ]]; then
cont_name="$1"
else
_exit "Unexpected argument '$1'" "$help_text" 1
fi
shift 1
;;
esac
done
if [[ -z "$image_name" ]]; then
image_name="bluetradingsystems/sparkd-dev:latest"
fi
host_name="$(hostname)"
if [[ -n "$cont_name" ]]; then
host_name="$host_name.$cont_name"
fi
if [[ -z "$mount_name" ]]; then
mount_name="$cont_name"
fi
if [[ -z "$mount_name" && -z "$cont_name" ]]; then
_exit "Please specify mount (-m,--mount) and/or container (-c,--container)" 1
fi
if [[ -z "$docker_user" ]]; then
docker_user="bts"
fi
if [[ "$docker_user" = "root" ]]; then
_exit "USER (--user) cannot be 'root'" 1
fi
cont_home="/home/$docker_user"
home_src="$mount_prefix/$mount_name"
home_tgt="$cont_home"
this_dir="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
repo_src="$(cd "$this_dir" && git rev-parse --show-toplevel)"
repo_tgt="$cont_home/spark"
working_dir="$cont_home"
if [[ $mount_existing -eq 0 && -e "$home_src" ]]; then
# Don't overwrite existing dir (might be mounted in another container)
_exit "Mount dir '$home_src' already exists (use --mount-existing to mount \
an existing directory)" 1
fi
mkdir -p "$home_src"
if [[ "${#port_mappings[@]}" -eq 0 ]]; then
port_mappings=("8000:8000" "8001:8001" "8002:8002" "8003:8003")
fi
# Adapt port_mappings into args for `docker run` by prepending "-p " to each
# item
declare -a docker_port_args=()
for port_mapping in "${port_mappings[@]}"; do
docker_port_args+=("-p $port_mapping")
done
echo "Starting container '$cont_name'
image_name : $image_name
cont_name : $cont_name
home_src : $home_src
home_tgt : $home_tgt
repo_src : $repo_src
repo_tgt : $repo_tgt
working dir : $working_dir
ports : ${port_mappings[*]}
"
# DON'T wrap $docker_port_args in quotes. We want bash to split args by
# whitespace
docker run \
--net bridge \
-h "$host_name" \
-e "TZ=America/New_York" \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--name "$cont_name" \
-v "$home_src:$home_tgt" \
-v "$repo_src:$repo_tgt" \
-w "$working_dir" \
${docker_port_args[*]} \
--shm-size=4024m \
-t \
-i \
-d \
-e DISPLAY="$DISPLAY" \
"$image_name"
# Add user w/ same uid and gid as the current user outside of docker, so the
# user inside the docker has file permissions in the mounted dir.
docker_group="$docker_user"
docker_uid="$(id -u "$USER")"
docker_gid="$(id -g "$USER")"
if [[ -z "$docker_pass" ]]; then
docker_pass="$docker_user"
fi
docker exec -u root -it "$cont_name" "$repo_tgt/docker/sparkd/dev/set_user.sh" \
--user "$docker_user" \
--uid "$docker_uid" \
--group "$docker_group" \
--gid "$docker_gid" \
--pass "$docker_pass"
# Run same script VS Code runs to initialize the container
docker exec -u "$docker_user" -it "$cont_name" "$repo_tgt/docker/sparkd/dev/devcontainer_post_create.sh"
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
function print_help_and_exit() {
cat <<EOF
Open a shell inside the docker container
Usage: docker_shell.sh [OPTIONS] [CONTAINER]
Options:
-u,--user The user to login with in the container (defaults to the current user)
-c,--container The container name (may also be specified as positional arg)
-h,--help Print this help message and exit
EOF
exit "$1"
}
declare docker_user=""
declare cont_name=""
while [[ $# -gt 0 ]]; do
case "$1" in
-u)
;&
--user)
if [[ -z "$2" ]]; then
echo "User (-u,--user) must be non-empty"
print_help_and_exit 1
fi
docker_user="$2"
shift 2
;;
-h)
;&
--help)
print_help_and_exit 0
;;
-c)
;&
--container)
shift 1
;&
*)
if [[ ! -z "$cont_name" ]]; then
echo "Unexpected argument '$1'"
print_help_and_exit 1
fi
cont_name="$1"
shift 1
;;
esac
done
if [[ -z "$cont_name" ]]; then
echo "Please specify the container ([-c,--container])"
print_help_and_exit 1
fi
if [[ -z "$docker_user" ]]; then
docker_user="$USER"
fi
docker exec -u "$docker_user" -it "$cont_name" bash
+233
View File
@@ -0,0 +1,233 @@
#!/usr/bin/env bash
mount_prefix="$HOME/.spark-dev/mounts"
function print_help_and_exit() {
cat <<EOF
Start a docker dev container
Usage: start_docker.sh [OPTIONS] image container
Positionals:
image The image to instantiate
container The name for the container
Options:
-u,--user USER The user to add in the container (defaults to the current user)
-p,--pass PASS The password for USER in the container (defaults to USER)
-i,--image IMAGE The image to instantiate
-c,--container CONT The name for the container
-m,--mount MOUNT The name for the folder (outside the container) to create/mount to the home
directory in the container. Mount folders are added here: $mount_prefix
--mount-existing Mount the specified dir even if it already exists
--port <cont_port>:<host_port> OR [<cont_port>:<host_port>,...] Port mapping(s). May be
specified multiple times.
-h,--help Print this help message and exit
EOF
exit "$1"
}
declare docker_user=""
declare docker_pass=""
declare image_name=""
declare cont_name=""
declare mount_name=""
declare -i mount_existing=0
declare -a port_mappings=()
while [[ $# -gt 0 ]]; do
case "$1" in
-u)
;&
--user)
if [[ -z "$2" ]]; then
echo "User (-u,--user) must be non-empty"
print_help_and_exit 1
fi
docker_user="$2"
shift 2
;;
-p)
;&
--pass)
if [[ -z "$2" ]]; then
echo "Password (-p,--pass) must be non-empty"
print_help_and_exit 1
fi
docker_pass="$2"
shift 2
;;
-i)
;&
--image)
if [[ -n "$image_name" ]]; then
echo "Unexpected argument '$1'"
print_help_and_exit 1
elif [[ -z "$2" ]]; then
echo "Image (-i,--image) must be non-empty"
print_help_and_exit 1
fi
image_name="$2"
shift 2
;;
-c)
;&
--container)
if [[ -n "$cont_name" ]]; then
echo "Unexpected argument '$1'"
print_help_and_exit 1
elif [[ -z "$2" ]]; then
echo "Container (-c,--container) must be non-empty"
print_help_and_exit 1
fi
cont_name="$2"
shift 2
;;
--port)
# Parse port(s) arg, and append the result to 'port_mappings'
# Note: port(s) may be specified in bracketed notation:
# [a:b,c:d,...,y:z]
next_ports="$(echo "$2" | sed -e 's/\[//g' -e 's/\]//g' -e 's/,/ /g')"
if [[ "${#port_mappings[@]}" -gt 0 ]]; then
next_ports=" $next_ports"
fi
port_mappings+=("$next_ports")
shift 2
;;
-m)
;&
--mount)
if [[ -z "$2" ]]; then
echo "Mount (-m,--mount) must be non-empty"
fi
mount_name="$2"
shift 2
;;
--mount-existing)
mount_existing=1
shift 1
;;
-h)
;&
--help)
print_help_and_exit 0
;;
*)
if [[ -z "$1" ]]; then
echo "Invalid positional argument '$1'"
print_help_and_exit 1
elif [[ -z "$image_name" ]]; then
image_name="$1"
elif [[ -z "$cont_name" ]]; then
cont_name="$1"
else
echo "Unexpected argument '$1'"
print_help_and_exit 1
fi
shift 1
;;
esac
done
if [[ -z "$image_name" ]]; then
image_name="bluetradingsystems/spark-crypto-dev"
fi
host_name="$(hostname)"
if [[ -n "$cont_name" ]]; then
host_name="$host_name.$cont_name"
fi
if [[ -z "$mount_name" ]]; then
mount_name="$cont_name"
fi
if [[ -z "$mount_name" && -z "$cont_name" ]]; then
echo "Please specify mount (-m,--mount) and/or container (-c,--container)"
print_help_and_exit 1
fi
if [[ -z "$docker_user" ]]; then
docker_user="$USER"
fi
home_src=$mount_prefix/$mount_name
home_tgt=$HOME
repo_src=$(git rev-parse --show-superproject-working-tree)
repo_tgt=$HOME/$(basename "$repo_src")
working_dir=$HOME
if [[ $mount_existing -eq 0 && -e "$home_src" ]]; then
# Don't overwrite existing dir (might be mounted in another container)
echo "Mount dir '$home_src' already exists (use --mount-existing to mount an existing directory)"
print_help_and_exit 1
fi
mkdir -p "$home_src"
if [[ "${#port_mappings[@]}" -eq 0 ]]; then
port_mappings=("8000:8000" "8001:8001" "8002:8002" "8003:8003")
fi
# Adapt port_mappings into args for `docker run` by prepending "-p " to each
# item
declare -a docker_port_args=()
for port_mapping in "${port_mappings[@]}"; do
docker_port_args+=("-p $port_mapping")
done
echo "Starting container '$cont_name'
image_name : $image_name
cont_name : $cont_name
home_src : $home_src
home_tgt : $home_tgt
repo_src : $repo_src
repo_tgt : $repo_tgt
working dir : $working_dir
ports : ${port_mappings[*]}
"
# DON'T wrap $docker_port_args in quotes. We want bash to split args by
# whitespace
docker run \
--net bridge \
-h "$host_name" \
-e "TZ=America/New_York" \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--name "$cont_name" \
-v "$home_src:$home_tgt" \
-v "$repo_src:$repo_tgt" \
-w "$working_dir" \
${docker_port_args[*]} \
--shm-size=4024m \
-t \
-i \
-d \
-e DISPLAY="$DISPLAY" \
"$image_name" \
bash
# Add user w/ same uid and gid as the current user outside of docker, so the
# user inside the docker has file permissions in the mounted dir.
docker_group="$docker_user"
docker_user_uid="$(id -u "$USER")"
docker_user_gid="$(id -g "$USER")"
echo "Adding user in container '$cont_name'
name : $docker_user
uid : $docker_user_uid
gid : $docker_user_gid"
if [[ -z "$docker_pass" ]]; then
docker_pass="$docker_user"
echo "Giving user '$docker_user' a secure password: $docker_pass"
fi
docker exec -u root -it "$cont_name" groupadd -g "$docker_user_gid" "$docker_group"
docker exec -u root -it "$cont_name" useradd -u "$docker_user_uid" -g "$docker_user_gid" "$docker_user"
docker exec -u root -it "$cont_name" usermod --password "$(openssl passwd -1 "$docker_pass")" "$docker_user"
docker exec -u root -it "$cont_name" usermod -aG sudo "$docker_user"
docker exec -u root -it "$cont_name" "$repo_tgt/externals/src/emumba/install_emumba-deribit-connector.sh"
@@ -14,7 +14,7 @@ namespace mbts.spark {
class VolSurfaceAdvancedSettingsEditorVM : ViewModelBase {
readonly VolSurfaceAdvancedSettings _settings;
public VolSurfaceAdvancedSettingsEditorVM(VolSurfaceAdvancedSettings settings, string vs_name, Action<VolSurfaceAdvancedSettings> commit) {
public VolSurfaceAdvancedSettingsEditorVM(VolSurfaceAdvancedSettings settings, string vs_name) {
_settings = settings;
_settings.theo_handle_measure = VolSurfaceEnums.GeneralChangeMeasure.Absolute;
_settings.slide_handle_measure = VolSurfaceEnums.GeneralChangeMeasure.Percentage;
@@ -23,7 +23,7 @@ namespace mbts.spark {
if (_settings.precomputed_theo_handles == null)
_settings.precomputed_theo_handles = new List<double>();
vol_surface_name = vs_name;
this.commit = new DelegateCommand(() => request_commit?.Invoke(ProtobufSerializationHelper.clone(_settings)));
commit = new DelegateCommand(() => request_commit?.Invoke(ProtobufSerializationHelper.clone(_settings)));
}
public event Action<VolSurfaceAdvancedSettings> request_commit;
+18 -15
View File
@@ -121,21 +121,24 @@ namespace mbts.spark {
if (settings == null) {
return null;
}
// return new VolSurfaceAdvancedSettingsEditor {
// DataContext = new VolSurfaceAdvancedSettingsEditorVM(
// VolSurfaceUtilityFunctions.clone_proto(settings.advanced_settings),
// settings.core_settings.vol_surface_name,
// advanced_settings => {
// var current_settings = _get_volsurface_settings();
// if (current_settings == null) BTSMessageBox.Show("Error changing settings", "VS Settings", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error, true, true, true);
// else {
// current_settings.advanced_settings = advanced_settings;
// _model.set_candidate_settings(current_settings);
// }
// }
// )
// };
return null;
var vm = new VolSurfaceAdvancedSettingsEditorVM(
VolSurfaceUtilityFunctions.clone_proto(settings.advanced_settings),
settings.core_settings.vol_surface_name);
vm.request_commit += commit_advanced_settings;
return new VolSurfaceAdvancedSettingsEditor {
DataContext = vm
};
async void commit_advanced_settings(VolSurfaceAdvancedSettings value) {
var current_settings = await _get_volsurface_settings();
if (current_settings == null) {
BTSMessageBox.Show("Error changing settings", "VS Settings");
}
else {
current_settings.advanced_settings = value;
_model.set_candidate_settings(current_settings);
}
}
}
Window? mk_grid_settings_win() {
@@ -9,26 +9,34 @@ namespace mbts.analytics
[ProtoBuf.ProtoContract(Name = "VolSurfaceAdvancedSettings")]
public class VolSurfaceAdvancedSettings
{
public static VolSurfaceAdvancedSettings Default =>
new() {
number_of_cashed_saved_days = double.NaN,
precomputed_theo_handles = new(0),
theo_handle_measure = VolSurfaceEnums.GeneralChangeMeasure.Absolute,
precomputed_slide_handles = new(0),
slide_handle_measure = VolSurfaceEnums.GeneralChangeMeasure.Percentage,
publish_from_client = false,
publish_from_client_user = string.Empty,
strike_normalization_axis = VolSurfaceEnums.VolCurveStrikeAxis.RawStrike,
};
[ProtoBuf.ProtoMember(1)]
[System.ComponentModel.DefaultValue(Double.NaN)]
public double number_of_cashed_saved_days;
[ProtoBuf.ProtoMember(10)]
public List<double> precomputed_theo_handles;
[ProtoBuf.ProtoMember(15)]
[System.ComponentModel.DefaultValue(VolSurfaceEnums.GeneralChangeMeasure.Absolute)]
public VolSurfaceEnums.GeneralChangeMeasure theo_handle_measure;
[ProtoBuf.ProtoMember(20)]
public List<double> precomputed_slide_handles;
[ProtoBuf.ProtoMember(25)]
[System.ComponentModel.DefaultValue(VolSurfaceEnums.GeneralChangeMeasure.Percentage)]
public VolSurfaceEnums.GeneralChangeMeasure slide_handle_measure;
[ProtoBuf.ProtoMember(30)]
//[System.ComponentModel.DefaultValue(false)]
public bool publish_from_client;
[ProtoBuf.ProtoMember(40)]
@@ -38,5 +46,11 @@ namespace mbts.analytics
[ProtoBuf.ProtoMember(50)]
[System.ComponentModel.DefaultValue(VolSurfaceEnums.VolCurveStrikeAxis.RawStrike)]
public VolSurfaceEnums.VolCurveStrikeAxis strike_normalization_axis;
public void ensure_no_nulls() {
precomputed_theo_handles ??= new(0);
precomputed_slide_handles ??= new(0);
publish_from_client_user ??= string.Empty;
}
}
}
@@ -25,7 +25,8 @@ namespace mbts.analytics {
volpath_settings = new(),
safety_limits = new(),
},
polynomial_model_settings = new()
polynomial_model_settings = new(),
advanced_settings = VolSurfaceAdvancedSettings.Default
};
[ProtoMember(10)]
@@ -79,6 +80,8 @@ namespace mbts.analytics {
if (core_settings.time_settings == null) {
core_settings.time_settings = new VolSurfaceTimeSettings();
}
advanced_settings ??= VolSurfaceAdvancedSettings.Default;
advanced_settings.ensure_no_nulls();
return this;
}
}
@@ -40,10 +40,8 @@ namespace mbts.analytics {
/// last computed min/max driver for each exp
private IDictionary<VolSurfaceExpiration, Tuple<double, double>>
_computed_driver_range_map = new Dictionary<VolSurfaceExpiration, Tuple<double, double>>();
private readonly IEnumerable<double> _default_abs_moves =
new double[] { 300, -200, -125, -100, -75, -50, -25, 0, 25, 50, 75, 100, 125, 200, 300 };
private readonly IEnumerable<double> _default_abs_moves = config.Configuration.get<bts.config.VolSurface>().default_abs_moves;
private readonly double _cushion_percentage = 25.0;
public event
Action<IDictionary<VolSurfaceExpiration, IEnumerable<Theos>>, double, double> on_theos =
(o, vt, tt) => { };
@@ -52,7 +50,7 @@ namespace mbts.analytics {
(o, vt, tt) => { };
public VolSurfaceInterpGreeksProducer(int vs_id) {
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}");
Log.Gen.info($"VolSurfaceInterpGreeksProducer.cons vs_id: {vs_id}, _default_abs_moves: {string.Join(", ", _default_abs_moves)}");
_vs_id = vs_id;
}
@@ -72,7 +70,7 @@ namespace mbts.analytics {
}
public void set_vol_data(VolSurfaceInput vs_input) {
Log.Gen.info($"VolSurfaceInterpGreeksProducer.set_vol_data vs_id: {_vs_id}");
Log.Gen.info($"VolSurfaceInterpGreeksProducer.set_vol_data vs_id: {_vs_id} precomputed_theo_handles?: {string.Join(", ", (vs_input?.surface_settings?.advanced_settings?.precomputed_theo_handles).empty_if_null())}");
_vs_input = vs_input != null ? VolSurfaceUtilityFunctions.clone_proto(vs_input) : null;
_compute_and_fire(InputType.Vol);
}
@@ -0,0 +1,58 @@
<Window
x:Class="mbts.ui.shell.VolSurfaceAdvancedSettingsEditor"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:mbts.ui.controls"
xmlns:cv="clr-namespace:mbts.ui.converters"
Title="{Binding vol_surface_name, StringFormat='{}Advanced Settings \&apos;{0}\&apos;'}"
Style="{StaticResource BTSDialog}"
SizeToContent="WidthAndHeight"
TextElement.FontSize="10">
<StackPanel>
<Grid Margin="5" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="label"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GroupBox Grid.ColumnSpan="2" Padding="3">
<GroupBox.Header>
<TextBlock Text="Theos" FontWeight="Bold"/>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="label"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<c:RadioButtonsControl Grid.ColumnSpan="2" Margin="0,0,0,5" ItemsSource="{Binding general_change_measures}" SelectedItem="{Binding theo_handle_measure}" IsEnabled="False"/>
<TextBlock Grid.Row="1" Text="Precomputed Handles" VerticalAlignment="Center"/>
<TextBox
x:Name="PART_Handles"
Validation.ErrorTemplate="{x:Null}"
Grid.Row="1"
Grid.Column="1"
Width="160"
Margin="5,0,0,0"
Text="{Binding precomputed_theo_handles, Converter={x:Static cv:EnumerableDoubleStringConverter.Instance}}" />
</Grid>
</GroupBox>
<TextBlock Visibility="Collapsed" Grid.Row="1" VerticalAlignment="Center" Margin="5,0,0,0" Text="Num Cash Saved Days" />
<c:TickUpDownDouble Visibility="Collapsed" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" Margin="14,5,5,5" Value="{Binding number_of_cash_saved_days}"/>
<TextBlock Grid.Row="2" VerticalAlignment="Center" Margin="5,0,0,0" Text="Strike Normalization Axis"/>
<ComboBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Stretch" Margin="14,5,5,5" ItemsSource="{Binding strike_normalization_axes}" SelectedValue="{Binding selected_strike_normalization_axis}"/>
<CheckBox Grid.Row="3" Margin="7,0,0,0" Content="Publish From Client" IsChecked="{Binding publish_from_client}"/>
</Grid>
<c:DialogSkirtOKCancel OKCommand="{Binding commit}" />
</StackPanel>
</Window>
@@ -0,0 +1,11 @@
namespace mbts.ui.shell {
using System.Windows;
/// <summary>
/// Interaction logic for VolSurfaceAdvancedSettingsEditor.xaml
/// </summary>
partial class VolSurfaceAdvancedSettingsEditor : Window {
public VolSurfaceAdvancedSettingsEditor() =>
InitializeComponent();
}
}
@@ -87,7 +87,7 @@ namespace mbts.spark {
length = _initial_row_count,
};
LadderPriceList _pending_price_list = null;
IComparer<double> _price_comparer;
IComparer<double> _price_comparer = MathUtils.epsilon_comparer.reverse();
double _last_origin_price;
readonly MarketBreakLines _mkt_break_lines;
NativePriceSize _last_trade;
@@ -14,6 +14,9 @@ namespace mbts.spark {
uint _bid_size, _ask_size;
ulong _time;
public BBOVM() =>
_dispose = Disposable.Create(() => Disposable.dispose(ref _md_poll));
public SecurityImmutable? security => _security_md?.security;
public SecurityMD? security_md {
get => _security_md;
@@ -63,7 +63,11 @@ namespace mbts {
if (ticks_per_base.is_int()) {
return true;
}
return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
// The Log10 check accounts for [non-perp] futures at Deribit, which use TickFactors 25/10
// (e.g., 10.0, 12.5, 15.0, etc.). It was added 11/02/2023, when it replaced the
// commented out line below.
// return tick.tick_unit > tick.tick_base && tick.tick_base == 1;
return tick.tick_unit > tick.tick_base && Math.Log10(tick.tick_base).is_int();
}
public static bool is_valid(this RegionalTickFactors regional_tick) =>
@@ -667,7 +671,8 @@ namespace mbts {
if (negate) {
value = -value;
}
var text = core_impl();
var text = tick.tick_unit == 25 && tick.tick_base == 10 ?
deribit_future_impl() : core_impl();
if (!settings.show_zero_integer) {
// Hide the integer if its zero. This could be better optimized,
// but speed shouldn't be an issue, so it doesn't seem worth the time/effort.
@@ -721,6 +726,37 @@ namespace mbts {
return "Overflow";
}
}
string deribit_future_impl() {
try {
// TODO: Replace this hack with a suitable long term solution
//
// The hack: [non-perp] futures at Deribit tick in units of '2.5' (e.g., 10.0, 12.5,
// 15.0, etc.). However, this battle-tested impl is predicated on the tick base being
// a multiple of the tick unit. So, to avoid changing key battle-tested logic, let's
// do the following:
// 1) Round the value to the nearest multiple of '2.5' (the tick size)
// 2) Format the rounded value with normal tick factors (unit 5, base 10),
// (ticks of the sort this method was designed to expect).
var tuf = tick.tick_unit_frac<T>();
var total_ticks = value / tuf;
long total_ticks_l = NumericCastConverter<T, long>.convert(value / tuf);
var rounded_total_ticks = NumericCastConverter<long, T>.convert(total_ticks_l);
var rem = value - (rounded_total_ticks * tuf);
var unit_rem = rem / tuf;
Log.assert(unit_rem <= T.One && unit_rem >= T.Zero);
// Check if unit_rem closer to T.One, or T.Zero. Round accordingly
if (T.One - unit_rem <= unit_rem - T.Zero) {
// round up
rounded_total_ticks++;
}
var rounded_val = tuf * rounded_total_ticks;
return _format_tick(new TickFactors(10, 5), rounded_val, settings);
} catch (OverflowException ex) {
Log.Ui.warn($"Arithmetic overflow exception in format_tick (tick:{tick}, value:{value}):{ex}");
return "Overflow";
}
}
}
static T _round_tick<T>(T tick_unit_frac, T input) where T : IFloatingPointIeee754<T> =>
@@ -134,8 +134,20 @@ namespace bts.data.ticks {
static TickFactors _inflate_to_base(TickFactors tick_factors, double new_base) =>
new TickFactors(new_base, tick_factors.tick_unit * new_base / tick_factors.tick_base);
static bool _check_tick_size(double tick_size) =>
tick_size > 0 && (tick_size <= 0.5 || MathUtils.is_int(tick_size));
static bool _check_tick_size(double tick_size) {
if (!tick_size.is_real_positive()) {
return false;
}
if (tick_size <= 0.5 || tick_size.is_int()) {
return true;
}
// Check if tick size represents quarters (i.e., `tick_size=0.25 * 10^c` for some int c).
// Note: this check is designed specifically to support (non-perp) futures at Deribit
if (Math.Log10(tick_size / 0.25).is_int()) {
return true;
}
return false;
}
struct SpecKey : IEquatable<SpecKey> {
public readonly uint denominator, display_digits;
@@ -209,6 +209,17 @@ namespace mbts.spark {
}
public static decimal round_to_tick(decimal tick_size, decimal value) {
if (tick_size == (decimal)2.5) {
// This is a special case for [non-perp] futures at Deribit, which tick in 25/10
// (unit/base). While this code looks harmless enough, and should work for all ticks in
// theory, let's be extra safe and only apply the change to a specific case (when
// tick_size == 2.5).
// Note: Making the ticks code work for such a wide range of tick factors was no easy
// task, and involved overcoming a plethora of obscure rounding errors along the way.
var n_ticks = value / tick_size;
var simple_round = tick_size * n_ticks;
return simple_round;
}
decimal intpart = (long)value;
decimal floatpart = value % 1;
floatpart = ((int)(floatpart / tick_size)) * tick_size;
@@ -293,6 +304,11 @@ namespace mbts.spark {
if (!tf.low_tick.Equals(tf.high_tick) || !tf.is_valid() || tf.low_tick.is_integer())
return null;
var ticks_per_dollar = (uint)(tf.low_tick.tick_base / tf.low_tick.tick_unit);
// Note: this check was introduced specifically to account for [non-perp] Futures at
// Deribit, which use TickFactors 25/10 (e.g., 10.0, 12.5, 15.0, etc.).
if (ticks_per_dollar == 0) {
return null;
}
// if ticks_per_row is greater than ticks_per_dollar, round to a multiple of ticks_per_dollar
if (ticks_per_row > ticks_per_dollar) {
return (uint)(ticks_per_dollar * Math.Floor((double)(ticks_per_row / ticks_per_dollar)));
@@ -1190,7 +1190,6 @@ namespace System.Collections.Generic {
/// A tuple whose items each represent the sum of their repsective items in
/// <paramref name="source"/>
/// </returns>
[RequiresPreviewFeatures]
public static (T1, T2) Sum<T1, T2>(this IEnumerable<(T1, T2)> source)
where T1 : INumber<T1>
where T2 : INumber<T2> =>
@@ -1203,7 +1202,6 @@ namespace System.Collections.Generic {
/// A tuple whose items each represent the sum of their repsective items in
/// <paramref name="source"/>
/// </returns>
[RequiresPreviewFeatures]
public static (T1, T2) Sum<T1, T2>(this IEnumerable<(T1, T2)> source, T1 seed1, T2 seed2)
where T1 : IAdditionOperators<T1, T1, T1>
where T2 : IAdditionOperators<T2, T2, T2> =>
@@ -1217,7 +1215,6 @@ namespace System.Collections.Generic {
/// A tuple whose items each represent the sum of their repsective items in
/// <paramref name="source"/>
/// </returns>
[RequiresPreviewFeatures]
public static (T1, T2, T3) Sum<T1, T2, T3>(this IEnumerable<(T1, T2, T3)> source)
where T1 : INumber<T1>
where T2 : INumber<T2>
@@ -1231,7 +1228,6 @@ namespace System.Collections.Generic {
/// A tuple whose items each represent the sum of their repsective items in
/// <paramref name="source"/>
/// </returns>
[RequiresPreviewFeatures]
public static (T1, T2, T3) Sum<T1, T2, T3>(
this IEnumerable<(T1, T2, T3)> source, T1 seed1, T2 seed2, T3 seed3)
where T1 : IAdditionOperators<T1, T1, T1>
@@ -1248,7 +1244,6 @@ namespace System.Collections.Generic {
/// A tuple whose items each represent the sum of their repsective items in
/// <paramref name="source"/>
/// </returns>
[RequiresPreviewFeatures]
public static (T1, T2, T3, T4) Sum<T1, T2, T3, T4>(this IEnumerable<(T1, T2, T3, T4)> source)
where T1 : INumber<T1>
where T2 : INumber<T2>
@@ -1263,7 +1258,6 @@ namespace System.Collections.Generic {
/// A tuple whose items each represent the sum of their repsective items in
/// <paramref name="source"/>
/// </returns>
[RequiresPreviewFeatures]
public static (T1, T2, T3, T4) Sum<T1, T2, T3, T4>(
this IEnumerable<(T1, T2, T3, T4)> source, T1 seed1, T2 seed2, T3 seed3, T4 seed4)
where T1 : IAdditionOperators<T1, T1, T1>
-1
View File
@@ -1 +0,0 @@
set(sources files/audit_ilink3.py;files/audit_upload.sh;files/audit_uploader.py;files/cert_secure_logon.txt;files/example.conf;files/futures.coconut.xz;files/onixs_ice/ConnectivityConfiguration.APITest.xml;files/onixs_ice/ConnectivityConfiguration.PerfTest.xml;files/onixs_ice/ConnectivityConfiguration.Production.xml;files/perf_trace/compiled.css;files/perf_trace/index.html;files/perf_trace/jquery-3.3.1.min.js;files/perf_trace/main.js;files/perf_trace/perf_log.sql;files/perf_trace/perf_trace_analysis.sql;files/perf_trace/plotly-latest.min.js;files/small_es.pcap.xz;files/spartan/create_polygon_books.sql;files/spartan/log.sql;files/spartan/md.sql;files/spartan/sim_db.sql)
-1
View File
@@ -1 +0,0 @@
set(sources api/analytics/ExpirationDriver.proto;api/analytics/TimeWeightedInterpolatedDriver.proto;api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/domain/Currency.proto;api/domain/SecurityCriteria.proto;api/domain/SecurityCriteriaClause.proto;api/domain/SecurityExpiration.proto;api/domain/SecurityMetadata.proto;api/domain/SecurityMetadataCollection.proto;api/domain/SecurityMetadataItem.proto;api/domain/SecurityType.proto;api/domain/TickDisplaySettings.proto;api/domain/TickFactors.proto;api/domain/Venue.proto;api/io/URL.proto;api/md/MarketDataSubscriptionParams.proto;api/md/binance/BinanceDepthStreamParams.proto;api/md/binance/BinanceMarketDataSubscriptionParams.proto;api/md/deribit/DeribitMarketDataSubscriptionParams.proto;api/md/ftx/FTXDepthStreamParams.proto;api/md/ftx/FTXMarketDataSubscriptionParams.proto;api/md/glbx/GlbxMarketDataSubscriptionParams.proto;api/md/huobi/HuobiMarketDataSubscriptionParams.proto;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/AnalyticsService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;communincation/websockets/tests/WebsocketSyncClientTests.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/SecurityMetadataCollection.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/algo_types.proto;config/protos/cme_reference_data.proto;config/protos/exchange_sim_cfg.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/market_data.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/ports.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spartan.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/symbols.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/protos/ui.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain/AggressorSide.cpp;domain/NewOrder.cpp;domain/OrderDefinition.cpp;domain/OrderStatus.cpp;domain/OrderUpdate.cpp;domain/Side.cpp;domain/TimeInForce.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;io/QueryParams.cpp;io/URL.cpp;io/http/CurlHandle.cpp;io/http/HTTPClient.cpp;io/http/HTTPHeaders.cpp;io/http/HTTPRequest.cpp;io/http/HTTPResponse.cpp;io/websocket/WebsocketClient.cpp;io/websocket/WebsocketClientConfiguration.cpp;io/websocket/WebsocketError.cpp;io/websocket/WebsocketMessage.cpp;io/websocket/WebsocketSSLClientInternal.cpp;io/websocket/tests/WebsocketClientTests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGatLine truncated
-1
View File
@@ -1 +0,0 @@
set(sources api/analytics/ExpirationDriver.proto;api/analytics/TimeWeightedInterpolatedDriver.proto;api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/domain/Currency.proto;api/domain/SecurityCriteria.proto;api/domain/SecurityCriteriaClause.proto;api/domain/SecurityExpiration.proto;api/domain/SecurityMetadata.proto;api/domain/SecurityMetadataCollection.proto;api/domain/SecurityMetadataItem.proto;api/domain/SecurityType.proto;api/domain/TickDisplayRule.proto;api/domain/TickDisplaySettings.proto;api/domain/TickFactors.proto;api/domain/Venue.proto;api/io/URL.proto;api/md/MarketDataSubscriptionParams.proto;api/md/binance/BinanceDepthStreamParams.proto;api/md/binance/BinanceMarketDataSubscriptionParams.proto;api/md/deribit/DeribitMarketDataSubscriptionParams.proto;api/md/ftx/FTXDepthStreamParams.proto;api/md/ftx/FTXMarketDataSubscriptionParams.proto;api/md/glbx/GlbxMarketDataSubscriptionParams.proto;api/md/huobi/HuobiMarketDataSubscriptionParams.proto;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/service_helpers/PositionUtilities.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/AnalyticsService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/BasePositionService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarginService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/MarketDataService.proto;api/services/MoneyFileService.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/SecurityMetadataCollection.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/keycode.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/position_cfg.conf;config/defaults/span.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/algo_types.proto;config/protos/cme_reference_data.proto;config/protos/exchange_sim_cfg.proto;config/protos/jetfix.proto;config/protos/keycode.proto;config/protos/logging.proto;config/protos/market_data.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/ports.proto;config/protos/position_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/span.proto;config/protos/spartan.proto;config/protos/spreader.proto;config/protos/subprocess.proto;config/protos/symbols.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/protos/ui.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/sod.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;io/QueryParams.cpp;io/URL.cpp;io/http/CurlHandle.cpp;io/http/HTTPClient.cpp;io/http/HTTPHeaders.cpp;io/http/HTTPRequest.cpp;io/http/HTTPResponse.cpp;io/websocket/WebsocketClient.cpp;io/websocket/WebsocketClientConfiguration.cpp;io/websocket/WebsocketError.cpp;io/websocket/WebsocketMessage.cpp;io/websocket/WebsocketSSLClientInternal.cpp;io/websocket/tests/WebsocketClientTests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/CCApiDepthWalker.cpp;md/CCApiListenerAdaptor.cpp;md/CCApiRequestor.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MarketDataLine truncated
+3 -4
View File
@@ -30,6 +30,7 @@ if(WIN32)
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
api/*.cpp
config/*.cpp
core/*.cpp
domain_model/*.cpp
expression/*.cpp
logging/*.cpp
@@ -108,13 +109,11 @@ target_include_directories(
${CMAKE_BINARY_DIR}/src)
if(WIN32)
find_package(fmt CONFIG REQUIRED)
target_link_libraries(
libspark
libcurl
debug
fmtd
optimized
fmt
fmt::fmt-header-only
libtbb
libboost_system
libboost_thread
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <functional>
namespace bts::algorithm {
template <typename _Tp, typename _TAddend, _Tp _Addend>
struct PlusX {
/// Returns the sum
_Tp operator()(const _Tp &__x) const { return __x + _Addend; }
};
} // namespace bts::algorithm
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <string>
namespace bts::algorithm {
class StringCaseConverter {
public:
static void to_upper(const std::string &str, std::string &dest) noexcept;
static std::string to_upper(const std::string &str) noexcept;
static void to_lower(const std::string &str, std::string &dest) noexcept;
static std::string to_lower(const std::string &str) noexcept;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
inline std::string StringCaseConverter::to_upper(const std::string &str) noexcept {
std::string rv{str};
for (auto &c : rv) {
c = std::toupper(c);
}
return rv;
}
inline void StringCaseConverter::to_upper(const std::string &str, std::string &dest) noexcept {
dest = str;
for (auto &c : dest) {
c = std::toupper(c);
}
}
inline std::string StringCaseConverter::to_lower(const std::string &str) noexcept {
std::string rv{str};
for (auto &c : rv) {
c = std::tolower(c);
}
return rv;
}
inline void StringCaseConverter::to_lower(const std::string &str, std::string &dest) noexcept {
dest = str;
for (auto &c : dest) {
c = std::tolower(c);
}
}
} // namespace bts::algorithm
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#define CATCH_AND_LOG(subsys, level, label) \
catch (const std::exception &ex) { \
LOG(subsys, level, label ": {}", std::string{ex.what()}); \
} \
catch (...) { \
LOG(subsys, level, label ": Unknown exception"); \
}
#define CATCH_AND_HANDLE(err_var_name, impl) \
catch (const std::exception &err_var_name##_ex) { \
const auto &err_var_name = err_var_name##_ex.what(); \
impl; \
} \
catch (...) { \
const std::string err_var_name{"Unknown exception"}; \
impl; \
}
// Normalizes 'std::exception' and '...' handlers into single callback
#define CATCH_AND_HANDLE_MSG(impl) \
catch (const std::exception &ex) { \
impl(std::string{ex.what()}); \
} \
catch (...) { \
impl(std::string{"Unknown exception"}); \
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/io/JsonReader.h"
#include "lib/logging/logging.h"
#include <nlohmann/json.hpp>
namespace bts::api::orderman {
PB_ENUM_FROM_JSON(OrderStatus)
PB_ENUM_TO_JSON(OrderStatus)
PB_ENUM_FROM_JSON(TriggerPriceType)
PB_ENUM_TO_JSON(TriggerPriceType)
} // namespace bts::api::orderman
FMT_IMPL(
bts::api::orderman::OrderStatus,
s,
"{}",
bts::api::orderman::OrderStatus_Name(s));
FMT_IMPL(
bts::api::orderman::TriggerPriceType,
t,
"{}",
bts::api::orderman::TriggerPriceType_Name(t));
+2 -2
View File
@@ -141,7 +141,7 @@ void ConfigValueQueue::assign(
if (field->is_repeated()) {
for (vector<Value>::iterator i = v.begin(); i != v.end(); ++i) {
int size = reflection->FieldSize(*message, field);
add<TProto>(
add(
message, reflection, field, (TProto)boost::get<TValue>(*i));
if (index != size) {
for (int i = index; i < size; ++i) {
@@ -182,7 +182,7 @@ void ConfigValueQueue::lexical_assign(
for (vector<Value>::iterator i = v.begin(); i != v.end(); ++i) {
int size = reflection->FieldSize(*message, field);
TProto result = boost::lexical_cast<TProto>(boost::get<TValue>(*i));
add<TProto>(message, reflection, field, result);
add(message, reflection, field, result);
if (index != size) {
for (int i = index; i < size; ++i) {
reflection->SwapElements(message, field, i, size);
@@ -0,0 +1,22 @@
VolSurface {
value {
default_abs_moves: -600
default_abs_moves: -300
default_abs_moves: -200
default_abs_moves: -125
default_abs_moves: -100
default_abs_moves: -75
default_abs_moves: -50
default_abs_moves: -25
default_abs_moves: 0
default_abs_moves: 25
default_abs_moves: 50
default_abs_moves: 75
default_abs_moves: 100
default_abs_moves: 125
default_abs_moves: 200
default_abs_moves: 300
default_abs_moves: 600
}
}
@@ -0,0 +1,6 @@
syntax = 'proto2';
package bts.config;
message VolSurface {
repeated double default_abs_moves = 1;
}
-15
View File
@@ -1,15 +0,0 @@
#pragma once
#define BITWISE_OP(enum_type, numeric_type, op) \
enum_type operator op(enum_type lhs, enum_type rhs) noexcept { \
return static_cast<enum_type>(static_cast<numeric_type>(lhs) \
op static_cast<numeric_type>(rhs)); \
}
#define BITWISE_OPS(enum_type, numeric_type) \
BITWISE_OP(enum_type, numeric_type, |) \
BITWISE_OP(enum_type, numeric_type, &) \
BITWISE_OP(enum_type, numeric_type, ^)
#define HAS_FLAG(enum_value, flag) (enum_value & flag) == flag
#define STRIP_FLAG(enum_value, flag) (enum_value | flag) ^ flag
+154
View File
@@ -0,0 +1,154 @@
#pragma once
#define ENUM_BASIC_MEMBERS(type) \
Value value; \
\
type() = default; \
/*NOLINTNEXTLINE*/ \
constexpr type(Value value) noexcept : value(value) {} \
\
type(const type &) noexcept = default; \
type(type &&) noexcept = default; \
type &operator=(const type &) noexcept = default; \
type &operator=(type &&) noexcept = default; \
\
~type() = default; \
\
static constexpr bool parse(const char *name, type *value) noexcept; \
static bool parse(const std::string &name, type *value) noexcept; \
\
[[nodiscard]] constexpr const char *name() const noexcept;
#define ENUM_VALUE_TO_NAME_CASE(type, v) \
case type::v: \
return #v;
#define ENUM_NAME_TO_VALUE_CLAUSE(type, v) \
if (name == #v) { \
*value = type::v; \
return true; \
}
#define ENUM_NAMEC_TO_VALUE_CLAUSE(type, v) \
if (std::string_view(name) == #v) { \
*value = type::v; \
return true; \
}
// clang-format off
#define ENUM_NAME_DEF(type, for_all) \
constexpr const char *type::name() const noexcept { \
switch (value) { \
for_all(ENUM_VALUE_TO_NAME_CASE) \
default: return "ENUM_UNKNOWN"; \
} \
}
// clang-format on
#define ENUM_PARSE_DEF(type, for_all) \
constexpr bool type::parse(const char *name, type *value) noexcept { \
for_all(ENUM_NAMEC_TO_VALUE_CLAUSE) return false; \
} \
inline bool type::parse(const std::string &name, type *value) noexcept { \
for_all(ENUM_NAME_TO_VALUE_CLAUSE) return false; \
}
#define ENUM_EQUALITY_OPS(type) \
constexpr bool operator==(type lhs, type rhs) noexcept { \
return lhs.value == rhs.value; \
} \
constexpr bool operator!=(type lhs, type rhs) noexcept { \
return lhs.value != rhs.value; \
} \
constexpr bool operator==(type lhs, type::Value rhs) noexcept { \
return lhs.value == rhs; \
} \
constexpr bool operator!=(type lhs, type::Value rhs) noexcept { \
return lhs.value != rhs; \
}
#define ENUM_OUTPUT_OPS(type) \
inline std::ostream &operator<<(std::ostream &out, type value) noexcept { \
out << value.name(); \
return out; \
} \
\
inline std::ostream &operator<<( \
std::ostream &out, type::Value value) noexcept { \
out << type{value}.name(); \
return out; \
}
#define ENUM_FROM_JSON(type) \
inline void from_json(const nlohmann::json &j, type &value) { \
std::string value_str; \
j.get_to(value_str); \
type::parse(value_str, &value); \
}
#define ENUM_TO_JSON(type) \
inline void to_json(nlohmann::json &j, const type &value) { \
j = value.name(); \
}
#define ENUM_DECLARE_VALUE(t, x) x,
#define ENUM_CLASS_DEF(type, for_all) \
class type { \
public: \
enum Value { for_all(ENUM_DECLARE_VALUE) }; \
\
ENUM_BASIC_MEMBERS(type) \
}; \
\
ENUM_OUTPUT_OPS(type) \
ENUM_NAME_DEF(type, for_all) \
ENUM_PARSE_DEF(type, for_all) \
ENUM_FROM_JSON(type) \
ENUM_TO_JSON(type) \
ENUM_EQUALITY_OPS(type)
#define ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, op) \
constexpr enum_type operator op(enum_type lhs, enum_type rhs) noexcept { \
return static_cast<enum_type>(static_cast<numeric_type>(lhs) \
op static_cast<numeric_type>(rhs)); \
}
#define ENUM_BITWISE_COMPARISON_OPS(enum_type, numeric_type) \
ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, |) \
ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, &) \
ENUM_BITWISE_COMPARISON_OP(enum_type, numeric_type, ^)
#define ENUM_BITWISE_ASSIGNMENT_OP(enum_type, op) \
inline enum_type &operator op##=(enum_type &lhs, enum_type rhs) noexcept { \
lhs = lhs op rhs; \
return lhs; \
}
#define ENUM_BITWISE_ASSIGNMENT_OPS(enum_type) \
ENUM_BITWISE_ASSIGNMENT_OP(enum_type, |) \
ENUM_BITWISE_ASSIGNMENT_OP(enum_type, &) \
ENUM_BITWISE_ASSIGNMENT_OP(enum_type, ^)
#define ENUM_BITWISE_OPS(enum_type, numeric_type) \
ENUM_BITWISE_COMPARISON_OPS(enum_type, numeric_type) \
ENUM_BITWISE_ASSIGNMENT_OPS(enum_type)
#define ENUM_HAS_FLAG(enum_value, flag) (enum_value & flag) == flag
#define ENUM_STRIP_FLAG(enum_value, flag) (enum_value | flag) ^ flag
#define PB_ENUM_FROM_JSON(enum_type) \
inline void from_json(const nlohmann::json &j, enum_type &value) { \
io::JsonReader jr{&j}; \
std::string name; \
bool success = jr.read(name) && enum_type##_Parse(name, &value); \
if (!success) { \
const auto j_str = j.dump(2); \
LOG(Gen, Diag, "Failed to parse json to " #enum_type ":\n{}", j_str); \
} \
}
#define PB_ENUM_TO_JSON(enum_type) \
inline void to_json(nlohmann::json &j, const enum_type &value) { \
j = enum_type##_Name(value); \
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <fmt/core.h>
#include <fmt/format.h>
#ifndef _WIN32
#include <range/v3/view.hpp>
#endif
#include <vector>
#define FMT(template_str, ...) fmt::format(template_str, __VA_ARGS__)
#define FMT_JOIN(range, delimiter) \
fmt::to_string(fmt::join(range.begin(), range.end(), delimiter))
namespace bts {
template <class Range>
std::string _fmt_join(Range range, const char *delimiter) {
return fmt::join(range.begin(), range.end(), delimiter);
}
#ifndef _WIN32
template <class Range, class Op>
std::string _fmt_join_fn(Range range, const char *delimiter, Op op) {
return fmt::to_string(
fmt::join(range | ranges::views::transform(op), delimiter));
}
#endif
} // namespace bts
#ifndef _WIN32
#define FMT_JOIN_FN(range, delimiter, fn) \
bts::_fmt_join_fn(range, delimiter, fn)
#endif
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <fmt/format.h>
#define FMT_IMPL(Type, var_name, ...) \
template <> \
struct fmt::formatter<Type> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const Type &var_name, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
return fmt::format_to(ctx.out(), __VA_ARGS__); \
} \
}
#define FMT_IMPL_T(Type, var_name, ...) \
struct fmt::formatter<Type> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const Type &var_name, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
return fmt::format_to(ctx.out(), __VA_ARGS__); \
} \
}
#define FMT_IMPL_OSTREAM(T) \
template <> \
struct fmt::formatter<T> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const T &t, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
std::stringstream ss; \
ss << t; \
return fmt::format_to(ctx.out(), "{}", ss.str()); \
} \
}
#define FMT_IMPL_OSTREAM_T(T, ...) \
struct fmt::formatter<T<__VA_ARGS__>> { \
constexpr auto parse(fmt::format_parse_context &ctx) \
-> decltype(ctx.end()) { \
return ctx.end(); \
} \
\
template <typename FormatContext> \
auto format(const T<__VA_ARGS__> &t, FormatContext &ctx) const \
-> decltype(ctx.out()) { \
std::stringstream ss; \
ss << t; \
return fmt::format_to(ctx.out(), "{}", ss.str()); \
} \
}
+2 -2
View File
@@ -132,7 +132,7 @@ std::size_t HashPhaser::auto_phase_range(Iter first, Iter last) {
std::uint16_t el_count =
num_vals > (std::numeric_limits<std::uint16_t>::max)()
? max_els
: std::max(static_cast<std::uint16_t>(num_vals), max_els);
: (static_cast<std::uint16_t>(num_vals) > max_els ? static_cast<std::uint16_t>(num_vals) : max_els);
auto bits_per_hash = (sizeof(std::size_t) * __CHAR_BIT__) / el_count;
std::size_t phase = 1U << (bits_per_hash - 1);
std::size_t composite_hash = 0;
@@ -157,7 +157,7 @@ std::size_t HashPhaser::auto_phase_range(Iter first, Iter last, Hash hash) {
std::uint16_t el_count =
num_vals > (std::numeric_limits<std::uint16_t>::max)()
? max_els
: std::max(static_cast<std::uint16_t>(num_vals), max_els);
: (static_cast<std::uint16_t>(num_vals) > max_els ? static_cast<std::uint16_t>(num_vals) : max_els);
auto bits_per_hash = (sizeof(std::size_t) * __CHAR_BIT__) / el_count;
std::size_t phase = 1U << (bits_per_hash - 1);
std::size_t composite_hash = 0;
+26
View File
@@ -0,0 +1,26 @@
#include "TimeSpan.h"
#include <boost/date_time/posix_time/time_formatters.hpp>
#include <boost/date_time/posix_time/time_parsers.hpp>
namespace bts::core {
TimeSpan TimeSpan::from_iso_string(const std::string &iso_string) {
const auto pdur = boost::posix_time::duration_from_string(iso_string);
return from_posix_duration(pdur);
}
std::string TimeSpan::to_iso_string() const {
const auto pdur = to_posix_duration();
return boost::posix_time::to_iso_string(pdur);
}
std::ostream &operator<<(std::ostream &out, const TimeSpan &ts) noexcept {
out << ts.to_iso_string();
return out;
}
TimeSpan TimeSpan::from_posix_duration(boost::posix_time::time_duration d) {
return TimeSpan{d.total_nanoseconds()};
}
} // namespace bts::core
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/conversion.hpp>
#include <boost/date_time/posix_time/posix_time_config.hpp>
#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 <string>
namespace bts::core {
/**
* @brief Represents a duration of time
*/
class TimeSpan {
public:
constexpr TimeSpan() = default;
explicit constexpr TimeSpan(std::int64_t ticks);
static constexpr TimeSpan zero();
static constexpr TimeSpan from_nanos(std::int64_t value);
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 TimeSpan from_posix_duration(boost::posix_time::time_duration t);
static TimeSpan from_iso_string(const std::string &iso_string);
constexpr std::int64_t nanos() const;
constexpr std::int64_t micros() const;
constexpr std::int64_t millis() const;
constexpr std::int64_t seconds() const;
constexpr double total_millis() const;
constexpr double total_seconds() const;
constexpr boost::posix_time::time_duration to_posix_duration() const;
std::string to_iso_string() const;
private:
// nanos
std::int64_t ticks_{};
};
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;
std::ostream &operator<<(std::ostream &out, const TimeSpan &ts) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr TimeSpan::TimeSpan(std::int64_t ticks) : ticks_(ticks) {}
constexpr TimeSpan TimeSpan::zero() { return TimeSpan{0}; }
constexpr TimeSpan TimeSpan::from_nanos(std::int64_t value) {
return TimeSpan{value};
}
constexpr TimeSpan TimeSpan::from_micros(std::int64_t value) {
constexpr const auto nanos_per_micro = 1'000;
return TimeSpan{value * nanos_per_micro};
}
constexpr TimeSpan TimeSpan::from_millis(std::int64_t value) {
constexpr const auto nanos_per_milli = 1'000'000;
return TimeSpan{value * nanos_per_milli};
}
constexpr TimeSpan TimeSpan::from_seconds(std::int64_t value) {
constexpr const auto nanos_per_second = 1'000'000'000;
return TimeSpan{value * nanos_per_second};
}
constexpr std::int64_t TimeSpan::nanos() const { return ticks_; }
constexpr std::int64_t TimeSpan::micros() const { return ticks_ / 1'000; }
constexpr std::int64_t TimeSpan::millis() const { return ticks_ / 1'000'000; }
constexpr std::int64_t TimeSpan::seconds() const {
return ticks_ / 1'000'000'000;
}
constexpr double TimeSpan::total_millis() const {
return static_cast<double>(ticks_) / 1000'000;
}
constexpr double TimeSpan::total_seconds() const {
return static_cast<double>(ticks_) / 1000'000'000;
}
constexpr boost::posix_time::time_duration TimeSpan::to_posix_duration() const {
return boost::date_time::subsecond_duration<
boost::posix_time::time_duration, 1000000000>{nanos()};
}
constexpr bool operator==(TimeSpan lhs, TimeSpan rhs) noexcept {
return lhs.nanos() == rhs.nanos();
}
constexpr bool operator!=(TimeSpan lhs, TimeSpan rhs) noexcept {
return lhs.nanos() != rhs.nanos();
}
constexpr bool operator<(TimeSpan lhs, TimeSpan rhs) noexcept {
return lhs.nanos() < rhs.nanos();
}
constexpr bool operator>(TimeSpan lhs, TimeSpan rhs) noexcept {
return lhs.nanos() > rhs.nanos();
}
} // namespace bts::core
FMT_IMPL(bts::core::TimeSpan, t, "{}", t.to_iso_string());
+58
View File
@@ -0,0 +1,58 @@
#include "TimeStamp.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/time_formatters.hpp>
namespace bts::core {
using std::uint32_t;
static bool is_valid_ymd(uint32_t year, uint32_t month, uint32_t day) {
return year >= 1970 && month >= 1 && month <= 12 && day >= 1 && day <= 31;
}
TimeStamp::TimeStamp(uint32_t year, uint32_t month, uint32_t day) {
bts_assert_msgf(
is_valid_ymd(year, month, day), "Invalid timestamp {}/{}/{} (y/m/d)",
year, month, day);
std::tm date = {};
date.tm_year = year - 1900;
date.tm_mon = month - 1;
date.tm_mday = day;
auto time = std::chrono::system_clock::from_time_t(mktime(&date));
auto duration = time.time_since_epoch();
value_ =
std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
}
bool TimeStamp::from_ymd(
std::uint32_t year, std::uint32_t month, std::uint32_t day, TimeStamp *result) {
if (!is_valid_ymd(year, month, day)) {
return false;
}
*result = TimeStamp{year, month, day};
return true;
}
TimeStamp TimeStamp::from_iso_string(const std::string &iso_string) {
auto ptime = datetime::parse_iso_date_time_string(iso_string);
return from_ptime(ptime);
}
std::string TimeStamp::to_iso_string() const {
return boost::posix_time::to_iso_string(to_ptime());
}
std::ostream &operator<<(std::ostream &out, const TimeStamp &ts) noexcept {
out << ts.to_iso_string();
return out;
}
TimeStamp TimeStamp::from_ptime(boost::posix_time::ptime t) {
// time_t is Unix time: number of seconds since epoch Jan 1, 1970
auto epoch_pt =
t - boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1));
auto epoch_nanos = epoch_pt.total_nanoseconds();
return TimeStamp{static_cast<std::uint64_t>(epoch_nanos)};
}
} // namespace bts::core
+186 -2
View File
@@ -1,27 +1,211 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/core/TimeSpan.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/conversion.hpp>
#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>
#include <string>
namespace bts::core {
// I can send this whole class later but its a nice wrapper around the linux
// posix time
/**
* @brief Represents time as nano-seconds since the unix epoch (Jan 1, 1970)
*/
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);
static constexpr TimeStamp min();
static constexpr TimeStamp max();
static constexpr TimeStamp from_int64(std::int64_t value);
static constexpr TimeStamp from_nanos(std::int64_t value);
static constexpr TimeStamp from_micros(std::int64_t value);
static constexpr TimeStamp from_millis(std::int64_t value);
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);
static bool from_ymd(
std::uint32_t year,
std::uint32_t month,
std::uint32_t day,
TimeStamp *result);
constexpr std::uint64_t nanos() const;
constexpr std::uint64_t micros() const;
constexpr std::uint64_t millis() const;
constexpr std::uint64_t seconds() const;
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 boost::posix_time::ptime to_ptime() const;
std::string to_iso_string() const;
// current epoch ns
static uint64_t now_ns() {
#ifndef _WIN32
return datetime::unix_nano_now();
#else
auto now = std::chrono::system_clock::now();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
now.time_since_epoch())
.count();
return ns;
#endif
}
static TimeStamp now() { return from_nanos(now_ns()); }
private:
// posix time
std::uint64_t value_{};
};
constexpr bool operator==(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr bool operator!=(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr bool operator<(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr bool operator<=(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr bool operator>(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr bool operator>=(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr TimeSpan operator-(TimeStamp lhs, TimeStamp rhs) noexcept;
constexpr TimeStamp operator-(TimeStamp lhs, TimeSpan rhs) noexcept;
constexpr TimeStamp operator+(TimeStamp lhs, TimeSpan rhs) noexcept;
std::ostream &operator<<(std::ostream &out, const TimeStamp &ts) noexcept;
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
constexpr TimeStamp::TimeStamp(std::uint64_t value) : value_(value) {}
constexpr TimeStamp TimeStamp::min() { return TimeStamp{0}; }
constexpr TimeStamp TimeStamp::max() {
return TimeStamp{std::numeric_limits<std::uint64_t>::max()};
}
constexpr TimeStamp TimeStamp::from_int64(std::int64_t value) {
bts_assert(value >= 0);
return TimeStamp{static_cast<std::uint64_t>(value)};
}
constexpr TimeStamp TimeStamp::from_nanos(std::int64_t value) {
bts_assert(value >= 0);
return TimeStamp{static_cast<std::uint64_t>(value)};
}
constexpr TimeStamp TimeStamp::from_micros(std::int64_t value) {
bts_assert(value >= 0);
constexpr const auto nanos_per_micro = 1'000;
return TimeStamp{static_cast<std::uint64_t>(value * nanos_per_micro)};
}
constexpr TimeStamp TimeStamp::from_millis(std::int64_t value) {
bts_assert(value >= 0);
constexpr const auto nanos_per_milli = 1'000'000;
return TimeStamp{static_cast<std::uint64_t>(value * nanos_per_milli)};
}
constexpr TimeStamp TimeStamp::from_seconds(std::int64_t value) {
bts_assert(value >= 0);
constexpr const auto nanos_per_second = 1'000'000'000;
return TimeStamp{static_cast<std::uint64_t>(value * nanos_per_second)};
}
constexpr std::uint64_t TimeStamp::nanos() const { return value_; }
constexpr std::uint64_t TimeStamp::micros() const { return value_ / 1'000; }
constexpr std::uint64_t TimeStamp::millis() const { return value_ / 1'000'000; }
constexpr std::uint64_t TimeStamp::seconds() const {
return value_ / 1'000'000'000;
}
constexpr double TimeStamp::total_millis() const {
return static_cast<double>(value_) / 1000'000;
}
constexpr double TimeStamp::total_seconds() const {
return static_cast<double>(value_) / 1000'000'000;
}
constexpr std::int64_t TimeStamp::to_int64() {
bts_assert(
value_ <=
static_cast<std::uint64_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) {
std::uint64_t epoch_secs = value_ / (1000 * 1000 * 1000);
time_t ts_tt(epoch_secs);
struct tm *ptm = gmtime(&ts_tt);
year = ptm->tm_year + 1900;
month = ptm->tm_mon + 1;
day = ptm->tm_mday;
}
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;
// time_t is Unix time: number of seconds since epoch Jan 1, 1970
std::time_t ts = value_ / nanos_per_sec;
const auto nanos_rem = value_ % nanos_per_sec;
return boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1)) +
boost::posix_time::seconds(static_cast<long>(ts)) +
boost::posix_time::microseconds(nanos_rem / nanos_per_micro);
}
constexpr bool operator==(TimeStamp lhs, TimeStamp rhs) noexcept {
return lhs.nanos() == rhs.nanos();
}
constexpr bool operator!=(TimeStamp lhs, TimeStamp rhs) noexcept {
return lhs.nanos() != rhs.nanos();
}
constexpr bool operator<(TimeStamp lhs, TimeStamp rhs) noexcept {
return lhs.nanos() < rhs.nanos();
}
constexpr bool operator<=(TimeStamp lhs, TimeStamp rhs) noexcept {
return lhs.nanos() <= rhs.nanos();
}
constexpr bool operator>(TimeStamp lhs, TimeStamp rhs) noexcept {
return lhs.nanos() > rhs.nanos();
}
constexpr bool operator>=(TimeStamp lhs, TimeStamp rhs) noexcept {
return lhs.nanos() >= rhs.nanos();
}
constexpr TimeSpan operator-(TimeStamp lhs, TimeStamp rhs) noexcept {
return TimeSpan::from_nanos(lhs.nanos() - rhs.nanos());
}
constexpr TimeStamp operator-(TimeStamp lhs, TimeSpan rhs) noexcept {
return TimeStamp{lhs.nanos() - rhs.nanos()};
}
constexpr TimeStamp operator+(TimeStamp lhs, TimeSpan rhs) noexcept {
return TimeStamp{lhs.nanos() + rhs.nanos()};
}
} // namespace bts::core
FMT_IMPL(bts::core::TimeStamp, t, "{}", t.to_iso_string());
+6
View File
@@ -1,10 +1,14 @@
#pragma once
#include "Side.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/utils/bts_assert.h"
#include <cmath>
#include <cstdint>
#include <limits>
#include <ostream>
#include <string>
#if bts_assert_msg
#define __ASSERT_MSG(EXP, msg) bts_assert_msg(EXP, msg)
@@ -490,6 +494,8 @@ inline std::ostream &operator<<(std::ostream &out, Price p) noexcept {
} // namespace bts::domain
FMT_IMPL(bts::domain::Price, p, "{}", p.to_double());
namespace std { // NOLINT
constexpr size_t hash<bts::domain::Price>::operator()(
+6
View File
@@ -1,5 +1,9 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/logging/logging.h"
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
namespace bts::domain {
@@ -110,3 +114,5 @@ QTY_UINT32_BIN_REF_OP(%=)
#undef QTY_UINT32_BIN_REF_OP
} // namespace bts::domain
FMT_IMPL(bts::domain::Quantity, q, "{}", *q);
+4
View File
@@ -1,4 +1,6 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/utils/ValueWrapper.h"
#include <cstdint>
@@ -7,3 +9,5 @@ namespace bts::domain {
using SecurityID = utils::ValueWrapper<struct SecuirtyIDTag, std::uint64_t>;
} // namespace bts::domain
FMT_IMPL(bts::domain::SecurityID, id, "{}", *id);
+7 -4
View File
@@ -1,12 +1,15 @@
#pragma once
#include "lib/utils/datetime.h"
#include <cmath>
#include <memory>
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/domain_model/Side.h"
#include "lib/domain_model/Venue.h"
#include "lib/domain_model/common.h"
#include "lib/utils/bts_assert.h"
#include "lib/utils/datetime.h"
#include <cmath>
#include <memory>
namespace boost {
namespace gregorian {
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#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 <nlohmann/json.hpp>
namespace bts::api {
PB_ENUM_FROM_JSON(Side)
PB_ENUM_TO_JSON(Side)
} // namespace bts::api
FMT_IMPL(bts::api::Side, s, "{}", bts::api::Side_Name(s));
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#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 <nlohmann/json.hpp>
namespace bts::api {
PB_ENUM_FROM_JSON(Venue)
PB_ENUM_TO_JSON(Venue)
} // namespace bts::api
FMT_IMPL(bts::api::Venue, v, "{}", bts::api::Venue_Name(v));
+4
View File
@@ -6,6 +6,7 @@
#include <cstdint>
#include <string>
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/utils/ValueWrapper.h"
@@ -102,3 +103,6 @@ using Price = utils::ValueWrapper<struct OrderPriceTag, double>;
using Quantity = CQuantity;
using UserLogin = utils::ValueWrapper<struct UserLoginTag, std::string>;
} // namespace bts
FMT_IMPL(bts::Price, p, "{}", p.value());
FMT_IMPL(bts::Quantity, q, "{}", q.value());
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <nlohmann/json.hpp>
#include <sstream>
#include <string>
namespace bts::io {
class JsonDumper {
public:
template <class T>
static std::string dump(const T &value, int indentation = 2);
template <class Iter>
static std::string dump(
Iter first,
Iter last,
int indentation = 2,
const char *separator = ",\n");
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
template <class T>
std::string JsonDumper::dump(const T &value, int indentation) {
nlohmann::json j;
to_json(j, value);
return j.dump(indentation);
}
template <class Iter>
std::string JsonDumper::dump(
Iter first, Iter last, int indentation, const char *separator) {
if (first == last) {
return "";
}
std::stringstream str;
auto it = first;
str << "[" << dump(*it, indentation);
while (++it != last) {
str << separator << dump(*it, indentation);
}
str << "]";
return str.str();
}
} // namespace bts::io
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#define _TO_JSON_FIELD(field) j[#field] = t.field;
#define _FROM_JSON_FIELD(field) bts::io::JsonReader{&j, #field}.read(t.field);
#define SPARK_TO_JSON_IMPL(type, for_each) \
inline void to_json(nlohmann::json &j, const type &t) { \
FOR_EACH(_TO_JSON_FIELD) \
}
#define SPARK_FROM_JSON_IMPL(type, for_each) \
inline void from_json(const nlohmann::json &j, type &t) { \
FOR_EACH(_FROM_JSON_FIELD) \
}
#define OSTREAM_JSON_IMPL(type) \
inline std::ostream &operator<<(std::ostream &out, const type &t) { \
nlohmann::json j; \
to_json(j, t); \
out << j.dump(2); \
return out; \
}
+520
View File
@@ -0,0 +1,520 @@
#include "JsonReader.h"
#include "lib/algorithm/TryCatch.h"
#include "lib/core/TimeStamp.h"
#include "lib/logging/logging.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/ptime.hpp>
#include <exception>
#include <limits>
#include <string>
namespace bts::io {
using core::TimeStamp;
using nlohmann::json;
using std::int32_t;
using std::int64_t;
using std::numeric_limits;
using std::stoull;
using std::string;
using std::uint32_t;
using std::uint64_t;
// 'read' fn bodies should be wrapped between BEGIN/END_READ_JSON tags.
// These macros abstract out repetative exception handling code (and a nullptr check).
#define BEGIN_READ_JSON \
if (json_ == nullptr) { \
return false; \
} \
try
#define END_READ_JSON(type_name) \
catch (const std::exception &e) { \
LOG(Gen, Diag, "Failed to parse json to '{}'. Exception: {}", \
std::string{type_name}, std::string{e.what()}); \
} \
catch (...) { \
bts_assert_msgf( \
false, "Failed to parse json to '{}'. Exception: unknown", \
std::string{type_name}); \
} \
return false;
// The idea here is we parse numbers to wide types (either int64, uint64, or double),
// then cast that value to the (potentially narrower) output type. This macro evaluates
// to true if 'wide_val' is within the bounds of the result type (otherwise false).
#define NUM_WITHIN_BOUNDS(wide_val, result_t) \
(wide_val >= \
static_cast<decltype(wide_val)>(numeric_limits<result_t>::min()) && \
wide_val <= \
static_cast<decltype(wide_val)>(numeric_limits<result_t>::max()))
static const nlohmann::json *get_field_json(
const nlohmann::json *json, const std::string &name) {
if (!json || !json->is_object()) {
return nullptr;
}
auto it = json->find(name);
if (it == json->end()) {
return nullptr;
}
return &it.value();
}
JsonReader::JsonReader(const nlohmann::json *json) : json_(json) {}
JsonReader::JsonReader(const nlohmann::json *json, const std::string &name)
: json_(get_field_json(json, name)) {}
bool JsonReader::try_parse(
const std::string &str, nlohmann::json &result, std::string *err) {
try {
result = nlohmann::json::parse(str);
return true;
}
CATCH_AND_HANDLE_MSG([err](const auto &msg) {
if (err != nullptr) {
*err = msg;
}
})
return false;
}
bool JsonReader::read(const string &name, string &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, int32_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, uint32_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, int64_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, uint64_t &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, float &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, double &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(const string &name, TimeStamp &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read_epoch_nanos(const string &name, TimeStamp &value) const {
return JsonReader{json_, name}.read_epoch_nanos(value);
}
bool JsonReader::read_epoch_micros(const string &name, TimeStamp &value) const {
return JsonReader{json_, name}.read_epoch_micros(value);
}
bool JsonReader::read_epoch_millis(const string &name, TimeStamp &value) const {
return JsonReader{json_, name}.read_epoch_millis(value);
}
bool JsonReader::read(const string &name, nlohmann::json &result) const {
return JsonReader{json_, name}.read(result);
}
bool JsonReader::read(int32_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
result = static_cast<int32_t>(d);
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to int32 ({})",
d, result);
}
return true;
}
case nlohmann::json::value_t::number_integer: {
const auto il = json_->get<int64_t>();
if (!NUM_WITHIN_BOUNDS(il, int32_t)) {
LOG(Gen, Error,
"Failed to convert field of type 'number_integer' ({}) to "
"uint32: value is negative.",
il);
}
result = static_cast<int32_t>(il);
return true;
}
case nlohmann::json::value_t::number_unsigned: {
const auto ul = json_->get<uint64_t>();
if (ul > static_cast<uint64_t>(numeric_limits<int32_t>::max())) {
LOG(Gen, Error,
"Failed to convert field of type 'number_unsigned' ({}) to "
"int32: value is too large.",
ul);
return false;
}
result = static_cast<int32_t>(ul);
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stoi(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("int32")
}
bool JsonReader::read(uint32_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
result = static_cast<uint32_t>(d);
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to uint32 ({})",
d, result);
}
return true;
}
case nlohmann::json::value_t::number_integer: {
const auto i = json_->get<int64_t>();
if (!NUM_WITHIN_BOUNDS(i, uint32_t)) {
LOG(Gen, Error,
"Failed to convert field of type 'number_integer' ({}) to "
"uint32: value is negative.",
i);
return false;
}
result = static_cast<uint32_t>(i);
return true;
}
case nlohmann::json::value_t::number_unsigned: {
const auto ul = json_->get<uint64_t>();
if (ul > static_cast<uint64_t>(numeric_limits<int32_t>::max())) {
LOG(Gen, Error,
"Failed to convert field of type 'number_unsigned' ({}) to "
"int32: value is too large.",
ul);
return false;
}
result = static_cast<int32_t>(ul);
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stoul(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("uint32")
}
bool JsonReader::read(int64_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to int64 ({})",
d, result);
}
result = static_cast<int64_t>(d);
return true;
}
case nlohmann::json::value_t::number_integer: {
result = json_->get<int64_t>();
return true;
}
case nlohmann::json::value_t::number_unsigned: {
const auto ul = json_->get<uint64_t>();
if (NUM_WITHIN_BOUNDS(ul, int64_t)) {
LOG(Gen, Error,
"Potential loss of data converting field of type "
"'number_unsigned' ({}) to int64 ({})",
ul, result);
return false;
}
result = static_cast<int64_t>(ul);
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stol(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("int64")
}
bool JsonReader::read(uint64_t &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float: {
const auto d = json_->get<double>();
if (result != d) {
LOG(Gen, Warn,
"Potential loss of data converting field of type "
"'number_float' ({}) to uint64 ({})",
d, result);
}
result = static_cast<uint64_t>(d);
return true;
}
case nlohmann::json::value_t::number_integer: {
const auto il = json_->get<int64_t>();
if (NUM_WITHIN_BOUNDS(il, uint64_t)) {
LOG(Gen, Error,
"Potential loss of data converting field of type "
"'number_integer' ({}) to uint64 ({})",
il, result);
return false;
}
result = static_cast<uint64_t>(il);
return true;
}
case nlohmann::json::value_t::number_unsigned: {
result = json_->get<uint64_t>();
return true;
}
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stoul(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("uint64")
}
bool JsonReader::read(float &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float:
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
json_->get_to(result);
return true;
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stof(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("float")
}
bool JsonReader::read(double &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::number_float:
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
json_->get_to(result);
return true;
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
if (!str.empty()) {
result = std::stod(str);
return true;
}
break;
}
default:
return false;
}
}
END_READ_JSON("double")
}
bool JsonReader::read(string &result) const {
BEGIN_READ_JSON {
json_->get_to(result);
return true;
}
END_READ_JSON("string")
}
bool JsonReader::read(nlohmann::json &result) const {
BEGIN_READ_JSON {
json_->get_to(result);
return true;
}
END_READ_JSON("json-object")
}
bool JsonReader::read_flat(std::vector<nlohmann::json> &result) const {
if (json_ == nullptr) {
return false;
}
switch (json_->type()) {
case json::value_t::array:
return std::all_of(json_->begin(), json_->end(), [&](const auto &j) {
return JsonReader{&j}.read_flat(result);
});
case json::value_t::null:
return false;
default:
read(result.emplace_back());
return true;
}
}
bool JsonReader::parse_time_stamp_(const string &str, TimeStamp &value) const {
return parse_iso_time_stamp_(str, value) ||
parse_epoch_nanos_time_stamp_(str, value);
}
bool JsonReader::parse_iso_time_stamp_(
const string &str, TimeStamp &value) const {
value = TimeStamp::from_iso_string(str);
return true;
}
bool JsonReader::parse_epoch_nanos_time_stamp_(
const string &str, TimeStamp &value) const {
uint64_t nanos = stoull(str);
value = TimeStamp{nanos};
return true;
}
bool JsonReader::read(TimeStamp &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::null:
case nlohmann::json::value_t::object:
case nlohmann::json::value_t::array:
case nlohmann::json::value_t::boolean:
case nlohmann::json::value_t::binary:
case nlohmann::json::value_t::discarded:
return false;
case nlohmann::json::value_t::string: {
const auto str = json_->get<string>();
return parse_iso_time_stamp_(str, result) ||
parse_epoch_nanos_time_stamp_(str, result);
}
case nlohmann::json::value_t::number_integer:
case nlohmann::json::value_t::number_unsigned:
case nlohmann::json::value_t::number_float:
// Maybe we could assume seconds here, idk though...when in doubt, prob
// better to be cautious than cavalier
return false;
}
}
END_READ_JSON("TimeStamp")
}
bool JsonReader::read_iso(
const std::string &name, core::TimeStamp &result) const {
return JsonReader{json_, name}.read_iso(result);
}
bool JsonReader::read_iso(core::TimeStamp &result) const {
BEGIN_READ_JSON {
switch (json_->type()) {
case nlohmann::json::value_t::string: {
return parse_iso_time_stamp_(json_->get<string>(), result);
}
default:
return false;
}
}
END_READ_JSON("TimeStamp")
}
bool JsonReader::read_epoch_nanos(TimeStamp &value) const {
int64_t nanos;
if (!read(nanos)) {
return false;
}
value = TimeStamp::from_nanos(nanos);
return true;
}
bool JsonReader::read_epoch_micros(TimeStamp &value) const {
int64_t micros;
if (!read(micros)) {
return false;
}
value = TimeStamp::from_micros(micros);
return true;
}
bool JsonReader::read_epoch_millis(TimeStamp &value) const {
int64_t millis;
if (!read(millis)) {
return false;
}
value = TimeStamp::from_millis(millis);
return true;
}
bool JsonReader::read_optional_epoch_millis(
std::optional<TimeStamp> &result) const {
if (json_ == nullptr) {
return false;
}
if (json_->is_null()) {
result = std::nullopt;
return true;
}
return read_epoch_millis(result.emplace());
}
bool JsonReader::read_optional_epoch_millis(
const std::string &name, std::optional<TimeStamp> &result) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
return JsonReader{&*it}.read_epoch_millis(result.emplace());
}
} // namespace bts::io
+291
View File
@@ -0,0 +1,291 @@
#pragma once
#include "lib/core/TimeStamp.h"
#include "lib/utils/bts_assert.h"
#include <cstdint>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
namespace bts::io {
class JsonReader {
public:
explicit JsonReader(const nlohmann::json *json);
JsonReader(const nlohmann::json *json, const std::string &name);
static bool try_parse(
const std::string &str,
nlohmann::json &result,
std::string *err = nullptr);
bool read(const std::string &name, std::string &result) const;
bool read(const std::string &name, std::int32_t &result) const;
bool read(const std::string &name, std::uint32_t &result) const;
bool read(const std::string &name, std::int64_t &result) const;
bool read(const std::string &name, std::uint64_t &result) const;
bool read(const std::string &name, float &result) const;
bool read(const std::string &name, double &result) const;
bool read(const std::string &name, nlohmann::json &result) const;
bool read(const std::string &name, core::TimeStamp &result) const;
bool read_iso(const std::string &name, core::TimeStamp &result) const;
bool read_epoch_nanos(
const std::string &name, core::TimeStamp &result) const;
bool read_epoch_micros(
const std::string &name, core::TimeStamp &result) const;
bool read_epoch_millis(
const std::string &name, core::TimeStamp &result) const;
template <class T>
bool read(const std::string &name, T &result) const;
template <class T>
bool read_one_or_more(
const std::string &name,
std::vector<T> &result,
bool append = true) const;
template <class T>
T read(const std::string &name) const;
bool read(std::string &result) const;
bool read(std::int32_t &result) const;
bool read(std::uint32_t &result) const;
bool read(std::int64_t &result) const;
bool read(std::uint64_t &result) const;
bool read(float &result) const;
bool read(double &result) const;
bool read(nlohmann::json &result) const;
bool read(core::TimeStamp &result) const;
bool read_iso(core::TimeStamp &result) const;
bool read_epoch_nanos(core::TimeStamp &result) const;
bool read_epoch_micros(core::TimeStamp &result) const;
bool read_epoch_millis(core::TimeStamp &result) const;
template <class T>
bool read_one_or_more(std::vector<T> &result, bool append = true) const;
template <class T>
static bool read_flat(
const std::vector<nlohmann::json> &items, std::vector<T> &result);
bool read_flat(std::vector<nlohmann::json> &result) const;
template <class T>
bool read_optional(std::optional<T> &result) const;
template <class T>
bool read_optional(const std::string &name, std::optional<T> &result) const;
bool read_optional_epoch_millis(
std::optional<core::TimeStamp> &result) const;
bool read_optional_epoch_millis(
const std::string &name, std::optional<core::TimeStamp> &result) const;
template <class T>
bool read(T &result) const;
template <class T>
T read() const;
/**
* @brief Reads a json field into \p result
*
* @details If the read fails, assigns \p default_value to \p result
* @return True if the read succeeds, otherwise false
*/
template <class T>
bool read_or(const std::string &name, T &result, T default_value) const;
private:
const nlohmann::json *json_;
bool parse_time_stamp_(
const std::string &str, core::TimeStamp &result) const;
bool parse_iso_time_stamp_(
const std::string &str, core::TimeStamp &result) const;
bool parse_epoch_nanos_time_stamp_(
const std::string &str, core::TimeStamp &result) const;
};
//////////////////////////////////////////////////
// Definition
//////////////////////////////////////////////////
#define CATCH_PARSE_ERRORS(type_name) \
catch (const std::exception &e) { \
LOG(Gen, Diag, "Failed to parse json to '{}'. Exception: {}", \
std::string{type_name}, std::string{e.what()}); \
} \
catch (...) { \
bts_assert_msgf( \
false, "Failed to parse json to '{}'. Exception: unknown", \
std::string{type_name}); \
}
template <class T>
inline bool JsonReader::read(const std::string &name, T &result) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
it->get_to(result);
return true;
}
template <class T>
inline bool JsonReader::read_one_or_more(
const std::string &name, std::vector<T> &result, bool append) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
try {
switch (it->type()) {
case nlohmann::json::value_t::array:
if (append) {
for (const auto &item : it.value()) {
item.get_to(result.emplace_back());
}
} else {
it.value().get_to(result);
}
return true;
default:
if (append) {
it.value().get_to(result.emplace_back());
} else {
if (result.empty()) {
result.emplace_back();
}
it.value().get_to(result.at(0));
result.erase(result.begin() + 1, result.end());
}
return true;
}
}
CATCH_PARSE_ERRORS(typeid(T).name())
return false;
}
template <class T>
inline bool JsonReader::read_one_or_more(
std::vector<T> &result, bool append) const {
if (!json_) {
return false;
}
try {
switch (json_->type()) {
case nlohmann::json::value_t::array:
if (append) {
for (const auto &item : json_->array()) {
item.get_to(result.emplace_back());
}
} else {
json_->get_to(result);
}
return true;
default:
if (append) {
json_->get_to(result.emplace_back());
} else {
if (result.empty()) {
result.emplace_back();
}
json_->get_to(result.at(0));
result.erase(result.begin() + 1, result.end());
}
return true;
}
}
CATCH_PARSE_ERRORS(typeid(T).name());
return false;
}
template <class T>
inline bool JsonReader::read(T &result) const {
try {
json_->get_to(result);
return true;
} catch (...) {
const auto j_str = json_->dump(2);
LOG(md, Error, "Exception parsing json:\n{}", j_str);
return false;
}
}
template <class T>
inline T JsonReader::read() const {
T result;
read(result);
return result;
}
template <class T>
inline bool JsonReader::read_optional(std::optional<T> &result) const {
if (json_ == nullptr) {
return false;
}
if (json_->is_null()) {
result = std::nullopt;
return true;
}
return read(result.emplace());
}
template <class T>
inline bool JsonReader::read_optional(
const std::string &name, std::optional<T> &result) const {
if (!json_ || !json_->is_object()) {
return false;
}
auto it = json_->find(name);
if (it == json_->end()) {
return false;
}
return JsonReader{&it.value()}.read_optional(result);
}
template <class T>
inline T JsonReader::read(const std::string &name) const {
bts_assert(json_ && json_->is_object());
auto it = json_->find(name);
bts_assert_msgf(it != json_->end(), "json doesn't contain field '{}'", name);
return JsonReader{&it.value()}.read<T>();
}
template <class T>
inline bool JsonReader::read_or(
const std::string &name, T &result, T default_value) const {
if (read(name, result)) {
return true;
}
result = default_value;
return false;
}
template <class T>
bool JsonReader::read_flat(
const std::vector<nlohmann::json> &items, std::vector<T> &result) {
std::vector<nlohmann::json> flat_js;
for (const auto &item_j : items) {
if (!JsonReader{&item_j}.read_flat(flat_js)) {
return false;
}
}
for (const auto &el_j : flat_js) {
JsonReader{&el_j}.read(result.emplace_back());
}
return true;
}
#undef CATCH_PARSE_ERRORS
} // namespace bts::io
@@ -89,7 +89,7 @@ void WebsocketSSLClientInternal::_handle_error(
str << "Error_code:" << error_code << " Text:" << err_msg;
LOG(OrderMan, Info,
"WebsocketSSLClientInternal::_handle_error error_code:{} err_msg:{}",
error_code, err_msg);
error_code.message(), err_msg);
_listener->on_error({err_type, str.str()});
close_connection();
}
@@ -131,7 +131,8 @@ void WebsocketSSLClientInternal::_on_connect(
return;
}
LOG(OrderMan, Info,
"WebsocketSSLClientInternal::_on_connect successfull ep:{}", ep);
"WebsocketSSLClientInternal::_on_connect successfull ep: {}:{}",
ep.address().to_string(), ep.port());
ip_port = _conf.ip + ':' + std::to_string(ep.port());
// Set a timeout on the operation
boost::beast::get_lowest_layer(_ws).expires_after(std::chrono::seconds(30));
+7 -1
View File
@@ -4,6 +4,7 @@
#include <fmt/ostream.h>
#include <iostream>
#include <type_traits>
#include <vector>
namespace bts {
namespace log {
@@ -12,7 +13,12 @@ template <class Tuple, std::size_t... Is>
std::string format_tuple(
const char *msg_template, Tuple const &t, std::index_sequence<Is...>) {
try {
return fmt::format(msg_template, std::get<Is>(t)...);
using ctx = fmt::format_context;
std::vector<fmt::basic_format_arg<ctx>> fmt_args;
(fmt_args.push_back(fmt::detail::make_arg<ctx>(std::get<Is>(t))), ...);
return fmt::vformat(
msg_template,
fmt::basic_format_args<ctx>(fmt_args.data(), fmt_args.size()));
} catch (fmt::format_error const &e) {
return fmt::format(
"Bad log message template: '{}' Error: '{}'", msg_template, e.what());
+6 -2
View File
@@ -1,8 +1,11 @@
#include "catch/catch.hpp"
#include "lib/config/config.h"
#include "lib/config/protos/logging.pb.h"
#include "lib/utils/bts_chrono.h"
#include "lib/logging/logging.h"
#include "lib/utils/bts_chrono.h"
#include <fmt/format.h>
#include <fmt/chrono.h>
namespace bts {
int logging_tests;
@@ -22,7 +25,8 @@ TEST_CASE("Logging Tests", "[logging] [benchmark]") {
SECTION("timestamp_interpolation") {
auto start = bts::Timer::now();
for (int i = 0; i < sz; i++)
LOG(Gen, Info, "Hello World. It is now: {0}", Timer::now());
LOG(Gen, Info, "Hello World. It is now: {}",
Timer::now().time_since_epoch());
auto stop = Timer::now();
LOG(Testing, Info, "Logging: ns/msg for log with timestamps: {} tt: {} ",
bts::ns_diff(stop, start) / sz, bts::ns_diff(stop, start));
+3
View File
@@ -2,6 +2,7 @@
#include "ExchangeOrderKey.h"
#include "IntPrice.h"
#include "MDProducerHandle.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/SecurityKey.h"
#include "lib/utils/TimeStamp.h"
@@ -169,3 +170,5 @@ class MBOProcessor {
namespace std {
std::ostream &operator<<(std::ostream &os, const bts::md::inc::Update &);
}
FMT_IMPL_OSTREAM(bts::md::inc::Update);
+6
View File
@@ -1,5 +1,9 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include <ostream>
#include <sstream>
#include <string>
namespace bts::md {
@@ -30,3 +34,5 @@ constexpr const char *MarketDataError::type_name() const noexcept {
}
} // namespace bts::md
FMT_IMPL_OSTREAM(bts::md::MarketDataError);
+6
View File
@@ -1,4 +1,7 @@
#pragma once
#ifndef _WIN32
#include "lib/core/FMT_IMPL.h"
#include "lib/core/ValueWrapper.h"
namespace bts::md {
@@ -7,3 +10,6 @@ using MarketDataListenerID =
core::ValueWrapper<struct MarketDataListenedIDTag, std::uint32_t>;
} // namespace bts::md
FMT_IMPL(bts::md::MarketDataListenerID, id, "{}", *id);
#endif
+3
View File
@@ -1,4 +1,6 @@
#pragma once
#ifndef _WIN32
#include <optional>
namespace bts::md {
@@ -22,3 +24,4 @@ struct MarketDataRegstrationError {
};
} // namespace bts::md
#endif
@@ -1,3 +1,4 @@
#ifndef _WIN32
#include "MarketDataRegistrationFactory.h"
#include "MarketDataRegistration.h"
@@ -8,3 +9,4 @@ MarketDataRegistration MarketDataRegistrationFactory::create() {
}
} // namespace bts::md
#endif
@@ -1,4 +1,5 @@
#pragma once
#ifndef _WIN32
#include <atomic>
#include <cstdint>
@@ -15,3 +16,4 @@ class MarketDataRegistrationFactory {
};
} // namespace bts::md
#endif
+3
View File
@@ -1,6 +1,7 @@
#pragma once
#include "../common.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/utils/ValueWrapper.h"
#include "lib/utils/bts_array_size.h"
#include "lib/utils/bts_assert.h"
@@ -284,3 +285,5 @@ void with_security_like(const api::Security &security, C cb) {
} // namespace md
} // namespace bts
FMT_IMPL(bts::md::SecurityKey, k, "{}", *k);
+1 -1
View File
@@ -152,7 +152,7 @@ void SyntheticSecurityService::create_synthetic(
} else {
LOG(Api, Info,
"Failed to un-delete security {}. Error {}", id,
result.error());
UpdateResult_Error_Name(result.error()));
send_reply(
reply,
CreateResult::ERROR_SECURITY_ALREADY_EXISTS, id);
+1 -1
View File
@@ -58,7 +58,7 @@ OnixsTemplLicense onixs_create_temporary_license() {
LOG(md, Warn,
"Licensing error. Failed to write to '{}'. Please ensure it is "
"writeable.",
linfo.path);
linfo.path.string());
}
}
return linfo;
+2 -3
View File
@@ -369,14 +369,13 @@ void Broker::notify(
if (srec->is_local() || srec->sub_id == ex_subid) {
LOG(Latency, Diag, "Started handling event {} on broker {}",
log::CharPtrWrapper{srec->topic_str}, me->my_id.value());
std::string{srec->topic_str}, me->my_id);
auto start = Timer::now();
srec->notify(payload);
auto stop = Timer::now();
LOG(Latency, Diag, "Finished handling event {} on broker {}. Time: {}us",
log::CharPtrWrapper{srec->topic_str}, me->my_id.value(),
us_diff(stop, start));
std::string{srec->topic_str}, me->my_id, us_diff(stop, start));
}
}
+4
View File
@@ -1,5 +1,6 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/mercury/common/FatWrapper.h"
#include "lib/utils/ValueWrapper.h"
#include <array>
@@ -30,3 +31,6 @@ using TimeStampStr = utils::ValueWrapper<
} // namespace mercury
} // namespace bts
FMT_IMPL(bts::mercury::BrokerID, id, "{}", *id);
FMT_IMPL(bts::mercury::LocalSubID, id, "{}", *id);
+12
View File
@@ -1,8 +1,10 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/mercury/events/Event.h"
#include "lib/mercury/topics/ITopic.h"
#include "lib/utils/type_name.h"
#include <fmt/format.h>
#include <memory>
@@ -52,3 +54,13 @@ template <class EV> class Topic<EV, false> : public ITopic {
} // namespace mercury
} // namespace bts
#define _TOPIC(...) bts::mercury::Topic<__VA_ARGS__>
template <class EV>
FMT_IMPL_T(_TOPIC(EV, true), t, "{}", t.to_str());
template <class EV>
FMT_IMPL_T(_TOPIC(EV, false), t, "{}", t.to_str());
#undef _TOPIC
+6
View File
@@ -1,7 +1,11 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include <sstream>
namespace bts::orderman {
struct CancelOrderMsg {
@@ -14,3 +18,5 @@ struct CancelOrderMsg {
};
} // namespace bts::orderman
FMT_IMPL_OSTREAM(bts::orderman::CancelOrderMsg);
+6
View File
@@ -1,7 +1,11 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/orderman/CreateOrderMsg.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include <sstream>
namespace bts::orderman {
struct CancelReplaceOrderMsg {
@@ -10,3 +14,5 @@ struct CancelReplaceOrderMsg {
};
} // namespace bts::orderman
FMT_IMPL_OSTREAM(bts::orderman::CancelReplaceOrderMsg);
+8
View File
@@ -1,6 +1,12 @@
#pragma once
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.h"
#include "lib/domain_model/common.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include <sstream>
namespace bts::orderman {
struct CreateOrderMsg {
@@ -18,3 +24,5 @@ struct CreateOrderMsg {
};
} // namespace bts::orderman
FMT_IMPL_OSTREAM(bts::orderman::CreateOrderMsg);
+32
View File
@@ -1,9 +1,12 @@
#pragma once
#include "lib/domain_model/Security.pb.h"
#include "lib/orderman/CancelOrderMsg.h"
#include "lib/orderman/CancelReplaceOrderMsg.h"
#include "lib/orderman/CreateOrderMsg.h"
#include "lib/orderman/RFQMsg.h"
#include "lib/utils/overloaded.h"
#include <variant>
namespace bts::orderman {
@@ -32,3 +35,32 @@ inline std::ostream &operator<<(std::ostream &out, OrderMsg const &msg) {
return out;
}
} // namespace bts::orderman
template <>
struct fmt ::formatter<bts ::orderman ::OrderMsg> {
constexpr auto parse(fmt ::format_parse_context &ctx)
-> decltype(ctx.end()) {
return ctx.end();
}
template <typename FormatContext>
auto format(const bts ::orderman ::OrderMsg &m, FormatContext &ctx) const
-> decltype(ctx.out()) {
std::visit(
bts::overloaded{
[&ctx](const bts::orderman::CreateOrderMsg &m) {
fmt::format_to(ctx.out(), "{}", m);
},
[&ctx](const bts::orderman::CancelReplaceOrderMsg &m) {
fmt::format_to(ctx.out(), "{}", m);
},
[&ctx](const bts::orderman::CancelOrderMsg &m) {
fmt::format_to(ctx.out(), "{}", m);
},
[&ctx](const bts::orderman::RFQMsg &r) {
fmt::format_to(ctx.out(), "{}", r);
},
[]() {}},
m);
return fmt::format_to(ctx.out(), "{}", m);
}
};
+5
View File
@@ -1,8 +1,11 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/orderman/events/RFQEvent.h"
#include "lib/orderman/orderman_utils/orderman_common.h"
#include <optional>
namespace bts::orderman {
@@ -13,3 +16,5 @@ struct RFQMsg {
};
} // namespace bts::orderman
FMT_IMPL(bts::orderman::RFQMsg, m, "{}", m.rfq.DebugString());
@@ -642,7 +642,7 @@ void OrderManager::after_event() {
LOG(Latency, Info,
"entry_type:after_event channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
log::CharPtrWrapper{corid.channel}, corid.sn,
std::string{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), now.to_uint64(),
(now - corid.app_arrival).nanoseconds());
osd->gw->send_msgs(queued_ops, osd->current_trace);
+3
View File
@@ -1,6 +1,7 @@
#pragma once
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/Security.pb.h"
#include "lib/mercury/events/Event.h"
#include "lib/tracing/PerfLog.h"
@@ -20,3 +21,5 @@ class RFQEvent : public mercury::Event<RFQEvent> {
} // namespace orderman
} // namespace bts
FMT_IMPL(bts::orderman::RFQEvent, e, "{}", e.rfq.DebugString());
@@ -4,6 +4,7 @@
#include "lib/domain_model/Security.h"
#include "lib/jetfix/common/OutFIX.h"
#include "lib/jetfix/store/session_name.h"
#include "lib/logging/logging.h"
#include "lib/orderman/events/NewSecuritiesEvent.h"
#include "lib/orderman/gateways/GLBXExecutionGateway.h"
#include "lib/orderman/ilink3/IL3GW.h"
@@ -24,14 +25,6 @@ struct OrderStats {
size_t n_cxls = 0;
};
enum class GLBXiLink3ExecutionGateway::State {
WAITING_FOR_SECURITIES,
PAUSED,
CONNECTING,
READY
};
struct GLBXiLink3ExecutionGateway::PImpl {
PImpl(
config::JetFIX::Connection const &cfg,
@@ -92,7 +85,7 @@ void GLBXiLink3ExecutionGateway::start() {
void GLBXiLink3ExecutionGateway::dispatch_incoming_fix_messages() {
if (me->started) {
switch (me->state) {
switch (me->state.value) {
case State::WAITING_FOR_SECURITIES:
break;
case State::PAUSED:
@@ -181,9 +174,8 @@ void GLBXiLink3ExecutionGateway::connect(jetfix::SessionName const &sn) {
me->gw->subscribe_state([this](auto gw_state) {
LOG(iLink3, Info, "Internal gateway state change in {}. State is now {}",
me->gw_name.value(),
log::CharPtrWrapper{ilink3::state_string(gw_state)});
switch (me->state) {
me->gw_name.value(), gw_state);
switch (me->state.value) {
case State::CONNECTING:
if (gw_state == ilink3::State::READY) {
transition(State::READY);
@@ -262,7 +254,7 @@ void GLBXiLink3ExecutionGateway::send_msgs(
auto cancel_replace_ptr = std::get_if<CancelReplaceOrderMsg>(&m);
if (cancel_replace_ptr) {
LOG(Latency, Info, "Cancel Replace. CorID:{}.{} Order:{}",
log::CharPtrWrapper{corid.channel}, corid.sn,
std::string{corid.channel}, corid.sn,
cancel_replace_ptr->exchange_order_id);
me->gw->send_cancel_replace(*cancel_replace_ptr, corid);
@@ -307,24 +299,9 @@ GLBXiLink3ExecutionGateway::on_exchange_open_orders() {
return me->on_exchange_open_orders;
}
log::CharPtrWrapper GLBXiLink3ExecutionGateway::state_name(State st) {
switch (st) {
case State::WAITING_FOR_SECURITIES:
return {"WAITING_FOR_SECURITIES"};
case State::PAUSED:
return {"PAUSED"};
case State::CONNECTING:
return {"CONNECTING"};
case State::READY:
return {"READY"};
}
bts_assert(false);
return {""};
}
void GLBXiLink3ExecutionGateway::transition(State new_state) {
LOG(iLink3, Info, "Transitioning {} from {} to {}", me->gw_name.value(),
state_name(me->state), state_name(new_state));
me->state, new_state);
me->state = new_state;
}
@@ -1,7 +1,7 @@
#pragma once
#include "IExecutionGateway.h"
#include "lib/logging/logging.h"
#include "lib/orderman/gateways/GLBXiLink3ExecutionGatewayState.h"
namespace bts {
namespace jetfix {
@@ -36,7 +36,7 @@ class GLBXiLink3ExecutionGateway : public IExecutionGateway {
OnExchangeOpenOrdersState &on_exchange_open_orders() override;
private:
enum class State;
using State = GLBXiLink3ExecutionGatewayState;
struct PImpl;
std::unique_ptr<PImpl> me;
@@ -44,7 +44,6 @@ class GLBXiLink3ExecutionGateway : public IExecutionGateway {
void on_time();
bool pause_in_effect() const;
bool max_failures_reached() const;
static log::CharPtrWrapper state_name(State);
void transition(State);
void pause();
@@ -0,0 +1,32 @@
#pragma once
#ifdef SPARK_ENABLE_CCAPI
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
#include <string>
namespace bts::orderman::ilink3 {
// clang-format off
#define FOR_EACH_GLBX_ILINK3_EXECUTION_GATEWAY_STATE(per_value) \
per_value(GLBXiLink3ExecutionGatewayState, WAITING_FOR_SECURITIES) \
per_value(GLBXiLink3ExecutionGatewayState, PAUSED) \
per_value(GLBXiLink3ExecutionGatewayState, CONNECTING) \
per_value(GLBXiLink3ExecutionGatewayState, READY)
// clang-format on
ENUM_CLASS_DEF(
GLBXiLink3ExecutionGatewayState,
FOR_EACH_GLBX_ILINK3_EXECUTION_GATEWAY_STATE)
#undef FOR_EACH_GLBX_ILINK3_EXECUTION_GATEWAY_STATE
} // namespace bts::orderman::ilink3
#endif
FMT_IMPL(
bts::orderman::ilink3::GLBXiLink3ExecutionGatewayState, s, "{}", s.name());
+9 -33
View File
@@ -33,7 +33,7 @@ namespace bts {
namespace orderman {
namespace ilink3 {
log::CharPtrWrapper wrap(const char *str) { return log::CharPtrWrapper{str}; }
using std::string;
IL3GW::IL3GW(
IL3Connection &con,
@@ -233,11 +233,9 @@ void IL3GW::send_order(
_current_corid = corid;
auto now = utils::TimeStamp::now();
LOG(Latency, Info,
"entry_type:send_order channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
log::CharPtrWrapper{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), now.to_uint64(),
(now - corid.app_arrival).nanoseconds());
"entry_type:send_order channel:{} sn:{} app_arrival:{} now:{} delta:{}",
string{corid.channel}, corid.sn, corid.app_arrival.to_uint64(),
now.to_uint64(), (now - corid.app_arrival).nanoseconds());
bts_assert_d(create.routing_record);
bts_assert_d(create.security);
bts_assert_d(create.security->has_glbx());
@@ -386,31 +384,9 @@ void IL3GW::flush(bool publish) {
publish_session_update();
}
const char *state_string(State s) {
switch (s) {
case State::INIT:
return "INIT";
case State::CONNECTING:
return "CONNECTING";
case State::NEGOTIATING:
return "NEGOTIATING";
case State::ESTABLISHING:
return "ESTABLISHING";
case State::RETRANSMITTING:
return "RETRANSMITTING";
case State::READY:
return "READY";
case State::ERROR:
return "ERROR";
}
return "UNKNOWN";
}
log::CharPtrWrapper wstate_string(State s) { return wrap(state_string(s)); }
void IL3GW::set_state(State s) {
if (s != _x_state) {
LOG(iLink3, Info, "ILinkGW {}: {}->{}", _session.uuid,
wstate_string(_x_state), wstate_string(s));
LOG(iLink3, Info, "ILinkGW {}: {}->{}", _session.uuid, _x_state, s);
_x_state = s;
if (_on_state_change)
_on_state_change(s);
@@ -572,8 +548,8 @@ void IL3GW::handle(const iLinkBinary::Sequence506 &msg) {
void IL3GW::handle(const iLinkBinary::Terminate507 &msg) {
// https://www.cmegroup.com/confluence/display/EPICSANDBOX/Terminate
LOG(iLink3, Error, "Terminated by exchange. Code: {} Reason: {} Text: {}",
msg.errorCodes(), wrap(msg.reason()),
wrap(translate_terminate(msg.errorCodes())));
msg.errorCodes(), std::string{msg.reason()},
std::string{translate_terminate(msg.errorCodes())});
if (msg.errorCodes() == 11 || msg.errorCodes() == 10) {
// This indicates our sequence number is too low. We will jump
// forward one at a time. That should eventually fix it.
@@ -607,7 +583,7 @@ void IL3GW::handle(
}
void IL3GW::handle(const iLinkBinary::BusinessReject521 &msg) {
LOG(iLink3, Error, "Business Reject: {} '{}'.", msg.businessRejectReason(),
wrap(translate_business_reject_reason(msg.businessRejectReason())));
string{translate_business_reject_reason(msg.businessRejectReason())});
handle_common(msg);
// A business reject can de-sync the sequence number because it rejects
// the sequence number of the rejected message. Send a sequence message
@@ -619,7 +595,7 @@ void IL3GW::handle(const iLinkBinary::ExecutionReportNew522 &msg) {
handle_exe_report(msg);
}
void IL3GW::handle(const iLinkBinary::ExecutionReportReject523 &msg) {
LOG(iLink3, Error, "Execution report reject: {}", wrap(msg.text()));
LOG(iLink3, Error, "Execution report reject: {}", string{msg.text()});
handle_exe_report(msg);
}
void IL3GW::handle(const iLinkBinary::ExecutionReportElimination524 &msg) {
+3 -10
View File
@@ -6,6 +6,7 @@
#include "TimeKeeper.h"
#include "generated/MessageHeader.h"
#include "lib/config/protos/jetfix.pb.h"
#include "lib/orderman/ilink3/IL3State.h"
#include "lib/orderman/ilink3/RawMsg.h"
#include "lib/orderman/gateways/IExecutionGateway.h"
#include "lib/orderman/orderman_utils/CancelReject.h"
@@ -42,16 +43,8 @@ struct Config {
raw_message_logger;
};
enum class State {
INIT,
CONNECTING,
NEGOTIATING,
ESTABLISHING,
RETRANSMITTING,
READY,
ERROR
};
const char *state_string(State s);
using State = IL3State;
typedef std::function<void(State)> OnStateChange;
typedef std::function<void(const ExecutionReport *)> OnExecutionReport;
typedef std::function<void(const Session &s)> OnSessionChange;
+1 -1
View File
@@ -130,7 +130,7 @@ struct IL3Reactor::Connection : public IL3Connection {
LOG(Latency, Info,
"entry_type:socket_write channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
log::CharPtrWrapper{corid.channel}, corid.sn,
std::string{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), before_write.to_uint64(),
(before_write - corid.app_arrival).nanoseconds());
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#ifdef SPARK_ENABLE_CCAPI
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
#include <string>
namespace bts::orderman::ilink3 {
// clang-format off
#define FOR_EACH_IL3_STATE(per_value) \
per_value(IL3State, INIT) \
per_value(IL3State, CONNECTING) \
per_value(IL3State, NEGOTIATING) \
per_value(IL3State, ESTABLISHING) \
per_value(IL3State, RETRANSMITTING) \
per_value(IL3State, READY) \
per_value(IL3State, ERROR)
// clang-format on
ENUM_CLASS_DEF(IL3State, FOR_EACH_IL3_STATE)
#undef FOR_EACH_IL3_STATE
} // namespace bts::orderman::ilink3
#endif
FMT_IMPL(bts::orderman::ilink3::IL3State, s, "{}", s.name());
@@ -8,6 +8,7 @@
#include <variant>
#include "lib/api/services/OrderService.pb.h"
#include "lib/core/FMT_IMPL.h"
#include "lib/domain_model/common.h"
#include "lib/utils/ValueWrapper.h"
#include "lib/utils/bts_assert.h"
@@ -289,3 +290,8 @@ inline void set_update_quantity(
} // namespace orderman
} // namespace bts
FMT_IMPL(bts::orderman::AccountID, id, "{}", *id);
FMT_IMPL(bts::orderman::ExchangeOrderID64, id, "{}", *id);
FMT_IMPL(bts::orderman::GatewayName, n, "{}", *n);
FMT_IMPL(bts::orderman::OrderKey, k, "{}", *k);
+8 -8
View File
@@ -27,6 +27,8 @@ namespace orderman {
using namespace api::orderman;
using std::string;
OrderStatus map_external_status(NativeOrderStatus);
bool use_ilink3(std::string const &for_session) {
@@ -458,9 +460,8 @@ OrderOpResult GLBXNativeOrder::on_activate(
LOG(Latency, Info,
"entry_type:on_activate_1 channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
log::CharPtrWrapper{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), now.to_uint64(),
(now - corid.app_arrival).nanoseconds());
string{corid.channel}, corid.sn, corid.app_arrival.to_uint64(),
now.to_uint64(), (now - corid.app_arrival).nanoseconds());
result = _on_activate(Warn, Info, p, q, services);
if (result.ok()) {
@@ -468,9 +469,8 @@ OrderOpResult GLBXNativeOrder::on_activate(
LOG(Latency, Info,
"entry_type:on_activate_2 channel:{} sn:{} app_arrival:{} now:{} "
"delta:{}",
log::CharPtrWrapper{corid.channel}, corid.sn,
corid.app_arrival.to_uint64(), now.to_uint64(),
(now - corid.app_arrival).nanoseconds());
string{corid.channel}, corid.sn, corid.app_arrival.to_uint64(),
now.to_uint64(), (now - corid.app_arrival).nanoseconds());
me->encoder->send_create(*me->services, *me->session_services);
me->services->governor(*me->session_services).record_sent();
@@ -1264,8 +1264,8 @@ bool GLBXNativeOrder::terminal_state(api::orderman::OrderStatus status) const {
return false;
}
api::orderman::NativeOrderState &
GLBXNativeOrder::get_native_state(api::orderman::OrderState &st) {
api::orderman::NativeOrderState &GLBXNativeOrder::get_native_state(
api::orderman::OrderState &st) {
switch (st.custom_case()) {
case OrderState::kMarket:
return *st.mutable_market()->mutable_native_state();
+12 -11
View File
@@ -1,4 +1,5 @@
#include "TriggerOrder.h"
#include "lib/api/services/OrderService.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/orderman/OrderBuilder.h"
@@ -62,7 +63,7 @@ void TriggerOrder::on_init_new(
const PreActivationOrderServices &pre_os) {
LOG(OrderMan, Info, "TriggerOrder::on_init_new key:{} state:{}", key(),
_state);
switch (_state) {
switch (_state.value) {
case State::START:
me->is_succ_inited = _on_init_new(order_def, pre_os);
break;
@@ -81,7 +82,7 @@ void TriggerOrder::on_init_recovered(
const PreActivationOrderServices &pre_os) {
LOG(OrderMan, Info, "TriggerOrder::on_init_recovered key:{} state:{}", key(),
_state);
switch (_state) {
switch (_state.value) {
case State::START:
me->is_succ_inited = _on_init_new(rec.def(), pre_os);
// get the state from what was recovered (we will only use
@@ -109,7 +110,7 @@ TriggerOrder::on_activate(Price price, Quantity qty, OrderServices &services) {
LOG(OrderMan, Info,
"TriggerOrder::on_activate key:{} state:{} price:{} qty:{}", key(),
_state, price, qty);
switch (_state) {
switch (_state.value) {
case State::START:
me->services = &services;
me->state.set_price(price.value());
@@ -150,7 +151,7 @@ OrderOpResult TriggerOrder::on_update(Price price, Quantity qty) {
LOG(OrderMan, Info,
"TriggerOrder::on_update key:{} state:{} price:{} qty:{}", key(), _state,
price, qty);
switch (_state) {
switch (_state.value) {
case State::START:
me->state.set_price(price.value());
me->state.set_quantity(qty.value());
@@ -177,7 +178,7 @@ OrderOpResult TriggerOrder::on_cancel() {
LOG(OrderMan, Info, "TriggerOrder::on_cancel key:{} state:{}", key(),
_state);
me->cancel_mode = CancelMode::external;
switch (_state) {
switch (_state.value) {
case State::START:
_switch_state(State::END);
break;
@@ -200,7 +201,7 @@ OrderOpResult TriggerOrder::on_cancel() {
return OrderOpResult{};
}
void TriggerOrder::_on_book_update(const BookUpdateEvent &b_event) {
switch (_state) {
switch (_state.value) {
case State::START:
break;
case State::WAITING_FOR_TRIGGER:
@@ -227,7 +228,7 @@ void TriggerOrder::_on_child_order_update(const OrderStateUpdateEvent &o_ev) {
if (o_ev.key == me->child_order.key()) {
auto ev_state = o_ev.rebuild_state();
me->child_state = ev_state;
switch (_state) {
switch (_state.value) {
case State::START:
case State::WAITING_FOR_TRIGGER:
bts_assert(false);
@@ -251,7 +252,7 @@ void TriggerOrder::_on_child_order_update(const OrderStateUpdateEvent &o_ev) {
}
}
void TriggerOrder::_on_time(const bts::mercury::TimerEvent &) {
switch (_state) {
switch (_state.value) {
case State::START:
case State::WAITING_FOR_TRIGGER:
case State::WAITING_FOR_FIRST_ACK:
@@ -342,7 +343,7 @@ void TriggerOrder::_report_state() {
}
}
void TriggerOrder::_switch_state(State new_state) {
switch (new_state) {
switch (new_state.value) {
case State::START:
break;
case State::WAITING_FOR_TRIGGER:
@@ -415,7 +416,7 @@ void TriggerOrder::_analyze_trigger_and_cancel() {
}
}
api::orderman::OrderStatus TriggerOrder::_status() const {
switch (_state) {
switch (_state.value) {
// will only be called once the order
// has been activated
case State::START:
@@ -440,7 +441,7 @@ api::orderman::OrderStatus TriggerOrder::_status() const {
return api::orderman::OrderStatus::ORDER_STATUS_COMPLETED;
else if (SpreaderUtils::order_in_cancelled_state(
me->child_state.status())) {
switch (me->cancel_mode) {
switch (me->cancel_mode.value) {
case CancelMode::none: // this means an unscolicted child cxl
case CancelMode::external:
return api::orderman::OrderStatus::ORDER_STATUS_CANCELED;
+4 -28
View File
@@ -2,6 +2,8 @@
#include "lib/api/services/OrderService.server.pb.h"
#include "lib/orderman/orders/Order.h"
#include "lib/orderman/orders/TriggerOrderCancelMode.h"
#include "lib/orderman/orders/TriggerOrderState.h"
#include <memory>
namespace bts {
@@ -24,34 +26,8 @@ class PreActivationOrderServices;
class TriggerOrder : public Order {
private:
enum State {
// remains here till on_actiavte is called
START = 0,
// waiting for trigger conditions, sends
// order and waits to steady
WAITING_FOR_TRIGGER = 1,
// once the order is submitted it gets into
// this state until getting a callback
// from the child order
WAITING_FOR_FIRST_ACK = 2,
// order is out
STEADY = 3,
END = 4
};
// this is the ways a child order
// can be cancelled
enum CancelMode {
// not cancelled, initial value
none = 0,
// meaning the trigger order
// cancelled the child order because
// of time or conditional reasons
trigger,
// the trigger order is itself
// externally cancelled
// through the cancel call
external
};
using State = TriggerOrderState;
using CancelMode = TriggerOrderCancelMode;
public:
TriggerOrder();
@@ -0,0 +1,34 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
#include <string>
namespace bts::orderman {
// clang-format off
#define FOR_ALL_VALUES(per_value) \
/* not cancelled, initial value */ \
per_value(TriggerOrderCancelMode, none) \
/* meaning the trigger order cancelled the child order because of time */ \
/* or conditional reasons */ \
per_value(TriggerOrderCancelMode, trigger) \
/* the trigger order is itself externally cancelled through the cancel */ \
/* call */ \
per_value(TriggerOrderCancelMode, external)
// clang-format on
/**
* @brief Represents the ways a TriggerOrder child order can be cancelled
*/
ENUM_CLASS_DEF(TriggerOrderCancelMode, FOR_ALL_VALUES)
#undef FOR_ALL_VALUES
} // namespace bts::orderman
FMT_IMPL(bts::orderman::TriggerOrderCancelMode, cm, "{}", cm.name());
@@ -0,0 +1,33 @@
#pragma once
#include "lib/core/EnumMacros.h"
#include "lib/core/FMT_IMPL.h"
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <ostream>
#include <string>
namespace bts::orderman {
// clang-format off
#define FOR_EACH_TRIGGER_ORDER_STATE(per_value) \
/* remains here till on_actiavte is called */ \
per_value(TriggerOrderState, START) \
/* waiting for trigger conditions, sends order and waits to steady */ \
per_value(TriggerOrderState, WAITING_FOR_TRIGGER) \
/* once the order is submitted it gets into this state until getting a */ \
/* callback from the child order */ \
per_value(TriggerOrderState, WAITING_FOR_FIRST_ACK) \
/* order is out */ \
per_value(TriggerOrderState, STEADY) \
per_value(TriggerOrderState, END)
// clang-format on
ENUM_CLASS_DEF(TriggerOrderState, FOR_EACH_TRIGGER_ORDER_STATE)
#undef FOR_EACH_TRIGGER_ORDER_STATE
} // namespace bts::orderman
FMT_IMPL(bts::orderman::TriggerOrderState, s, "{}", s.name());
@@ -1129,7 +1129,7 @@ bool AlgoWrapperOrder::is_working() const {
return me->status == Status::WORKING;
}
log::CharPtrWrapper AlgoWrapperOrder::state_name(Status st) {
std::string AlgoWrapperOrder::state_name(Status st) {
switch (st) {
case Status::COMPLETED:
return {"COMPLETED"};
@@ -105,7 +105,7 @@ class AlgoWrapperOrder : public Order, private IAlgoServices {
OrderKey index_and_launch_child(ChildOrder &&, Price, Quantity, bool);
bool is_working() const;
size_t working_children_count() const;
log::CharPtrWrapper state_name(Status);
std::string state_name(Status);
ChildStatus map_child_status(api::orderman::OrderStatus);
bool _is_attaching(api::orderman::OrderDefinition const &) const;
};
@@ -10,6 +10,8 @@ namespace orderman {
using namespace api::orderman;
using std::string;
enum class CoverAlgo::Status { WORKING, WAITING_FOR_HEDGES, DONE, CANCELING };
struct CoverAlgo::PImpl {
@@ -262,8 +264,7 @@ void CoverAlgo::update_definition(
void CoverAlgo::terminate_if_hedges_complete() {
LOG(OrderMan, Diag,
"CoverAlgo {} check_terminate. State: {} Hedge count: {}",
algo().order_key(), log::CharPtrWrapper{status_name(me->status)},
me->hedges.size());
algo().order_key(), string{status_name(me->status)}, me->hedges.size());
if (me->status == Status::WAITING_FOR_HEDGES && me->hedges.empty()) {
LOG(OrderMan, Info, "CoverAlgo {}. Last hedge completed, terminating",
@@ -13,6 +13,8 @@ namespace orderman {
using namespace api::orderman;
using std::string;
enum class ProRataAlgo::Status {
WAITING_FOR_MD,
WORKING,
@@ -373,8 +375,7 @@ void ProRataAlgo::set_parameters(
void ProRataAlgo::check_terminate() {
LOG(OrderMan, Diag,
"ProRataAlgo {} check_terminate. State: {} Hedge count: {}",
algo().order_key(), log::CharPtrWrapper{status_name(me->status)},
me->hedges.size());
algo().order_key(), string{status_name(me->status)}, me->hedges.size());
if (me->status == Status::DONE_WORKING && me->hedges.empty() &&
me->overfill_hedges.empty()) {
@@ -300,6 +300,9 @@ extern "C" void init_execution_tester_plugin(bts::SparkServices &srv) {
extern "C" void init_task_eye_plugin(bts::SparkServices &srv) {
DynamicOrderDescriptor tasking_eye_algo;
tasking_eye_algo.set_dynamic_order_type("Tasking Eye");
// if this is set to true, it waits for the existance of Signal File
*tasking_eye_algo.add_param_desc() = param_bool("Wait For Signal");
*tasking_eye_algo.add_param_desc() = param_string("Signal File");
*tasking_eye_algo.add_param_desc() = param_string("Account");
*tasking_eye_algo.add_param_desc() = param_double("Edge");
*tasking_eye_algo.add_param_desc() = param_double("Shot Size");
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more