diff --git a/.github/workflows/exchange-ci.yml b/.github/workflows/exchange-ci.yml index b37ce729..58d12943 100644 --- a/.github/workflows/exchange-ci.yml +++ b/.github/workflows/exchange-ci.yml @@ -35,7 +35,7 @@ jobs: submodules: 'recursive' - uses: actions/setup-python@v4 - with: { python-version: "3.11" } + with: { python-version: "3.12" } - name: Install LLVM 17 run: | @@ -75,7 +75,11 @@ jobs: - name: Install Python uses: actions/setup-python@v4 - with: { python-version: "3.11" } + with: { python-version: "3.12" } + + - name: Install Python + uses: actions/setup-python@v4 + with: { python-version: "3.12-dev" } - name: Install LLVM 17 run: | @@ -97,7 +101,7 @@ jobs: run: cmake --preset=ci-sanitize - name: Build - run: cmake --build build/sanitize -j 2 + run: cmake --build build/sanitize -j - name: Test working-directory: exchange/build/sanitize @@ -152,7 +156,11 @@ jobs: - name: Install Python uses: actions/setup-python@v4 - with: { python-version: "3.11" } + with: { python-version: "3.12" } + + - name: Install Python + uses: actions/setup-python@v4 + with: { python-version: "3.12-dev" } - name: Install dependencies shell: bash @@ -175,7 +183,7 @@ jobs: run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])" - name: Build - run: cmake --build build --config Release -j 2 + run: cmake --build build --config Release -j - name: Install run: cmake --install build --config Release --prefix prefix @@ -192,12 +200,16 @@ jobs: os: [ubuntu-22.04] runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: exchange env: CC: clang-17 CXX: clang++-17 CLANG_DIR: '/usr/lib/llvm-17/lib/cmake/clang' LLVM_DIR: '/usr/lib/llvm-17/lib/cmake/llvm' + NUTC_WRAPPER_BINARY_PATH: ${{ github.workspace }}/exchange/build/WRAPPER steps: - uses: actions/checkout@v3 @@ -222,22 +234,21 @@ jobs: - name: Install Python uses: actions/setup-python@v4 - with: { python-version: "3.11" } + with: { python-version: "3.12" } + + - name: Install Python + uses: actions/setup-python@v4 + with: { python-version: "3.12-dev" } - - name: Setup conan + - name: Install dependencies shell: bash run: | pip3 install conan sudo apt install libssl-dev rabbitmq-server -y - bash < .github/scripts/conan-profile.sh - - - name: Install dependencies - shell: bash - run: | - conan install exchange -s build_type=Release -b missing - conan install wrapper -s build_type=Release -b missing + bash < ../.github/scripts/conan-profile.sh + conan install . -s build_type=Release -b missing - name: Setup MultiToolTask if: matrix.os == 'windows-2022' @@ -247,36 +258,15 @@ jobs: - name: Configure exchange shell: pwsh - working-directory: exchange run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])" - name: Build exchange - working-directory: exchange - run: cmake --build build --config Release -j 2 + run: cmake --build build --config Release -j - name: Install exchange - working-directory: exchange - run: cmake --install build --config Release --prefix prefix - - - name: Install Python Developer Headers - run: sudo apt install python3.11-dev - - # TODO: cache wrapper - - name: Configure wrapper - shell: pwsh - working-directory: wrapper - run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])" - - - name: Build wrapper - working-directory: wrapper - run: cmake --build build --config Release -j 2 - - - name: Install wrapper - working-directory: wrapper - run: sudo cmake --install build --config Release --prefix /usr/local + run: cmake --install build --config Release --prefix . - name: Move test algo files - working-directory: exchange run: cp -r test/test_algos build/test/test_algos # TODO: use secrets @@ -331,7 +321,7 @@ jobs: submodules: 'recursive' - uses: actions/setup-python@v4 - with: { python-version: "3.11" } + with: { python-version: "3.12" } - name: Install m.css dependencies run: pip3 install jinja2 Pygments diff --git a/.github/workflows/wrapper-ci.yml b/.github/workflows/wrapper-ci.yml deleted file mode 100644 index aba958eb..00000000 --- a/.github/workflows/wrapper-ci.yml +++ /dev/null @@ -1,239 +0,0 @@ -name: Wrapper - Continuous Integration - -on: - push: - branches: - - main - paths: - - 'wrapper/**' - - pull_request: - branches: - - main - paths: - - 'wrapper/**' - - workflow_dispatch: - -# We only care about the latest revision of a PR, so cancel all previous instances. -concurrency: - group: wrapper-ci-${{ github.event.pull_request.number || github.ref_name }} - cancel-in-progress: true - -jobs: - lint: - runs-on: ubuntu-22.04 - defaults: - run: - working-directory: wrapper - - steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - uses: actions/setup-python@v4 - with: { python-version: "3.11" } - - - name: Install LLVM 17 - run: | - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - sudo apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" - sudo apt update - sudo apt install llvm-17 llvm-17-dev llvm-17-tools clang-17 clang-tidy-17 clang-format-17 clang-tools-17 libclang-17-dev -y - - - name: Install codespell - run: pip3 install codespell - - - name: Lint - run: cmake -D FORMAT_COMMAND=clang-format-17 -P cmake/lint.cmake - - - name: Spell check - run: cmake -P cmake/spell.cmake - - sanitize: - needs: [lint] - - runs-on: ubuntu-22.04 - defaults: - run: - working-directory: wrapper - - - env: - CC: clang-17 - CXX: clang++-17 - CLANG_DIR: '/usr/lib/llvm-17/lib/cmake/clang' - LLVM_DIR: '/usr/lib/llvm-17/lib/cmake/llvm' - - steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - name: Install Python - uses: actions/setup-python@v4 - with: { python-version: "3.11" } - - - name: Install Python Developer Headers - run: sudo apt install python3.11-dev - - - name: Install LLVM 17 - run: | - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - sudo apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" - sudo apt update - sudo apt install llvm-17 llvm-17-dev llvm-17-tools clang-17 clang-tidy-17 clang-tools-17 libclang-17-dev -y - - - name: Install dependencies - run: | - pip3 install conan - - sudo apt install libssl-dev -y - - bash < ../.github/scripts/conan-profile.sh - conan install . -s build_type=Release -b missing - - - name: Configure - run: cmake --preset=ci-sanitize - - - name: Build - run: cmake --build build/sanitize -j 2 - - - name: Test - working-directory: wrapper/build/sanitize - env: - ASAN_OPTIONS: "strict_string_checks=1:\ - detect_stack_use_after_return=1:\ - check_initialization_order=1:\ - strict_init_order=1:\ - detect_leaks=1" - UBSAN_OPTIONS: print_stacktrace=1 - run: ctest --output-on-failure --no-tests=error -j 2 - - test: - needs: [lint] - - strategy: - matrix: - os: [ubuntu-22.04] - - runs-on: ${{ matrix.os }} - defaults: - run: - working-directory: wrapper - - - env: - CC: clang-17 - CXX: clang++-17 - CLANG_DIR: '/usr/lib/llvm-17/lib/cmake/clang' - LLVM_DIR: '/usr/lib/llvm-17/lib/cmake/llvm' - - steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - name: Install LLVM 17 - run: | - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc - sudo apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" - sudo apt update - sudo apt install llvm-17 llvm-17-dev llvm-17-tools clang-17 clang-tidy-17 clang-tools-17 libclang-17-dev -y - - - name: Install static analyzers - if: matrix.os == 'ubuntu-22.04' - run: >- - sudo apt install cppcheck -y -q - - sudo update-alternatives --install - /usr/bin/clang-tidy clang-tidy - /usr/bin/clang-tidy-17 160 - - - name: Install Python - uses: actions/setup-python@v4 - with: { python-version: "3.11" } - - - name: Install Python Developer Headers - run: sudo apt install python3.11-dev - - - name: Install dependencies - shell: bash - run: | - pip3 install conan - - sudo apt install libssl-dev -y - - bash < ../.github/scripts/conan-profile.sh - conan install . -s build_type=Release -b missing - - - name: Setup MultiToolTask - if: matrix.os == 'windows-2022' - run: | - Add-Content "$env:GITHUB_ENV" 'UseMultiToolTask=true' - Add-Content "$env:GITHUB_ENV" 'EnforceProcessCountAcrossBuilds=true' - - - name: Configure - shell: pwsh - run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])" - - - name: Build - run: cmake --build build --config Release -j 2 - - - name: Install - run: cmake --install build --config Release --prefix prefix - - - name: Test - working-directory: wrapper/build - run: ctest --output-on-failure --no-tests=error -C Release -j 2 - - docs: - # Deploy docs only when builds succeed - needs: [sanitize, test] - - runs-on: ubuntu-22.04 - defaults: - run: - working-directory: wrapper - - - # To enable, first you have to create an orphaned gh-pages branch: - # - # git switch --orphan gh-pages - # git commit --allow-empty -m "Initial commit" - # git push -u origin gh-pages - # - # Edit the placeholder below to your GitHub name, so this action - # runs only in your repository and no one else's fork. After these, delete - # this comment and the last line in the conditional below. - # If you do not wish to use GitHub Pages for deploying documentation, then - # simply delete this job similarly to the coverage one. - if: github.ref == 'refs/heads/main' - && github.event_name == 'push' - && github.repository_owner == 'northwesternfintech' - - steps: - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - - - uses: actions/setup-python@v4 - with: { python-version: "3.11" } - - - name: Install m.css dependencies - run: pip3 install jinja2 Pygments - - - name: Install Doxygen - run: sudo apt update -q - && sudo apt install doxygen -q -y - - - name: Build docs - run: cmake "-DPROJECT_SOURCE_DIR=$PWD" "-DPROJECT_BINARY_DIR=$PWD/build" - -P cmake/docs-ci.cmake - - - name: Deploy docs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: build/docs/html diff --git a/exchange/.clang-tidy b/exchange/.clang-tidy index 80b09418..f37c2a2a 100644 --- a/exchange/.clang-tidy +++ b/exchange/.clang-tidy @@ -33,7 +33,9 @@ Checks: "\ -hicpp-uppercase-literal-suffix,\ -bugprone-easily-swappable-parameters,\ -cppcoreguidelines-avoid-const-or-ref-data-members,\ - -cppcoreguidelines-avoid-do-while" + -cppcoreguidelines-avoid-do-while, \ + -*-magic-numbers, \ + -*-vararg" WarningsAsErrors: false AnalyzeTemporaryDtors: false FormatStyle: file diff --git a/exchange/.codespellrc b/exchange/.codespellrc index d3634a3f..b9d2eae2 100644 --- a/exchange/.codespellrc +++ b/exchange/.codespellrc @@ -1,5 +1,6 @@ [codespell] builtin = clear,rare,en-GB_to_en-US,names,informal,code +ignore-words-list = uint,deque check-filenames = check-hidden = skip = */.git,*/build,*/prefix,*/conan diff --git a/exchange/CMakeLists.txt b/exchange/CMakeLists.txt index 53cac651..232f482e 100644 --- a/exchange/CMakeLists.txt +++ b/exchange/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.14) include(cmake/prelude.cmake) project( - NUTC24 + NUTC VERSION 0.1.0 DESCRIPTION "Northwestern University Trading Competition, 2024" HOMEPAGE_URL "https://github.com/northwesternfintech/NUTC" @@ -13,108 +13,196 @@ project( include(cmake/project-is-top-level.cmake) include(cmake/variables.cmake) +# ---- EXCHANGE ---------- + # ---- Declare library ---- add_library( - NUTC24_lib OBJECT + EXCHANGE_lib OBJECT - src/algos/sandbox_mode/sandbox_mode.cpp - src/algos/dev_mode/dev_mode.cpp - src/algos/normal_mode/normal_mode.cpp + src/exchange/algos/sandbox_mode/sandbox_mode.cpp + src/exchange/algos/dev_mode/dev_mode.cpp + src/exchange/algos/normal_mode/normal_mode.cpp - src/logging.cpp + src/exchange/logging.cpp - src/curl/curl.cpp + src/exchange/curl/curl.cpp + + src/exchange/process_spawning/spawning.cpp - src/process_spawning/spawning.cpp + src/exchange/rabbitmq/client_manager/RabbitMQClientManager.cpp + src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp + src/exchange/rabbitmq/consumer/RabbitMQConsumer.cpp + src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.cpp + src/exchange/rabbitmq/publisher/RabbitMQPublisher.cpp + src/exchange/rabbitmq/queue_manager/RabbitMQQueueManager.cpp + + src/exchange/bots/bot_container.cpp + src/exchange/bots/bot_types/retail.cpp + + src/exchange/tickers/engine/engine.cpp + src/exchange/tickers/manager/ticker_manager.cpp - src/rabbitmq/client_manager/RabbitMQClientManager.cpp - src/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp - src/rabbitmq/consumer/RabbitMQConsumer.cpp - src/rabbitmq/order_handler/RabbitMQOrderHandler.cpp - src/rabbitmq/publisher/RabbitMQPublisher.cpp - src/rabbitmq/queue_manager/RabbitMQQueueManager.cpp + src/exchange/traders/trader_manager.cpp - src/matching/engine/engine.cpp - src/matching/manager/engine_manager.cpp + src/exchange/utils/logger/logger.cpp + src/shared/file_operations/file_operations.cpp - src/client_manager/client_manager.cpp + src/exchange/theo/brownian.cpp - src/utils/logger/logger.cpp - src/utils/file_operations/file_operations.cpp + src/exchange/tick_manager/tick_manager.cpp - src/randomness/brownian.cpp + src/exchange/dashboard/state/ticker_state.cpp ) target_include_directories( - NUTC24_lib ${warning_guard} + EXCHANGE_lib ${warning_guard} PUBLIC "$" ) -target_compile_features(NUTC24_lib PUBLIC cxx_std_20) + +target_compile_features(EXCHANGE_lib PUBLIC cxx_std_20) # argparse find_package(argparse REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC argparse::argparse) +target_link_libraries(EXCHANGE_lib PUBLIC argparse::argparse) + # libzip find_package(libzip REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC libzip::zip) +target_link_libraries(EXCHANGE_lib PUBLIC libzip::zip) # Fmt find_package(fmt REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC fmt::fmt) +target_link_libraries(EXCHANGE_lib PUBLIC fmt::fmt) # quill find_package(quill REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC quill::quill) +target_link_libraries(EXCHANGE_lib PUBLIC quill::quill) # rabbitmq find_package(rabbitmq-c REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC rabbitmq::rabbitmq-static) +target_link_libraries(EXCHANGE_lib PUBLIC rabbitmq::rabbitmq-static) # libcurl find_package(CURL REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC CURL::libcurl) +target_link_libraries(EXCHANGE_lib PUBLIC CURL::libcurl) # glaze find_package(glaze REQUIRED) -target_link_libraries(NUTC24_lib PUBLIC glaze::glaze) +target_link_libraries(EXCHANGE_lib PUBLIC glaze::glaze) + +# ---- DASHBOARD ---------- +add_library( + DASHBOARD_lib OBJECT + src/exchange/dashboard/dashboard.cpp +) + +target_link_libraries(DASHBOARD_lib PUBLIC EXCHANGE_lib) + +target_include_directories( + DASHBOARD_lib ${warning_guard} + PUBLIC + "$" +) + +# ncurses +find_package(Curses REQUIRED) +target_link_libraries(DASHBOARD_lib PUBLIC Curses::Curses) + +target_compile_features(DASHBOARD_lib PUBLIC cxx_std_20) # ---- Declare executable ---- -add_executable(NUTC24_exe src/main.cpp) -add_executable(NUTC24::exe ALIAS NUTC24_exe) +add_executable(EXCHANGE_exe src/exchange/main.cpp) +add_executable(EXCHANGE::exe ALIAS EXCHANGE_exe) -set_property(TARGET NUTC24_exe PROPERTY OUTPUT_NAME NUTC24) +set_property(TARGET EXCHANGE_exe PROPERTY OUTPUT_NAME NUTC) -target_compile_features(NUTC24_exe PRIVATE cxx_std_20) +target_compile_features(EXCHANGE_exe PRIVATE cxx_std_20) -target_link_libraries(NUTC24_exe PRIVATE NUTC24_lib) +target_link_libraries(EXCHANGE_exe PRIVATE EXCHANGE_lib) +target_link_libraries(EXCHANGE_exe PRIVATE DASHBOARD_lib) # quill -target_link_libraries(NUTC24_exe PRIVATE quill::quill) +target_link_libraries(EXCHANGE_exe PRIVATE quill::quill) + +# ncurses +target_link_libraries(EXCHANGE_exe PRIVATE Curses::Curses) # fmt -target_link_libraries(NUTC24_exe PRIVATE fmt::fmt) +target_link_libraries(EXCHANGE_exe PRIVATE fmt::fmt) # libzip -target_link_libraries(NUTC24_exe PUBLIC libzip::zip) +target_link_libraries(EXCHANGE_exe PUBLIC libzip::zip) # rabbitmq find_package(rabbitmq-c REQUIRED) -target_link_libraries(NUTC24_exe PRIVATE rabbitmq::rabbitmq-static) +target_link_libraries(EXCHANGE_exe PRIVATE rabbitmq::rabbitmq-static) # curl find_package(CURL REQUIRED) -target_link_libraries(NUTC24_exe PRIVATE CURL::libcurl) +target_link_libraries(EXCHANGE_exe PRIVATE CURL::libcurl) -target_link_libraries(NUTC24_exe PRIVATE argparse::argparse) +target_link_libraries(EXCHANGE_exe PRIVATE argparse::argparse) # glaze find_package(glaze REQUIRED) -target_link_libraries(NUTC24_exe PRIVATE glaze::glaze) +target_link_libraries(EXCHANGE_exe PRIVATE glaze::glaze) + +# ---- WRAPPER ---------- + +add_library( + WRAPPER_lib OBJECT + + src/wrapper/rabbitmq/rabbitmq.cpp + src/wrapper/firebase/firebase.cpp + src/wrapper/pywrapper/pywrapper.cpp + src/wrapper/dev_mode/dev_mode.cpp + src/wrapper/pywrapper/rate_limiter.cpp + # Utils + src/wrapper/logging.cpp +) + +target_include_directories( + WRAPPER_lib ${warning_guard} + PUBLIC + "$" +) + +find_package(Python3 3.12 COMPONENTS Interpreter Development EXACT REQUIRED) +find_package(pybind11 REQUIRED) + +target_link_libraries(WRAPPER_lib PUBLIC fmt::fmt) +target_link_libraries(WRAPPER_lib PUBLIC quill::quill) + +target_link_libraries(WRAPPER_lib PUBLIC argparse::argparse) +target_link_libraries(WRAPPER_lib PUBLIC rabbitmq::rabbitmq-static) +target_link_libraries(WRAPPER_lib PUBLIC CURL::libcurl) +target_link_libraries(WRAPPER_lib PUBLIC glaze::glaze) +target_link_libraries(WRAPPER_lib PUBLIC pybind11::pybind11) +target_link_libraries(WRAPPER_lib PUBLIC Python3::Python) +target_link_libraries(WRAPPER_lib PUBLIC ${Python3_LIBRARIES}) + +add_executable(WRAPPER_exe src/wrapper/main.cpp) +add_executable(WRAPPER::exe ALIAS WRAPPER_exe) + +set_property(TARGET WRAPPER_exe PROPERTY OUTPUT_NAME WRAPPER) + +target_compile_features(WRAPPER_exe PRIVATE cxx_std_20) + +target_link_libraries(WRAPPER_exe PRIVATE WRAPPER_lib) +target_link_libraries(WRAPPER_exe PRIVATE fmt::fmt) +target_link_libraries(WRAPPER_exe PRIVATE quill::quill) + +target_link_libraries(WRAPPER_exe PRIVATE argparse::argparse) +target_link_libraries(WRAPPER_exe PRIVATE rabbitmq::rabbitmq-static) +target_link_libraries(WRAPPER_exe PRIVATE CURL::libcurl) +target_link_libraries(WRAPPER_exe PRIVATE glaze::glaze) +target_link_libraries(WRAPPER_exe PRIVATE pybind11::pybind11) +target_link_libraries(WRAPPER_exe PRIVATE Python3::Python) +target_link_libraries(WRAPPER_exe PRIVATE ${Python3_LIBRARIES}) # ---- Install rules ---- diff --git a/exchange/CMakePresets.json b/exchange/CMakePresets.json index 48239a41..ee9b0045 100644 --- a/exchange/CMakePresets.json +++ b/exchange/CMakePresets.json @@ -65,7 +65,7 @@ "name": "flags-linux", "hidden": true, "cacheVariables": { - "CMAKE_CXX_FLAGS": "-D_FORTIFY_SOURCE=3 -fstack-protector-strong -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", + "CMAKE_CXX_FLAGS": "-D_FORTIFY_SOURCE=3 -fstack-protector-strong -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now", "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now" } @@ -74,7 +74,7 @@ "name": "flags-darwin", "hidden": true, "cacheVariables": { - "CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast" + "CMAKE_CXX_FLAGS": "-Werror -fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast" } }, { @@ -90,7 +90,7 @@ "name": "flags-windows-mingw", "hidden": true, "cacheVariables": { - "CMAKE_CXX_FLAGS": "-D_FORTIFY_SOURCE=3 -fstack-protector-strong -fcf-protection=full -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", + "CMAKE_CXX_FLAGS": "-D_FORTIFY_SOURCE=3 -fstack-protector-strong -fcf-protection=full -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed", "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed" } diff --git a/exchange/Dockerfile b/exchange/Dockerfile index 6f49d271..1fd9de0b 100644 --- a/exchange/Dockerfile +++ b/exchange/Dockerfile @@ -1,28 +1,4 @@ -#should be run from root: -#sudo docker build -t nutc24:latest . -f exchange/Dockerfile - -# Build stage for wrapper -FROM python:3.11-slim as build-wrapper - -RUN pip install conan numpy pandas polars scipy scikit-learn \ - # c++ stuff - && apt update \ - && apt install -y --no-install-recommends build-essential libssl-dev cmake git - -WORKDIR /app/wrapper -COPY ./wrapper/conanfile.py /app/wrapper -COPY ./.github/scripts/conan-profile.sh /app/wrapper - -RUN cat conan-profile.sh | bash \ - && conan install . -b missing - -COPY ./wrapper /app/wrapper -RUN cmake --preset=ci-docker \ - && cmake --build build --config Release -j - -# Build stage for exchange - -FROM python:3.11-slim as build-exchange +FROM python:3.12 as build RUN pip install conan numpy pandas polars scipy scikit-learn \ # c++ stuff @@ -41,19 +17,20 @@ RUN cmake --preset=ci-docker \ && cmake --build build --config Release -j # Main stage for exchange -FROM python:3.11-slim as run-exchange +FROM python:3.12 as run-exchange RUN apt update \ && apt install -y rabbitmq-server \ && pip install numpy pandas polars scipy scikit-learn -COPY --from=build-wrapper /app/wrapper/build/NUTC-client /bin/NUTC-client -COPY --from=build-exchange /app/exchange/build/NUTC24 /bin/NUTC-exchange +COPY --from=build /app/exchange/build/NUTC /bin/NUTC-exchange +COPY --from=build /app/exchange/build/WRAPPER /bin/WRAPPER COPY ./exchange/scripts/docker-start.sh /app/docker-start.sh -RUN chmod +x /bin/NUTC-client /bin/NUTC-exchange /app/docker-start.sh +RUN chmod +x /bin/NUTC-exchange /bin/WRAPPER /app/docker-start.sh ENV RABBITMQ_DEFAULT_USER="NUFT" ENV RABBITMQ_DEFAULT_PASS="ADMIN" +ENV NUTC_WRAPPER_BINARY_PATH="/bin/WRAPPER" ENTRYPOINT ["./app/docker-start.sh"] diff --git a/exchange/Taskfile.yml b/exchange/Taskfile.yml index 8561a8dc..6cd1d7c8 100644 --- a/exchange/Taskfile.yml +++ b/exchange/Taskfile.yml @@ -1,7 +1,7 @@ version: '3' env: - NAME: NUTC24 + NAME: NUTC tasks: deps: @@ -14,8 +14,12 @@ tasks: cmds: - cmake -D FIX=YES -P cmake/lint.cmake + new-log-dir: + cmds: + - rm -rf logs + - mkdir logs + init: - dir: '{{.USER_WORKING_DIR}}' preconditions: - test -f CMakeUserPresets.json cmds: @@ -32,21 +36,52 @@ tasks: - test -f CMakeUserPresets.json cmds: - cmake --build --preset=dev -j + + build-no-parallel: + dir: '{{.USER_WORKING_DIR}}' + preconditions: + - test -f CMakeUserPresets.json + cmds: + - cmake --build --preset=dev run: - dir: '{{.USER_WORKING_DIR}}' + env: + NUTC_WRAPPER_BINARY_PATH: "./build/dev/WRAPPER" + cmds: + - task: build + - task: check-and-start-services + - task: new-log-dir + - ./build/dev/{{.NAME}} + + bots: + env: + NUTC_WRAPPER_BINARY_PATH: "./build/dev/WRAPPER" cmds: - task: build - task: check-and-start-services - - ./build/dev/{{.NAME}} + - task: new-log-dir + - ./build/dev/{{.NAME}} --bots-only + run-with-gdb: + env: + NUTC_WRAPPER_BINARY_PATH: "./build/dev/WRAPPER" + cmds: + - task: build + - task: check-and-start-services + - task: new-log-dir + - gdb ./build/dev/{{.NAME}} + dev: + env: + NUTC_WRAPPER_BINARY_PATH: "./build/dev/WRAPPER" cmds: - task: build - task: check-and-start-services - ./build/dev/{{.NAME}} --dev sandbox: + env: + NUTC_WRAPPER_BINARY_PATH: "./build/dev/WRAPPER" cmds: - task: build - task: check-and-start-services @@ -65,18 +100,26 @@ tasks: - task: utest - task: itest + + utest-with-gdb: + cmds: + - task: build + - gdb --args ./build/dev/test/NUTC_tests --preset=dev -R "Unit*" + utest: cmds: - task: build - ctest --preset=dev -R "Unit*" itest: + env: + NUTC_WRAPPER_BINARY_PATH: "../WRAPPER" cmds: - task: build - task: check-and-start-services - mkdir -p build/dev/test/test_algos - cp -r test/test_algos/* build/dev/test/test_algos - - ctest --timeout 3 --preset=dev -R "Integration*" + - ctest --timeout 4 --preset=dev -R "Integration*" docs: dir: '{{.USER_WORKING_DIR}}' @@ -93,7 +136,6 @@ tasks: cmds: - task: run - lint: cmds: - cmake -D FIX=YES -P cmake/lint.cmake diff --git a/exchange/cmake/dev-mode.cmake b/exchange/cmake/dev-mode.cmake index 79bd2265..e376dca9 100644 --- a/exchange/cmake/dev-mode.cmake +++ b/exchange/cmake/dev-mode.cmake @@ -7,10 +7,10 @@ endif() add_custom_target( run-exe - COMMAND NUTC24_exe + COMMAND EXCHANGE_exe VERBATIM ) -add_dependencies(run-exe NUTC24_exe) +add_dependencies(run-exe EXCHANGE_exe) option(BUILD_MCSS_DOCS "Build documentation using Doxygen and m.css" OFF) if(BUILD_MCSS_DOCS) diff --git a/exchange/cmake/install-rules.cmake b/exchange/cmake/install-rules.cmake index 62b80cfd..97cd46ff 100644 --- a/exchange/cmake/install-rules.cmake +++ b/exchange/cmake/install-rules.cmake @@ -1,6 +1,6 @@ install( - TARGETS NUTC24_exe - RUNTIME COMPONENT NUTC24_Runtime + TARGETS EXCHANGE_exe + RUNTIME COMPONENT EXCHANGE_runtime ) if(PROJECT_IS_TOP_LEVEL) diff --git a/exchange/conanfile.py b/exchange/conanfile.py index 61bb6dbe..3953454f 100644 --- a/exchange/conanfile.py +++ b/exchange/conanfile.py @@ -9,13 +9,18 @@ def layout(self): self.folders.generators = "conan" def requirements(self): + # Exchange self.requires("fmt/[>=10.1.0]") - self.requires("quill/2.9.2") + self.requires("quill/3.3.1") self.requires("rabbitmq-c/0.13.0") self.requires("libcurl/8.2.1") self.requires("argparse/2.9") self.requires("glaze/1.3.5") self.requires("libzip/1.10.1") + self.requires("ncurses/6.4") + + # Wrapper + self.requires("pybind11/2.10.4") def build_requirements(self): self.test_requires("gtest/1.13.0") diff --git a/exchange/src/client_manager/client_manager.cpp b/exchange/src/client_manager/client_manager.cpp deleted file mode 100644 index b8c0117e..00000000 --- a/exchange/src/client_manager/client_manager.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "client_manager.hpp" - -namespace nutc { -namespace manager { - -void -ClientManager::set_client_pid(const std::string& user_id, pid_t pid) -{ - if (!user_exists_(user_id)) - return; - - clients_[user_id].pid = pid; -} - -void -ClientManager::modify_holdings( - const std::string& user_id, const std::string& ticker, float change_in_holdings -) -{ - if (!user_exists_(user_id)) - return; - - if (!user_holds_stock_(user_id, ticker)) { - clients_[user_id].holdings[ticker] = change_in_holdings; - return; - } - - clients_[user_id].holdings[ticker] += change_in_holdings; -} - -std::optional -ClientManager::validate_match(const messages::Match& match) const -{ - float trade_value = match.price * match.quantity; - float remaining_capital = get_capital(match.buyer_id) - trade_value; - bool insufficient_holdings = - get_holdings(match.seller_id, match.ticker) - match.quantity < 0; - - if (remaining_capital < 0) [[unlikely]] - return messages::SIDE::BUY; - - if (match.seller_id != "SIMULATED" && insufficient_holdings) [[unlikely]] - return messages::SIDE::SELL; - - return std::nullopt; -} - -void -ClientManager::set_active(const std::string& user_id) -{ - if (!user_exists_(user_id)) - return; - - clients_[user_id].active = true; -} - -} // namespace manager -} // namespace nutc diff --git a/exchange/src/client_manager/client_manager.hpp b/exchange/src/client_manager/client_manager.hpp deleted file mode 100644 index 312474ae..00000000 --- a/exchange/src/client_manager/client_manager.hpp +++ /dev/null @@ -1,129 +0,0 @@ -#pragma once -// keep track of active users and account information -#include "config.h" -#include "utils/messages.hpp" - -#include - -#include -#include -#include - -namespace nutc { -/** - * @brief Handles client tracking and capital management - * Keeps track of all clients, whether they're active, and their capital - */ -namespace manager { - -enum class ClientLocation { LOCAL, REMOTE }; - -struct client_t { - std::string uid; - pid_t pid; - std::string algo_id; - bool active; - ClientLocation algo_location; - float capital_remaining; - std::unordered_map holdings; -}; - -class ClientManager { - std::unordered_map clients_; - -public: - void - add_client( - const std::string& user_id, const std::string& algo_id, - ClientLocation algo_location - ) - { - clients_[user_id] = - client_t{user_id, {}, algo_id, false, algo_location, STARTING_CAPITAL, {}}; - } - - void - modify_capital(const std::string& user_id, float change_in_capital) - { - if (!user_exists_(user_id)) - return; - - clients_[user_id].capital_remaining += change_in_capital; - } - - float - get_capital(const std::string& user_id) const - { - if (!user_exists_(user_id)) - return 0.0f; - - return clients_.at(user_id).capital_remaining; - } - - void set_client_test(const std::string& user_id); - void set_client_pid(const std::string& user_id, pid_t pid); - void initialize_from_firebase(const glz::json_t::object_t& users); - void set_active(const std::string& user_id); - - float - get_holdings(const std::string& user_id, const std::string& ticker) const - { - if (!user_holds_stock_(user_id, ticker)) - return 0.0f; - - return clients_.at(user_id).holdings.at(ticker); - } - - inline const std::unordered_map& - get_clients() const - { - return clients_; - } - - void modify_holdings( - const std::string& user_id, const std::string& ticker, float change_in_holdings - ); - - [[nodiscard]] std::optional - validate_match(const messages::Match& match) const; - -private: - bool - user_exists_(const std::string& user_id) const - { - return clients_.contains(user_id); - } - - bool - user_holds_stock_(const std::string& user_id, const std::string& ticker) const - { - if (!user_exists_(user_id)) [[unlikely]] - return false; - - return clients_.at(user_id).holdings.contains(ticker); - } -}; - -} // namespace manager -} // namespace nutc - -/* I don't think we should need this? It was already here and I don't want to break -anything -/// \cond -template <> -struct glz::meta { - using t = nutc::manager::client_t; - static constexpr auto value = object( // NOLINT (*) - "uid", &t::uid, "active", &t::active, "is_local_algo", &t::is_local_algo, - "capital_remaining", &t::capital_remaining, "holdings", &t::holdings - ); -}; - -/// \cond -template <> -struct glz::meta { - static constexpr auto value = object( // NOLINT (*) - "clients", [](auto&& self) { return self.get_clients(); } - ); -}; -*/ diff --git a/exchange/src/algos/algo_manager.hpp b/exchange/src/exchange/algos/algo_manager.hpp similarity index 92% rename from exchange/src/algos/algo_manager.hpp rename to exchange/src/exchange/algos/algo_manager.hpp index 242bee6c..0f3af4b7 100644 --- a/exchange/src/algos/algo_manager.hpp +++ b/exchange/src/exchange/algos/algo_manager.hpp @@ -1,6 +1,6 @@ #pragma once -#include "client_manager/client_manager.hpp" +#include "exchange/traders/trader_manager.hpp" namespace nutc { namespace algo_mgmt { @@ -30,7 +30,7 @@ class AlgoManager { virtual void initialize_client_manager(manager::ClientManager& manager) = 0; - virtual size_t get_num_clients() const = 0; + [[nodiscard]] virtual size_t get_num_clients() const = 0; virtual ~AlgoManager() = default; diff --git a/exchange/src/algos/dev_mode/dev_mode.cpp b/exchange/src/exchange/algos/dev_mode/dev_mode.cpp similarity index 76% rename from exchange/src/algos/dev_mode/dev_mode.cpp rename to exchange/src/exchange/algos/dev_mode/dev_mode.cpp index 4af9d3a6..9ffe11d6 100644 --- a/exchange/src/algos/dev_mode/dev_mode.cpp +++ b/exchange/src/exchange/algos/dev_mode/dev_mode.cpp @@ -1,8 +1,8 @@ #include "dev_mode.hpp" -#include "config.h" -#include "logging.hpp" -#include "utils/file_operations/file_operations.hpp" +#include "exchange/config.h" +#include "exchange/logging.hpp" +#include "shared/file_operations/file_operations.hpp" #include @@ -12,17 +12,15 @@ namespace algo_mgmt { void DevModeAlgoManager::initialize_client_manager(manager::ClientManager& users) { - using manager::ClientLocation; - auto handle_algos_provided_filenames = [&]() { - for (const std::string& name : algo_filenames_.value()) - users.add_client(name, name, ClientLocation::LOCAL); + for (const std::string& filepath : algo_filenames_.value()) + users.add_local_trader(filepath); }; auto handle_algos_default_filenames = [&]() { for (size_t i = 0; i < num_clients_; i++) { std::string algo_id = std::string(ALGO_DIR) + "/algo_" + std::to_string(i); - users.add_client(algo_id, algo_id, ClientLocation::LOCAL); + users.add_local_trader(algo_id); } }; @@ -35,7 +33,10 @@ DevModeAlgoManager::initialize_client_manager(manager::ClientManager& users) void DevModeAlgoManager::initialize_files() const { - std::string content = file_ops::read_file_content("./template.py"); + if (algo_filenames_.has_value()) + return; + + std::string content = file_ops::read_file_content("template.py"); std::string dir_name = std::string(ALGO_DIR); if (!file_ops::create_directory(dir_name)) { throw std::runtime_error("Failed to create directory"); diff --git a/exchange/src/algos/dev_mode/dev_mode.hpp b/exchange/src/exchange/algos/dev_mode/dev_mode.hpp similarity index 67% rename from exchange/src/algos/dev_mode/dev_mode.hpp rename to exchange/src/exchange/algos/dev_mode/dev_mode.hpp index 3c0144f6..1b746d03 100644 --- a/exchange/src/algos/dev_mode/dev_mode.hpp +++ b/exchange/src/exchange/algos/dev_mode/dev_mode.hpp @@ -1,7 +1,7 @@ #pragma once -#include "algos/algo_manager.hpp" -#include "client_manager/client_manager.hpp" +#include "exchange/algos/algo_manager.hpp" +#include "exchange/traders/trader_manager.hpp" #include @@ -15,18 +15,15 @@ class DevModeAlgoManager : public AlgoManager { public: explicit DevModeAlgoManager(size_t num_clients) : num_clients_(num_clients) {} - explicit DevModeAlgoManager( - size_t num_clients, const std::vector& filenames - ) : - num_clients_(num_clients), - algo_filenames_(filenames) + explicit DevModeAlgoManager(const std::vector& filenames) : + num_clients_(filenames.size()), algo_filenames_(filenames) {} void initialize_client_manager(manager::ClientManager& users) override; void initialize_files() const override; - size_t + [[nodiscard]] size_t get_num_clients() const override { return num_clients_; diff --git a/exchange/src/algos/normal_mode/normal_mode.cpp b/exchange/src/exchange/algos/normal_mode/normal_mode.cpp similarity index 79% rename from exchange/src/algos/normal_mode/normal_mode.cpp rename to exchange/src/exchange/algos/normal_mode/normal_mode.cpp index 8eb60dc4..ab1d84bd 100644 --- a/exchange/src/algos/normal_mode/normal_mode.cpp +++ b/exchange/src/exchange/algos/normal_mode/normal_mode.cpp @@ -1,6 +1,7 @@ #include "normal_mode.hpp" -#include "curl/curl.hpp" +#include "exchange/config.h" +#include "exchange/curl/curl.hpp" namespace nutc { namespace algo_mgmt { @@ -8,17 +9,13 @@ namespace algo_mgmt { void NormalModeAlgoManager::initialize_client_manager(manager::ClientManager& users) { - using manager::ClientLocation; - num_clients_ = 0; glz::json_t::object_t firebase_users = get_all_users(); for (const auto& [id, user] : firebase_users) { if (!user.contains("latestAlgoId")) continue; - users.add_client( - id, user["latestAlgoId"].get(), ClientLocation::REMOTE - ); + users.add_remote_trader(id, user["latestAlgoId"].get()); num_clients_ += 1; } } diff --git a/exchange/src/algos/normal_mode/normal_mode.hpp b/exchange/src/exchange/algos/normal_mode/normal_mode.hpp similarity index 89% rename from exchange/src/algos/normal_mode/normal_mode.hpp rename to exchange/src/exchange/algos/normal_mode/normal_mode.hpp index a6aa6c79..920aa523 100644 --- a/exchange/src/algos/normal_mode/normal_mode.hpp +++ b/exchange/src/exchange/algos/normal_mode/normal_mode.hpp @@ -1,6 +1,6 @@ #pragma once -#include "algos/algo_manager.hpp" +#include "exchange/algos/algo_manager.hpp" namespace nutc { namespace algo_mgmt { @@ -17,7 +17,7 @@ class NormalModeAlgoManager : public AlgoManager { {} // WARNING!! not initialized until initialize_client_manager is run - size_t + [[nodiscard]] size_t get_num_clients() const override { return num_clients_; diff --git a/exchange/src/algos/sandbox_mode/sandbox_mode.cpp b/exchange/src/exchange/algos/sandbox_mode/sandbox_mode.cpp similarity index 75% rename from exchange/src/algos/sandbox_mode/sandbox_mode.cpp rename to exchange/src/exchange/algos/sandbox_mode/sandbox_mode.cpp index 8e7b044a..b95c3cce 100644 --- a/exchange/src/algos/sandbox_mode/sandbox_mode.cpp +++ b/exchange/src/exchange/algos/sandbox_mode/sandbox_mode.cpp @@ -1,8 +1,9 @@ #include "sandbox_mode.hpp" -#include "curl/curl.hpp" -#include "logging.hpp" -#include "utils/file_operations/file_operations.hpp" +#include "exchange/curl/curl.hpp" +#include "exchange/logging.hpp" +#include "exchange/traders/trader_types.hpp" +#include "shared/file_operations/file_operations.hpp" #include @@ -11,20 +12,17 @@ namespace algo_mgmt { void SandboxAlgoManager::initialize_client_manager(manager::ClientManager& users) { - using manager::ClientLocation; - num_clients_ = 0; // check number of algos in algos directory for (const auto& entry : std::filesystem::directory_iterator(ALGO_DIR)) { std::string algo_id = entry.path().filename().string(); algo_id = algo_id.substr(0, algo_id.find(".py")); - log_i(sandbox, "Adding client: {}", algo_id); - users.add_client(algo_id, algo_id, ClientLocation::LOCAL); + users.add_local_trader(algo_id); num_clients_ += 1; } - users.add_client(user_id_, algo_id_, ClientLocation::REMOTE); + users.add_remote_trader(user_id_, algo_id_); num_clients_ += 1; } diff --git a/exchange/src/algos/sandbox_mode/sandbox_mode.hpp b/exchange/src/exchange/algos/sandbox_mode/sandbox_mode.hpp similarity index 83% rename from exchange/src/algos/sandbox_mode/sandbox_mode.hpp rename to exchange/src/exchange/algos/sandbox_mode/sandbox_mode.hpp index c6d7ea46..c4efb3a1 100644 --- a/exchange/src/algos/sandbox_mode/sandbox_mode.hpp +++ b/exchange/src/exchange/algos/sandbox_mode/sandbox_mode.hpp @@ -1,7 +1,7 @@ #pragma once -#include "algos/algo_manager.hpp" -#include "client_manager/client_manager.hpp" +#include "exchange/algos/algo_manager.hpp" +#include "exchange/traders/trader_manager.hpp" namespace nutc { namespace algo_mgmt { @@ -20,7 +20,7 @@ class SandboxAlgoManager : public AlgoManager { void initialize_files() const override; - size_t + [[nodiscard]] size_t get_num_clients() const override { return num_clients_; diff --git a/exchange/src/exchange/bots/bot_container.cpp b/exchange/src/exchange/bots/bot_container.cpp new file mode 100644 index 00000000..6d0d59ee --- /dev/null +++ b/exchange/src/exchange/bots/bot_container.cpp @@ -0,0 +1,219 @@ +#include "bot_container.hpp" + +#include "exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp" +#include "exchange/tickers/manager/ticker_manager.hpp" +#include "exchange/traders/trader_manager.hpp" + +#include + +#include +#include + +double +generate_gaussian_noise(double mean, double stddev) +{ + static std::random_device rand; + static std::mt19937 gen(rand()); + std::normal_distribution<> distr(mean, stddev); // Define the normal distribution + + return distr(gen); +} + +namespace nutc { + +namespace bots { +void +BotContainer::on_tick(uint64_t) +{ + auto theo = fabs(theo_generator_.generate_next_price() + brownian_offset_); + auto& ticker = engine_manager::EngineManager::get_instance().get_engine(ticker_); + double current = ticker.get_midprice(); + auto orders = BotContainer::on_new_theo(theo, current); + + for (auto& order : orders) { + order.ticker = ticker_; + rabbitmq::RabbitMQOrderHandler::handle_incoming_market_order( + engine_manager::EngineManager::get_instance(), + manager::ClientManager::get_instance(), std::move(order) + ); + } +} + +void +BotContainer::add_retail_bots( + double mean_capital, double stddev_capital, size_t num_bots +) +{ + std::random_device rand; + std::mt19937 gen(rand()); + std::normal_distribution<> distr(mean_capital, stddev_capital); + for (size_t i = 0; i < num_bots; i++) { + auto capital = distr(gen); + capital = std::abs(capital); + add_retail_bot_(capital); + } +} + +void +BotContainer::add_mm_bots(double mean_capital, double stddev_capital, size_t num_bots) +{ + std::random_device rand; + std::mt19937 gen(rand()); + std::normal_distribution<> distr(mean_capital, stddev_capital); + for (size_t i = 0; i < num_bots; i++) { + auto capital = distr(gen); + capital = std::abs(capital); + add_mm_bot_(capital); + } +} + +void +BotContainer::add_retail_bot_(double starting_capital) +{ + assert(starting_capital > 0); + manager::ClientManager& users = nutc::manager::ClientManager::get_instance(); + std::string bot_id = users.add_bot_trader(); + + retail_bots_.emplace( + std::piecewise_construct, std::forward_as_tuple(bot_id), + std::forward_as_tuple(bot_id, starting_capital) + ); + const auto& bot = users.get_trader(bot_id); + assert(bot->get_type() == manager::BOT); + bot->set_capital(starting_capital); + // bot->modify_holdings(ticker_, INFINITY); +} + +void +BotContainer::add_mm_bot_(double starting_capital) +{ + assert(starting_capital > 0); + manager::ClientManager& users = nutc::manager::ClientManager::get_instance(); + std::string bot_id = users.add_bot_trader(); + + market_makers_.emplace( + std::piecewise_construct, std::forward_as_tuple(bot_id), + std::forward_as_tuple(bot_id, starting_capital) + ); + const auto& bot = users.get_trader(bot_id); + assert(bot->get_type() == manager::BOT); + bot->set_capital(starting_capital); + bot->modify_holdings(ticker_, INFINITY); +} + +std::vector +BotContainer::on_new_theo(double new_theo, double current) +{ + auto mm_new_theo = [new_theo](auto& mm_trader, std::vector& orders) { + double noised_theo = + new_theo + static_cast(generate_gaussian_noise(0, .02)); + std::vector mm_orders = + mm_trader.take_action(noised_theo); + orders.insert(orders.end(), mm_orders.begin(), mm_orders.end()); + }; + + auto retail_new_theo = + [new_theo, current](auto& retail_trader, std::vector& orders) { + double noised_theo = + new_theo + static_cast(generate_gaussian_noise(0, .1)); + auto bot_order = retail_trader.take_action(current, noised_theo); + if (bot_order.has_value()) + orders.push_back(bot_order.value()); + }; + + std::vector orders{}; + for (auto& [_, mm_trader] : market_makers_) { + mm_new_theo(mm_trader, orders); + } + + for (auto& [_, bot] : retail_bots_) { + retail_new_theo(bot, orders); + } + + return orders; +} + +void +BotContainer::process_order_match(Match& match) +{ + auto process_buyer_match = [&match](auto& umap) { + auto buyer_match = umap.find(match.buyer_id); + if (buyer_match == umap.end()) + return; + buyer_match->second.modify_held_stock(-match.quantity); + buyer_match->second.modify_capital(-match.quantity * match.price); + }; + + auto process_seller_match = [&match](auto& umap) { + auto seller_match = umap.find(match.seller_id); + if (seller_match == umap.end()) + return; + seller_match->second.modify_held_stock(match.quantity); + seller_match->second.modify_capital(match.quantity * match.price); + }; + + process_buyer_match(market_makers_); + process_buyer_match(retail_bots_); + process_seller_match(market_makers_); + process_seller_match(retail_bots_); +} + +void +BotContainer::process_order_add( + const std::string& bot_id, messages::SIDE side, double total_cap +) +{ + auto process_order_add = [side, total_cap](auto& match) { + if (side == messages::SIDE::BUY) { + match->second.modify_long_capital(total_cap); + match->second.modify_open_bids(1); + } + else { + match->second.modify_short_capital(total_cap); + match->second.modify_open_asks(1); + } + }; + + auto retail_match = retail_bots_.find(bot_id); + if (retail_match != retail_bots_.end()) { + process_order_add(retail_match); + return; + } + auto mm_match = market_makers_.find(bot_id); + if (mm_match != market_makers_.end()) { + process_order_add(mm_match); + return; + } + throw std::runtime_error("Bot not found"); +} + +void +BotContainer::process_order_expiration( + const std::string& bot_id, messages::SIDE side, double total_cap +) +{ + auto process_order_expiration = [side, total_cap](auto match) { + if (side == messages::SIDE::BUY) { + match->second.modify_long_capital(-total_cap); + match->second.modify_open_bids(-1); + } + else { + match->second.modify_short_capital(-total_cap); + match->second.modify_open_asks(-1); + } + }; + + auto retail_match = retail_bots_.find(bot_id); + if (retail_match != retail_bots_.end()) { + process_order_expiration(retail_match); + return; + } + auto match1 = market_makers_.find(bot_id); + if (match1 != market_makers_.end()) { + process_order_expiration(match1); + return; + } +} + +} // namespace bots +} // namespace nutc diff --git a/exchange/src/exchange/bots/bot_container.hpp b/exchange/src/exchange/bots/bot_container.hpp new file mode 100644 index 00000000..d642b167 --- /dev/null +++ b/exchange/src/exchange/bots/bot_container.hpp @@ -0,0 +1,65 @@ +#pragma once +#include "exchange/bots/bot_types/market_maker.hpp" +#include "exchange/bots/bot_types/retail.hpp" +#include "exchange/theo/brownian.hpp" +#include "exchange/tick_manager/tick_observer.hpp" +#include "shared/messages_exchange_to_wrapper.hpp" + +namespace nutc { + +namespace bots { +using MarketOrder = messages::MarketOrder; +using Match = messages::Match; + +class BotContainer : public ticks::TickObserver { +public: + void on_tick(uint64_t) override; + + double + get_theo() const + { + return brownian_offset_ + theo_generator_.get_price(); + } + + const std::unordered_map& + get_market_makers() const + { + return market_makers_; + } + + const std::unordered_map& + get_retail_traders() const + { + return retail_bots_; + } + + std::vector on_new_theo(double new_theo, double current); + + void process_order_expiration( + const std::string& bot_id, messages::SIDE side, double total_cap + ); + void + process_order_add(const std::string& bot_id, messages::SIDE side, double total_cap); + void process_order_match(Match& match); + + void add_retail_bots(double mean_capital, double stddev_capital, size_t num_bots); + void add_mm_bots(double mean_capital, double stddev_capital, size_t num_bots); + + BotContainer() = default; + + explicit BotContainer(std::string ticker, double starting_price) : + ticker_(std::move(ticker)), brownian_offset_(starting_price) + {} + +private: + void add_mm_bot_(double starting_capital); + void add_retail_bot_(double starting_capital); + std::unordered_map retail_bots_{}; + std::unordered_map market_makers_{}; + std::string ticker_; + + stochastic::BrownianMotion theo_generator_{}; + double brownian_offset_ = 0.0; +}; +} // namespace bots +} // namespace nutc diff --git a/exchange/src/exchange/bots/bot_types/generic_bot.hpp b/exchange/src/exchange/bots/bot_types/generic_bot.hpp new file mode 100644 index 00000000..8227b61a --- /dev/null +++ b/exchange/src/exchange/bots/bot_types/generic_bot.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include + +#include + +namespace nutc { +namespace bots { + +class GenericBot { + const std::string BOT_ID; + double short_interest_ = 0; + double long_interest_ = 0; + + size_t open_bids_ = 0; // for stats, not the strategy + size_t open_asks_ = 0; + + const double INTEREST_LIMIT; + double capital_ = 0; + double held_stock_ = 0; + +public: + [[nodiscard]] double + get_capital() const + { + return capital_; + } + + void + modify_capital(double delta) + { + capital_ += delta; + } + + [[nodiscard]] double + get_held_stock() const + { + return held_stock_; + } + + void + modify_held_stock(double delta) + { + held_stock_ += delta; + } + + [[nodiscard]] const std::string& + get_id() const + { + return BOT_ID; + } + + void + modify_short_capital(double delta) + { + short_interest_ += delta; + } + + void + modify_long_capital(double delta) + { + long_interest_ += delta; + } + + [[nodiscard]] double + get_long_interest() const + { + return long_interest_; + } + + [[nodiscard]] double + get_interest_limit() const + { + return INTEREST_LIMIT; + } + + [[nodiscard]] double + get_short_interest() const + { + return short_interest_; + } + + [[nodiscard]] size_t + get_open_bids() const + { + return open_bids_; + } + + [[nodiscard]] size_t + get_open_asks() const + { + return open_asks_; + } + + [[nodiscard]] double + get_capital_utilization() const + { + return (get_long_interest() + get_short_interest()) / get_interest_limit(); + } + + void + modify_open_bids(int delta) + { + if (delta < 0) { + assert(open_bids_ >= static_cast(std::abs(delta))); + open_bids_ -= static_cast(std::abs(delta)); + } + else { + open_bids_ += static_cast(delta); + } + } + + void + modify_open_asks(int delta) + { + if (delta < 0) { + assert(open_asks_ >= static_cast(std::abs(delta))); + open_asks_ -= static_cast(std::abs(delta)); + } + else { + open_asks_ += static_cast(delta); + } + } + + virtual ~GenericBot() = default; + [[nodiscard]] virtual bool is_active() const = 0; + + GenericBot(std::string bot_id, double interest_limit) : + BOT_ID(std::move(bot_id)), INTEREST_LIMIT(interest_limit) + {} + + GenericBot(const GenericBot& other) = default; + GenericBot(GenericBot&& other) = default; + GenericBot& operator=(const GenericBot& other) = delete; + GenericBot& operator=(GenericBot&& other) = delete; +}; + +} // namespace bots +} // namespace nutc diff --git a/exchange/src/exchange/bots/bot_types/market_maker.hpp b/exchange/src/exchange/bots/bot_types/market_maker.hpp new file mode 100644 index 00000000..df62c9be --- /dev/null +++ b/exchange/src/exchange/bots/bot_types/market_maker.hpp @@ -0,0 +1,96 @@ +#pragma once +#include "generic_bot.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" + +#include + +namespace nutc { +namespace bots { + +class MarketMakerBot : public GenericBot { +public: + MarketMakerBot(std::string bot_id, double interest_limit) : + GenericBot(std::move(bot_id), interest_limit) + {} + + static constexpr double BASE_SPREAD = 0.16; + + std::vector + take_action(double new_theo) + { + double long_interest = get_long_interest(); + double short_interest = get_short_interest(); + + double interest_limit = get_interest_limit(); + + static constexpr uint8_t LEVELS = 6; + std::vector orders(LEVELS); + + std::array quantities = {1.0 / 12, 1.0 / 6, 1.0 / 4, + 1.0 / 4, 1.0 / 6, 1.0 / 12}; + + std::array prices = { + new_theo - BASE_SPREAD - .10, new_theo - BASE_SPREAD - .05, + new_theo - BASE_SPREAD, new_theo + BASE_SPREAD, + new_theo + BASE_SPREAD + .05, new_theo + BASE_SPREAD * .10, + }; + + double capital_tolerance = compute_capital_tolerance_(); + double lean = + -1 * ((long_interest - short_interest) / (long_interest + short_interest)) + * interest_limit * 2.7; // NOLINT(*) + + if (true || long_interest + short_interest == 0) + lean = 0; + for (auto& price : prices) { + price += lean; + } + + double avg_price = 0; + for (size_t i = 0; i < LEVELS; ++i) { + avg_price += prices[i] * quantities[i]; + } + + double total_quantity = capital_tolerance / avg_price; + + for (size_t i = 0; i < LEVELS; ++i) { + auto side = (i < LEVELS / 2) ? messages::SIDE::BUY : messages::SIDE::SELL; + orders[i] = messages::MarketOrder{ + GenericBot::get_id(), side, "", total_quantity * quantities[i], + prices[i] + }; + if (side == messages::SIDE::BUY) { + modify_long_capital(total_quantity * quantities[i] * prices[i]); + } + else { + modify_short_capital(total_quantity * quantities[i] * prices[i]); + } + } + modify_open_bids(LEVELS / 2); + modify_open_asks(LEVELS / 2); + + return orders; + } + + [[nodiscard]] bool + is_active() const override + { + return true; + } + +private: + [[nodiscard]] double + compute_net_exposure_() const + { + return (get_long_interest() - get_short_interest()); + } + + double + compute_capital_tolerance_() + { + return (1 - get_capital_utilization()) * (get_interest_limit() / 3); + } +}; + +} // namespace bots +} // namespace nutc diff --git a/exchange/src/exchange/bots/bot_types/retail.cpp b/exchange/src/exchange/bots/bot_types/retail.cpp new file mode 100644 index 00000000..0e03bb41 --- /dev/null +++ b/exchange/src/exchange/bots/bot_types/retail.cpp @@ -0,0 +1,76 @@ +#include "retail.hpp" + +namespace nutc { +namespace bots { + +bool +RetailBot::is_active() const +{ + return get_capital() + > -get_interest_limit() * .9; // drop out if they lose 90% of their money +} + +std::optional +RetailBot::take_action(double current, double theo) +{ + if (!is_active()) { + return std::nullopt; + } + double p_trade = (1 - get_capital_utilization()); + + std::uniform_real_distribution<> dis(0.0, 1.0); + double noise_factor = dis(gen); + + double signal_strength = AGGRESSIVENESS * std::abs(theo - current) / current; + + if (poisson_dist(gen) > 0) { + if (noise_factor < p_trade * signal_strength) { + if (current < theo) { + double price = current; + assert(price > 0); + double quantity = + (1 - get_capital_utilization()) * get_interest_limit() / price; + quantity *= .01; + modify_open_bids(1); + modify_long_capital(quantity * price); + return messages::MarketOrder{ + get_id(), messages::SIDE::BUY, "", quantity, price + }; + } + if (current > theo) { + double price = current; + assert(price > 0); + double quantity = + (1 - get_capital_utilization()) * get_interest_limit() / price; + quantity *= .01; + modify_open_asks(1); + modify_short_capital(quantity * price); + return messages::MarketOrder{ + get_id(), messages::SIDE::SELL, "", quantity, price + }; + } + } + } + // hold + return std::nullopt; +} + +double +RetailBot::calculate_order_price( + messages::SIDE side, double current_price, double theo_price, double buffer_percent +) +{ + double price_difference = std::abs(theo_price - current_price); + + double signal_strength_adjustment = price_difference * 0.5; + + double buffer_amount = current_price * buffer_percent; + + if (side == messages::SIDE::BUY) { + return current_price - buffer_amount - signal_strength_adjustment; + } + return current_price + buffer_amount + signal_strength_adjustment; +} + +} // namespace bots +} // namespace nutc diff --git a/exchange/src/exchange/bots/bot_types/retail.hpp b/exchange/src/exchange/bots/bot_types/retail.hpp new file mode 100644 index 00000000..f44973f8 --- /dev/null +++ b/exchange/src/exchange/bots/bot_types/retail.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "generic_bot.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" + +namespace nutc { + +namespace bots { + +class RetailBot : public GenericBot { + std::random_device rd{}; + std::mt19937 gen{}; + std::poisson_distribution<> poisson_dist{}; + +public: + RetailBot(std::string bot_id, double interest_limit) : + GenericBot(std::move(bot_id), interest_limit), + AGGRESSIVENESS(std::normal_distribution<>{50, 2000}(gen)) + {} + + [[nodiscard]] bool is_active() const override; + + std::optional take_action(double current, double theo); + +private: + static double calculate_order_price( + messages::SIDE side, double current_price, double theo_price, + double buffer_percent = 0.02 + ); + + const double AGGRESSIVENESS; +}; + +} // namespace bots + +} // namespace nutc diff --git a/exchange/src/config.h b/exchange/src/exchange/config.h similarity index 79% rename from exchange/src/config.h rename to exchange/src/exchange/config.h index faa26a5d..24073b2d 100644 --- a/exchange/src/config.h +++ b/exchange/src/exchange/config.h @@ -1,10 +1,13 @@ #pragma once +// TODO: a lot of these should be in a config file, not here + #define VERSION "1.0" #define STARTING_CAPITAL 100000 #define DEBUG_NUM_USERS 2 #define CLIENT_WAIT_SECS 3 +#define ORDER_EXPIRATION_TIME 10 // logging #define LOG_BACKTRACE_SIZE 10 @@ -24,4 +27,4 @@ // #define ALGO_DIR "algos" -enum class Mode { SANDBOX, DEV, NORMAL }; +enum class Mode { SANDBOX, DEV, NORMAL, BOTS_ONLY }; diff --git a/exchange/src/curl/curl.cpp b/exchange/src/exchange/curl/curl.cpp similarity index 99% rename from exchange/src/curl/curl.cpp rename to exchange/src/exchange/curl/curl.cpp index bece48e0..cd697d52 100644 --- a/exchange/src/curl/curl.cpp +++ b/exchange/src/exchange/curl/curl.cpp @@ -1,6 +1,6 @@ #include "curl.hpp" -#include "logging.hpp" +#include "exchange/logging.hpp" #include diff --git a/exchange/src/curl/curl.hpp b/exchange/src/exchange/curl/curl.hpp similarity index 100% rename from exchange/src/curl/curl.hpp rename to exchange/src/exchange/curl/curl.hpp diff --git a/exchange/src/exchange/dashboard/dashboard.cpp b/exchange/src/exchange/dashboard/dashboard.cpp new file mode 100644 index 00000000..571ade33 --- /dev/null +++ b/exchange/src/exchange/dashboard/dashboard.cpp @@ -0,0 +1,340 @@ +#include "dashboard.hpp" + +#include "exchange/tick_manager/tick_manager.hpp" +#include "exchange/traders/trader_manager.hpp" +#include "exchange/traders/trader_types.hpp" +#include "state/global_metrics.hpp" + +#include + +#include + +namespace nutc { +namespace dashboard { + +Dashboard::Dashboard() : err_file_(freopen("logs/error_log.txt", "w", stderr)) +{ + quill::stdout_handler("console")->set_log_level(quill::LogLevel::Error); + std::ofstream create_file("logs/app.log"); + create_file.close(); + log_file_ = std::ifstream("logs/app.log", std::ios::in); + if (!log_file_.is_open()) { + printw("Failed to open file\n"); + close(); + std::abort(); + } + + initscr(); + noecho(); + curs_set(0); + cbreak(); + keypad(stdscr, TRUE); + + // make our calls to get input non blocking + nodelay(stdscr, TRUE); + + int x_max{}; + int y_max{}; + getmaxyx(stdscr, y_max, x_max); + ticker_window_ = newwin(y_max, x_max, 0, 0); + log_window_ = newwin(y_max, x_max, 0, 0); + leaderboard_window_ = newwin(y_max, x_max, 0, 0); + performance_window_ = newwin(y_max, x_max, 0, 0); + + scrollok(ticker_window_, TRUE); + scrollok(log_window_, TRUE); + scrollok(leaderboard_window_, TRUE); + scrollok(performance_window_, TRUE); + + werase(ticker_window_); + werase(leaderboard_window_); + werase(performance_window_); + werase(log_window_); + + wrefresh(ticker_window_); + wrefresh(leaderboard_window_); + wrefresh(log_window_); + wrefresh(performance_window_); + + mainLoop(0); +} + +void +Dashboard::close() +{ + delwin(ticker_window_); + delwin(log_window_); + delwin(leaderboard_window_); + delwin(performance_window_); + fflush(err_file_); // NOLINT + fclose(err_file_); // NOLINT + clear(); + refresh(); + curs_set(0); + endwin(); +} + +void +draw_generic_text(WINDOW* window, int start_y) +{ + mvwprintw( + window, start_y++, window->_maxx / 2 - 52, + "Press '1' for Ticker Window, '2' for Log Window, '3' for Leaderboard Window, " + "'4' for Performance Window" + ); +} + +void +Dashboard::drawTickerLayout(WINDOW* window, int start_y, size_t num_tickers) +{ + assert(num_tickers <= 4); + + int x_max{}; + int y_max{}; + getmaxyx(stdscr, y_max, x_max); + + for (size_t i = 1; i < num_tickers + 1; ++i) { + for (int j = start_y + 2; j < y_max; ++j) { + mvwprintw( + window, j, static_cast(i) * x_max / static_cast(num_tickers), + "|" + ); // NOLINT + } + } + + if (has_colors()) { + start_color(); + init_pair(1, COLOR_WHITE, COLOR_BLACK); + init_pair(2, COLOR_CYAN, COLOR_BLACK); + init_pair(3, COLOR_GREEN, COLOR_BLACK); + init_pair(4, COLOR_BLUE, COLOR_BLACK); + init_pair(5, COLOR_RED, COLOR_BLACK); + } + + attron(COLOR_PAIR(2)); + mvwprintw(window, start_y, x_max / 2 - 3, "Tickers"); + attroff(COLOR_PAIR(2)); +} + +void +Dashboard::displayStockTickerData( + WINDOW* window, int start_y, int start_x, const TickerState& ticker +) +{ + mvwprintw(window, start_y++, start_x, "Ticker: %s", ticker.TICKER.c_str()); + mvwprintw(window, start_y++, start_x, "Midprice: %.2f", ticker.midprice_); + mvwprintw(window, start_y++, start_x, "Theo Price: %.2f", ticker.theo_); + mvwprintw( + window, start_y++, start_x, "Spread: %.2f %.2f", ticker.spread_.first, + ticker.spread_.second + ); + mvwprintw( + window, start_y++, start_x, "Bids/asks: %lu %lu", ticker.num_bids_, + ticker.num_asks_ + ); + start_y++; + + auto display_bot_stats = [&](const BotStates& state, const char* bot_type) { + mvwprintw( + window, start_y++, start_x, "%s bots: %lu", bot_type, state.num_bots_ + ); + mvwprintw( + window, start_y++, start_x, "%s active bots: %lu - %.2f%%", bot_type, + state.num_bots_active_, state.percent_active_ + ); + mvwprintw( + window, start_y++, start_x, "%s total capital held: %.0f", bot_type, + state.total_capital_held_ + ); + mvwprintw( + window, start_y++, start_x, "%s min/max/avg num bids: %lu %lu %.2f", + bot_type, state.min_open_bids_, state.max_open_bids_, state.avg_open_bids_ + ); + mvwprintw( + window, start_y++, start_x, "%s min/max/avg num asks: %lu %lu %.2f", + bot_type, state.min_open_asks_, state.max_open_asks_, state.avg_open_asks_ + ); + mvwprintw( + window, start_y++, start_x, "%s min/max/avg utilization: %.2f %.2f %.2f", + bot_type, state.min_utilization_, state.max_utilization_, + state.avg_utilization_ + ); + mvwprintw( + window, start_y++, start_x, "%s min/max/avg PnL: %.2f %.2f %.2f", bot_type, + state.min_pnl_, state.max_pnl_, state.avg_pnl_ + ); + start_y++; + }; + + display_bot_stats(ticker.mm_state_, "MM"); + display_bot_stats(ticker.retail_state_, "Retail"); +} + +void +Dashboard::displayStockTickers(WINDOW* window, int start_y) +{ + auto& states = dashboard::DashboardState::get_instance().get_ticker_states(); + size_t num_tickers = states.size(); + drawTickerLayout(window, start_y, num_tickers); + + size_t idx = 0; + for (auto& [ticker, state] : states) { + size_t start_x = + (idx++ * static_cast(window->_maxx) / states.size()) + 3; + displayStockTickerData(window, start_y + 2, static_cast(start_x), state); + } +} + +void +Dashboard::displayLeaderboard(WINDOW* window, int start_y) +{ + mvwprintw(window, start_y, window->_maxx / 2 - 5, "Leaderboard"); + + manager::ClientManager& client_manager = manager::ClientManager::get_instance(); + for (const auto& [user_id, trader] : client_manager.get_traders()) { + mvwprintw(window, start_y++, 2, "User: %s", trader->get_id().c_str()); + mvwprintw(window, start_y++, 2, " Capital: %.2f", trader->get_capital()); + } +} + +void +Dashboard::displayPerformance(WINDOW* window, int start_y) +{ + mvwprintw(window, start_y, window->_maxx / 2 - 5, "Performance"); + + ticks::TickManager& tick_manager = ticks::TickManager::get_instance(); + ticks::TickManager::tick_metrics_t metrics = tick_manager.get_tick_metrics(); + start_y++; + if (tick_manager.get_current_tick() < 100) { + mvwprintw( + window, start_y + 4, window->_maxx / 2 - 23, + "Current tick (%lu) below 100. Not enough data", + tick_manager.get_current_tick() + ); + return; + } + mvwprintw( + window, start_y++, window->_maxx / 2 - 8, "Current Tick: %lu", + tick_manager.get_current_tick() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 13, "Top 1p tick times(ms): %lu", + metrics.top_1p_ms.count() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 13, "Top 5p tick times(ms): %lu", + metrics.top_5p_ms.count() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 13, "Top 10p tick times(ms): %lu", + metrics.top_10p_ms.count() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 13, "Top 50p tick times(ms): %lu", + metrics.top_50p_ms.count() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 13, "Average tick time(ms): %lu", + metrics.avg_tick_ms.count() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 12, "Median tick time(ms): %lu", + metrics.median_tick_ms.count() + ); + mvwprintw( + window, start_y++, window->_maxx / 2 - 12, "Theoretical max hz: %.2f", + 1000.0 / static_cast(metrics.avg_tick_ms.count()) + ); +} + +void +Dashboard::displayLog(WINDOW* window, int start_y) +{ + mvwprintw(window, start_y, window->_maxx / 2 - 2, "Logs"); + std::string line; + + if (log_file_.eof()) { + log_file_.clear(); + } + + int curr_y = start_y + 3; + while (std::getline(log_file_, line)) { + log_queue_.push_back(line); + if (log_file_.peek() == EOF) + break; + } + + while (static_cast(log_queue_.size()) > (window->_maxy - start_y - 1)) { + log_queue_.pop_front(); + } + + for (const std::string& line : log_queue_) { + mvwprintw(window, curr_y++, 2, "%s", line.c_str()); + } + // this is a bit hacky, but prevents the log file from getting too big + // std::ofstream("logs/app.log"); +} + +void +Dashboard::calculate_ticker_metrics() +{ + auto& states = dashboard::DashboardState::get_instance().get_ticker_states(); + for (auto& [_, state] : states) { + state.calculate_metrics(); + } +} + +void +Dashboard::mainLoop(uint64_t tick) +{ + char chr = static_cast(getch()); + if (chr == '1' || chr == '2' || chr == '3' || chr == '4') + current_window_ = chr; + else if (tick % 15 != 0) + return; + + switch (current_window_) { + case '1': + calculate_ticker_metrics(); + werase(log_window_); + werase(leaderboard_window_); + werase(ticker_window_); + werase(performance_window_); + draw_generic_text(ticker_window_, 0); + displayStockTickers(ticker_window_, 1); + wrefresh(ticker_window_); + break; + case '2': + werase(ticker_window_); + werase(leaderboard_window_); + werase(log_window_); + werase(performance_window_); + draw_generic_text(log_window_, 0); + displayLog(log_window_, 1); + wrefresh(log_window_); + break; + case '3': + werase(ticker_window_); + werase(leaderboard_window_); + werase(log_window_); + werase(performance_window_); + draw_generic_text(leaderboard_window_, 0); + displayLeaderboard(leaderboard_window_, 1); + wrefresh(leaderboard_window_); + break; + case '4': + werase(ticker_window_); + werase(log_window_); + werase(performance_window_); + werase(leaderboard_window_); + draw_generic_text(performance_window_, 0); + displayPerformance(performance_window_, 2); + wrefresh(performance_window_); + break; + default: + break; + } +} + +} // namespace dashboard +} // namespace nutc diff --git a/exchange/src/exchange/dashboard/dashboard.hpp b/exchange/src/exchange/dashboard/dashboard.hpp new file mode 100644 index 00000000..95aede9f --- /dev/null +++ b/exchange/src/exchange/dashboard/dashboard.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "exchange/dashboard/state/ticker_state.hpp" +#include "exchange/tick_manager/tick_observer.hpp" + +#include + +#include +#include + +namespace nutc { +namespace dashboard { + +// NOLINTBEGIN + +class Dashboard : public ticks::TickObserver { +public: + void + on_tick(uint64_t tick) override + { + mainLoop(tick); + } + + static Dashboard& + get_instance() + { + static Dashboard instance; + return instance; + } + + Dashboard& operator=(const Dashboard&) = delete; + Dashboard(const Dashboard&) = delete; + Dashboard& operator=(Dashboard&&) = delete; + Dashboard(Dashboard&&) = delete; + void close(); + +private: + void static drawTickerLayout(WINDOW* window, int start_y, size_t num_tickers); + void static displayStockTickerData( + WINDOW* window, int start_y, int start_x, const TickerState& ticker + ); + void static displayStockTickers(WINDOW* window, int start_y); + void displayLog(WINDOW* window, int start_y); + void static displayLeaderboard(WINDOW* window, int start_y); + void static displayPerformance(WINDOW* window, int start_y); + + void static calculate_ticker_metrics(); + + void mainLoop(uint64_t tick); + + static void* read_pipe_and_log(void* args); + + std::deque log_queue_{}; + + FILE* err_file_; + + WINDOW* ticker_window_; + WINDOW* log_window_; + WINDOW* leaderboard_window_; + WINDOW* performance_window_; + + std::streampos log_pos_ = std::ios::beg; + + char current_window_ = '1'; + + std::ifstream log_file_; + + Dashboard(); + ~Dashboard() = default; +}; + +} // namespace dashboard +} // namespace nutc + +// +// +// NOLINTEND diff --git a/exchange/src/exchange/dashboard/state/global_metrics.hpp b/exchange/src/exchange/dashboard/state/global_metrics.hpp new file mode 100644 index 00000000..d90ebc0f --- /dev/null +++ b/exchange/src/exchange/dashboard/state/global_metrics.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "ticker_state.hpp" + +#include +#include +#include + +namespace nutc { +namespace dashboard { + +class DashboardState { + std::unordered_map ticker_states_{}; + +public: + void + add_ticker(std::string ticker, double starting_price) + { + ticker_states_.emplace( + std::piecewise_construct, std::forward_as_tuple(ticker), + std::forward_as_tuple(ticker, starting_price) + ); + } + + TickerState& + get_ticker_state(const std::string& ticker) + { + return ticker_states_.at(ticker); + } + + std::unordered_map& + get_ticker_states() + { + return ticker_states_; + } + + size_t + num_tickers() const + { + return ticker_states_.size(); + } + + static DashboardState& + get_instance() + { + static DashboardState instance; + return instance; + } + + DashboardState(const DashboardState&) = delete; + DashboardState& operator=(const DashboardState&) = delete; + DashboardState(DashboardState&&) = delete; + DashboardState& operator=(DashboardState&&) = delete; + +private: + DashboardState() = default; + ~DashboardState() = default; +}; + +} // namespace dashboard +} // namespace nutc diff --git a/exchange/src/exchange/dashboard/state/ticker_state.cpp b/exchange/src/exchange/dashboard/state/ticker_state.cpp new file mode 100644 index 00000000..b80e3aab --- /dev/null +++ b/exchange/src/exchange/dashboard/state/ticker_state.cpp @@ -0,0 +1,81 @@ +#include "ticker_state.hpp" + +#include "exchange/bots/bot_types/generic_bot.hpp" +#include "exchange/tickers/manager/ticker_manager.hpp" + +#include + +namespace nutc { +namespace dashboard { + +void +TickerState::calculate_metrics() +{ + reset_(); + bots::BotContainer& bot_container = + engine_manager::EngineManager::get_instance().get_bot_container(TICKER); + + auto& engine_ref = engine_manager::EngineManager::get_instance().get_engine(TICKER); + + midprice_ = engine_ref.get_midprice(); + spread_ = engine_ref.get_spread(); + auto [asks, bids] = engine_ref.get_spread_nums(); + num_asks_ = asks; + num_bids_ = bids; + + theo_ = bot_container.get_theo(); + + auto calculate_pnl = [&engine_ref](const bots::GenericBot& bot) { + double capital = bot.get_capital(); + + // Held stock can be negative due to leverage + double held_stock = bot.get_held_stock(); + double stock_value = engine_ref.get_midprice() * held_stock; + return capital + stock_value; + }; + + auto update_bot_state = [&calculate_pnl](auto&& container, BotStates& state) { + state.num_bots_ = container.size(); + for (const auto& [id, bot] : container) { + state.total_capital_held_ += bot.get_interest_limit() + bot.get_capital(); + if (!bot.is_active()) { + continue; + } + state.num_bots_active_++; + state.min_open_bids_ = std::min(state.min_open_bids_, bot.get_open_bids()); + state.min_open_asks_ = std::min(state.min_open_asks_, bot.get_open_asks()); + state.max_open_bids_ = std::max(state.max_open_bids_, bot.get_open_bids()); + state.max_open_asks_ = std::max(state.max_open_asks_, bot.get_open_asks()); + state.min_utilization_ = + std::min(state.min_utilization_, bot.get_capital_utilization()); + state.max_utilization_ = + std::max(state.max_utilization_, bot.get_capital_utilization()); + state.min_pnl_ = std::min(state.min_pnl_, calculate_pnl(bot)); + state.max_pnl_ = std::max(state.max_pnl_, calculate_pnl(bot)); + + state.avg_open_bids_ += static_cast(bot.get_open_bids()); + state.avg_open_asks_ += static_cast(bot.get_open_asks()); + state.avg_bid_interest_ += bot.get_long_interest(); + state.avg_ask_interest_ += bot.get_short_interest(); + state.avg_utilization_ += bot.get_capital_utilization(); + state.avg_pnl_ += calculate_pnl(bot); + } + if (state.num_bots_active_ == 0) { + return; + } + state.avg_open_bids_ /= static_cast(state.num_bots_active_); + state.avg_open_asks_ /= static_cast(state.num_bots_active_); + state.avg_bid_interest_ /= static_cast(state.num_bots_active_); + state.avg_ask_interest_ /= static_cast(state.num_bots_active_); + state.avg_utilization_ /= static_cast(state.num_bots_active_); + state.avg_pnl_ /= static_cast(state.num_bots_active_); + state.percent_active_ = static_cast(state.num_bots_active_) + / static_cast(state.num_bots_); + }; + update_bot_state(bot_container.get_market_makers(), mm_state_); + update_bot_state(bot_container.get_retail_traders(), retail_state_); +} + +} // namespace dashboard + +} // namespace nutc diff --git a/exchange/src/exchange/dashboard/state/ticker_state.hpp b/exchange/src/exchange/dashboard/state/ticker_state.hpp new file mode 100644 index 00000000..d6980636 --- /dev/null +++ b/exchange/src/exchange/dashboard/state/ticker_state.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include + +#include + +namespace nutc { +namespace dashboard { + +// NOLINTBEGIN +struct BotStates { + size_t num_bots_{}; + size_t num_bots_active_{}; + double percent_active_{}; + double total_capital_held_{}; + + size_t min_open_bids_{}; + size_t min_open_asks_{}; + size_t max_open_bids_{}; + size_t max_open_asks_{}; + double min_utilization_{}; + double max_utilization_{}; + double min_pnl_{}; + double max_pnl_{}; + + double avg_open_bids_{}; + double avg_open_asks_{}; + double avg_bid_interest_{}; + double avg_ask_interest_{}; + double avg_utilization_{}; + double avg_pnl_{}; +}; + +struct TickerState { + const std::string TICKER; + const double STARTING_PRICE; + + double theo_{}; + double midprice_{}; + std::pair spread_{}; + size_t num_bids_{}; + size_t num_asks_{}; + + BotStates mm_state_{}; + BotStates retail_state_{}; + + TickerState(std::string ticker, double starting_price) : + TICKER(std::move(ticker)), STARTING_PRICE(starting_price) + {} + + void calculate_metrics(); + +private: + void + reset_() + { + theo_ = 0; + midprice_ = 0; + spread_ = {0, 0}; + num_bids_ = 0; + num_asks_ = 0; + + auto reset_bot_states = [](BotStates& bot_states) { + bot_states.num_bots_ = 0; + bot_states.num_bots_active_ = 0; + bot_states.percent_active_ = 0; + bot_states.total_capital_held_ = 0; + bot_states.min_open_bids_ = std::numeric_limits::max(); + bot_states.min_open_asks_ = std::numeric_limits::max(); + bot_states.max_open_bids_ = std::numeric_limits::min(); + bot_states.max_open_asks_ = std::numeric_limits::min(); + bot_states.min_utilization_ = std::numeric_limits::max(); + bot_states.max_utilization_ = std::numeric_limits::min(); + bot_states.min_pnl_ = std::numeric_limits::max(); + bot_states.max_pnl_ = std::numeric_limits::min(); + bot_states.avg_open_bids_ = 0; + bot_states.avg_open_asks_ = 0; + bot_states.avg_bid_interest_ = 0; + bot_states.avg_ask_interest_ = 0; + bot_states.avg_utilization_ = 0; + bot_states.avg_pnl_ = 0; + }; + + reset_bot_states(mm_state_); + reset_bot_states(retail_state_); + } +}; + +} // namespace dashboard +} // namespace nutc + +// NOLINTEND diff --git a/exchange/src/logging.cpp b/exchange/src/exchange/logging.cpp similarity index 90% rename from exchange/src/logging.cpp rename to exchange/src/exchange/logging.cpp index 4b3ba319..896d72cb 100644 --- a/exchange/src/logging.cpp +++ b/exchange/src/exchange/logging.cpp @@ -77,16 +77,11 @@ init(quill::LogLevel log_level) cfg.default_handlers.emplace_back(stdout_handler); - // - // Initialize rotating file handler - // - auto file_handler = quill::rotating_file_handler( - LOG_FILE, - "w", // append - FilenameAppend::None, // just keep the filename - LOG_FILE_SIZE, // 512 KB - LOG_BACKUP_COUNT // 5 backups - ); + auto handler_cfg = quill::FileHandlerConfig{}; + handler_cfg.set_open_mode('w'); + + const std::string log_file = LOG_FILE; + auto file_handler = quill::file_handler(log_file, handler_cfg); file_handler->set_pattern( LOGLINE_FORMAT, diff --git a/exchange/src/logging.hpp b/exchange/src/exchange/logging.hpp similarity index 98% rename from exchange/src/logging.hpp rename to exchange/src/exchange/logging.hpp index 041d4d44..aec1215c 100644 --- a/exchange/src/logging.hpp +++ b/exchange/src/exchange/logging.hpp @@ -83,6 +83,8 @@ CREATE_LOG_CATEGORY(rabbitmq); CREATE_LOG_CATEGORY(dev_mode); CREATE_LOG_CATEGORY(events); CREATE_LOG_CATEGORY(sandbox); +CREATE_LOG_CATEGORY(tick_manager); +CREATE_LOG_CATEGORY(retail_bot); #undef CREATE_LOG_CATEGORY // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/exchange/src/main.cpp b/exchange/src/exchange/main.cpp similarity index 55% rename from exchange/src/main.cpp rename to exchange/src/exchange/main.cpp index a9bbf33b..2000576e 100644 --- a/exchange/src/main.cpp +++ b/exchange/src/exchange/main.cpp @@ -2,18 +2,24 @@ #include "algos/dev_mode/dev_mode.hpp" #include "algos/normal_mode/normal_mode.hpp" #include "algos/sandbox_mode/sandbox_mode.hpp" -#include "client_manager/client_manager.hpp" #include "config.h" +#include "dashboard/dashboard.hpp" +#include "exchange/bots/bot_container.hpp" +#include "exchange/dashboard/state/global_metrics.hpp" +#include "exchange/tick_manager/tick_manager.hpp" #include "logging.hpp" -#include "matching/manager/engine_manager.hpp" #include "process_spawning/spawning.hpp" #include "rabbitmq/client_manager/RabbitMQClientManager.hpp" #include "rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" #include "rabbitmq/consumer/RabbitMQConsumer.hpp" -#include "rabbitmq/order_handler/RabbitMQOrderHandler.hpp" +#include "tickers/manager/ticker_manager.hpp" +#include "traders/trader_manager.hpp" +#include "utils/logger/logger.hpp" #include +#include + #include #include @@ -38,6 +44,13 @@ process_arguments(int argc, const char** argv) .implicit_value(true) .nargs(0); + program.add_argument("--bots-only") + .help("No algos spawned in, just bots") + .action([](const auto& /* unused */) {}) + .default_value(false) + .implicit_value(true) + .nargs(0); + program.add_argument("-S", "--sandbox").help("Provide a sandbox algo id").nargs(2); program.add_argument("-V", "--version") @@ -77,27 +90,103 @@ process_arguments(int argc, const char** argv) } bool dev_mode = program.get("--dev"); + bool bots_only = program.get("--bots-only"); auto get_mode = [&]() -> Mode { if (dev_mode) return Mode::DEV; if (algo.has_value()) return Mode::SANDBOX; + if (bots_only) + return Mode::BOTS_ONLY; return Mode::NORMAL; }; return std::make_tuple(get_mode(), algo); } +void +print_file_contents(const std::string& filepath) +{ + std::ifstream file(filepath); + + if (!file) { + return; + } + + std::string line; + while (std::getline(file, line)) { + std::cout << line << std::endl; // NOLINT + } +} + +void +flush_log(int) +{ + nutc::events::Logger::get_logger().flush(); + nutc::dashboard::Dashboard::get_instance().close(); + print_file_contents("logs/error_log.txt"); + std::exit(0); // NOLINT(concurrency-*) +} + +// Initializes tick manager with brownian motion +void +initialize_ticker(const std::string& ticker, double starting_price) +{ + using nutc::dashboard::DashboardState; + using nutc::engine_manager::EngineManager; + using nutc::ticks::PRIORITY; + using nutc::ticks::TickManager; + + auto& tick_manager = TickManager::get_instance(); + EngineManager::get_instance().add_engine(ticker, starting_price); + + nutc::bots::BotContainer& bot_container = + EngineManager::get_instance().get_bot_container(ticker); + + // Should run after stale order removal, so they can react to removed orders + tick_manager.attach(&bot_container, PRIORITY::second, "Bot Engine"); + + DashboardState::get_instance().add_ticker(ticker, starting_price); +} + +// todo: please god clean this up int main(int argc, const char** argv) { - using namespace nutc; // NOLINT(*) + std::signal(SIGINT, flush_log); + std::signal(SIGABRT, flush_log); + using namespace nutc; // NOLINT(*) // Set up logging - logging::init(quill::LogLevel::TraceL3); + logging::init(quill::LogLevel::Info); + + static constexpr uint16_t TICK_HZ = 60; + nutc::ticks::TickManager::get_instance(TICK_HZ); + + initialize_ticker("ETH", 100); + initialize_ticker("BTC", 200); + initialize_ticker("USD", 300); + + auto& dashboard = nutc::dashboard::Dashboard::get_instance(); + nutc::ticks::TickManager::get_instance().attach( + &dashboard, nutc::ticks::PRIORITY::fourth, "Dashboard Manager" + ); + + auto& engine_manager = engine_manager::EngineManager::get_instance(); - manager::ClientManager users; - nutc::engine_manager::Manager engine_manager; + engine_manager.get_bot_container("ETH").add_mm_bots(100000, 10000, 5); + engine_manager.get_bot_container("BTC").add_mm_bots(25000, 5000, 10); + engine_manager.get_bot_container("USD").add_mm_bots(100000, 25000, 3); + engine_manager.get_bot_container("ETH").add_retail_bots(10, 3, 200); + engine_manager.get_bot_container("BTC").add_retail_bots(100, 5, 500); + engine_manager.get_bot_container("USD").add_retail_bots(100, 10, 100); + + ticks::TickManager::get_instance().attach( + &engine_manager, ticks::PRIORITY::first, "Matching Engine" + ); + ticks::TickManager::get_instance().start(); + + manager::ClientManager& users = manager::ClientManager::get_instance(); auto [mode, sandbox] = process_arguments(argc, argv); @@ -109,11 +198,14 @@ main(int argc, const char** argv) return 1; } + while (mode == Mode::BOTS_ONLY) { + } // spin forever, but keep rmq running. maybe remove this later? + size_t num_clients{}; using algo_mgmt::AlgoManager; auto initialize_dev_mode = [&]() { - log_t1(main, "Initializing NUTC in development mode"); + log_i(main, "Initializing NUTC in development mode"); using algo_mgmt::DevModeAlgoManager; DevModeAlgoManager manager = DevModeAlgoManager(DEBUG_NUM_USERS); @@ -122,7 +214,7 @@ main(int argc, const char** argv) // Weird name because of shadowing auto initialize_sandbox_mode = [&, &sandbox = sandbox]() { - log_t1(main, "Initializing NUTC in sandbox mode"); + log_i(main, "Initializing NUTC in sandbox mode"); using algo_mgmt::SandboxAlgoManager; auto& [uid, algo_id] = sandbox.value(); // NOLINT (unchecked-*) @@ -131,7 +223,7 @@ main(int argc, const char** argv) }; auto initialize_normal_mode = [&]() { - log_t1(main, "Initializing NUTC in normal mode"); + log_i(main, "Initializing NUTC in normal mode"); using algo_mgmt::NormalModeAlgoManager; NormalModeAlgoManager manager = NormalModeAlgoManager(); @@ -147,26 +239,16 @@ main(int argc, const char** argv) break; case Mode::NORMAL: initialize_normal_mode(); + break; + case Mode::BOTS_ONLY: + break; } - client::spawn_all_clients(users); - - engine_manager.add_engine("A"); - engine_manager.add_engine("B"); - engine_manager.add_engine("C"); + spawning::spawn_all_clients(users); // Run exchange rabbitmq::RabbitMQClientManager::wait_for_clients(users, num_clients); rabbitmq::RabbitMQClientManager::send_start_time(users, CLIENT_WAIT_SECS); - rabbitmq::RabbitMQOrderHandler::add_liquidity_to_ticker( - users, engine_manager, "A", 1000, 100 - ); - rabbitmq::RabbitMQOrderHandler::add_liquidity_to_ticker( - users, engine_manager, "B", 2000, 200 - ); - rabbitmq::RabbitMQOrderHandler::add_liquidity_to_ticker( - users, engine_manager, "C", 3000, 300 - ); // Main event loop rabbitmq::RabbitMQConsumer::handle_incoming_messages(users, engine_manager); diff --git a/exchange/src/exchange/process_spawning/spawning.cpp b/exchange/src/exchange/process_spawning/spawning.cpp new file mode 100644 index 00000000..6dca5a9e --- /dev/null +++ b/exchange/src/exchange/process_spawning/spawning.cpp @@ -0,0 +1,100 @@ +#include "spawning.hpp" + +#include "exchange/logging.hpp" +#include "exchange/traders/trader_types.hpp" +#include "shared/file_operations/file_operations.hpp" + +#include + +namespace nutc { +namespace spawning { + +std::string +quote_id(std::string user_id) +{ + std::replace(user_id.begin(), user_id.end(), '-', ' '); + return user_id; +} + +pid_t +spawn_client( + const std::unique_ptr& trader, + const std::string& binary_path +) +{ + if (trader->get_type() == manager::LOCAL) { + const std::string filepath = trader->get_algo_id() + ".py"; + assert(file_ops::file_exists(filepath)); + } + + pid_t pid = fork(); + if (pid == 0) { + std::vector args = { + binary_path, "--uid", trader->get_id(), "--algo_id", trader->get_algo_id() + }; + + if (trader->get_type() == manager::LOCAL) { + args.emplace_back("--dev"); + } + + if (!trader->has_start_delay()) { + args.emplace_back("--no-start-delay"); + } + + std::vector c_args; + c_args.reserve(args.size() + 1); + for (auto& arg : args) { + c_args.push_back(arg.data()); + } + c_args.push_back(nullptr); + + execvp(c_args[0], c_args.data()); + log_e(client_spawning, "Failed to execute NUTC-client"); + std::abort(); + } + else if (pid < 0) { // Fork failed + log_e(client_spawning, "Failed to fork"); + std::abort(); + } + + return pid; +} + +size_t +spawn_all_clients(nutc::manager::ClientManager& users) +{ + const char* wrapper_binary_location = std::getenv("NUTC_WRAPPER_BINARY_PATH"); + if (wrapper_binary_location == nullptr) [[unlikely]] { + log_e( + client_spawning, + "Failed to get NUTC_WRAPPER_BINARY_PATH from environment variable" + ); + exit(1); + } + const std::string wrapper_binary_path(wrapper_binary_location); + + size_t num_clients = 0; + auto spawn_one_trader = [&](const std::unique_ptr& trader) { + if (trader->get_type() == manager::BOT) + return; + + if (trader->is_active()) + return; + + const std::string& trader_id = trader->get_id(); + log_i(client_spawning, "Spawning client: {}", trader_id); + + std::string algo_id; + trader->set_pid(spawn_client(trader, wrapper_binary_path)); + num_clients++; + }; + + for (const auto& [id, trader] : users.get_traders()) { + spawn_one_trader(trader); + } + + return num_clients; +} + +} // namespace spawning +} // namespace nutc diff --git a/exchange/src/exchange/process_spawning/spawning.hpp b/exchange/src/exchange/process_spawning/spawning.hpp new file mode 100644 index 00000000..3dff9cd4 --- /dev/null +++ b/exchange/src/exchange/process_spawning/spawning.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "exchange/traders/trader_manager.hpp" + +#include +#include +#include +#include + +namespace nutc { + +/** @brief Contains all functions related to spawning client processes */ +namespace spawning { + +/** + * @brief Spawns all clients in the given ClientManager + * @param users The ClientManager to spawn clients for + * @returns the number of clients spawned + */ +size_t spawn_all_clients(nutc::manager::ClientManager& users); + +} // namespace spawning +} // namespace nutc diff --git a/exchange/src/rabbitmq/client_manager/RabbitMQClientManager.cpp b/exchange/src/exchange/rabbitmq/client_manager/RabbitMQClientManager.cpp similarity index 76% rename from exchange/src/rabbitmq/client_manager/RabbitMQClientManager.cpp rename to exchange/src/exchange/rabbitmq/client_manager/RabbitMQClientManager.cpp index c3260d6f..0c5fbf11 100644 --- a/exchange/src/rabbitmq/client_manager/RabbitMQClientManager.cpp +++ b/exchange/src/exchange/rabbitmq/client_manager/RabbitMQClientManager.cpp @@ -1,9 +1,8 @@ #include "RabbitMQClientManager.hpp" -#include "client_manager/client_manager.hpp" -#include "logging.hpp" -#include "rabbitmq/consumer/RabbitMQConsumer.hpp" -#include "rabbitmq/publisher/RabbitMQPublisher.hpp" +#include "exchange/logging.hpp" +#include "exchange/rabbitmq/consumer/RabbitMQConsumer.hpp" +#include "exchange/rabbitmq/publisher/RabbitMQPublisher.hpp" namespace nutc { namespace rabbitmq { @@ -29,7 +28,7 @@ RabbitMQClientManager::wait_for_clients( message.client_id, message.ready ? "ready" : "not ready" ); if (message.ready) { - manager.set_active(message.client_id); + manager.get_trader(message.client_id)->set_active(/*active=*/true); num_running++; } } @@ -64,17 +63,11 @@ RabbitMQClientManager::send_start_time( messages::StartTime message{time_ns}; std::string buf = glz::write_json(message); - auto send_to_client = [buf](const std::pair& pair) { - const auto& [id, client] = pair; - - if (!client.active) - return; - - RabbitMQPublisher::publish_message(id, buf); - }; - - const auto& clients = manager.get_clients(); - std::for_each(clients.begin(), clients.end(), send_to_client); + const auto& traders = manager.get_traders(); + for (const auto& [id, trader] : traders) { + if (trader->is_active()) + RabbitMQPublisher::publish_message(id, buf); + } } } // namespace rabbitmq diff --git a/exchange/src/rabbitmq/client_manager/RabbitMQClientManager.hpp b/exchange/src/exchange/rabbitmq/client_manager/RabbitMQClientManager.hpp similarity index 92% rename from exchange/src/rabbitmq/client_manager/RabbitMQClientManager.hpp rename to exchange/src/exchange/rabbitmq/client_manager/RabbitMQClientManager.hpp index 7c755d61..f2725144 100644 --- a/exchange/src/rabbitmq/client_manager/RabbitMQClientManager.hpp +++ b/exchange/src/exchange/rabbitmq/client_manager/RabbitMQClientManager.hpp @@ -1,6 +1,6 @@ #pragma once -#include "client_manager/client_manager.hpp" +#include "exchange/traders/trader_manager.hpp" namespace nutc { namespace rabbitmq { diff --git a/exchange/src/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp b/exchange/src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp similarity index 95% rename from exchange/src/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp rename to exchange/src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp index 156fb83b..6b0751a5 100644 --- a/exchange/src/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp +++ b/exchange/src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.cpp @@ -1,7 +1,7 @@ #include "RabbitMQConnectionManager.hpp" -#include "logging.hpp" -#include "rabbitmq/queue_manager/RabbitMQQueueManager.hpp" +#include "exchange/logging.hpp" +#include "exchange/rabbitmq/queue_manager/RabbitMQQueueManager.hpp" #include diff --git a/exchange/src/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp b/exchange/src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp similarity index 98% rename from exchange/src/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp rename to exchange/src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp index d83d3869..ab3955ca 100644 --- a/exchange/src/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp +++ b/exchange/src/exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp @@ -51,7 +51,7 @@ class RabbitMQConnectionManager { return connected_; } - amqp_connection_state_t + amqp_connection_state_t& get_connection_state() { return connection_state_; diff --git a/exchange/src/rabbitmq/consumer/RabbitMQConsumer.cpp b/exchange/src/exchange/rabbitmq/consumer/RabbitMQConsumer.cpp similarity index 80% rename from exchange/src/rabbitmq/consumer/RabbitMQConsumer.cpp rename to exchange/src/exchange/rabbitmq/consumer/RabbitMQConsumer.cpp index 4793691d..d93ffee3 100644 --- a/exchange/src/rabbitmq/consumer/RabbitMQConsumer.cpp +++ b/exchange/src/exchange/rabbitmq/consumer/RabbitMQConsumer.cpp @@ -1,8 +1,10 @@ #include "RabbitMQConsumer.hpp" -#include "logging.hpp" -#include "rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" -#include "rabbitmq/order_handler/RabbitMQOrderHandler.hpp" +#include "exchange/logging.hpp" +#include "exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" +#include "exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp" + +#include #include @@ -11,7 +13,7 @@ namespace rabbitmq { void RabbitMQConsumer::handle_incoming_messages( - manager::ClientManager& clients, engine_manager::Manager& engine_manager + manager::ClientManager& clients, engine_manager::EngineManager& engine_manager ) { while (true) { @@ -22,16 +24,16 @@ RabbitMQConsumer::handle_incoming_messages( [&](auto&& arg) { using t = std::decay_t; if constexpr (std::is_same_v) { - log_e(rabbitmq, "Not expecting initialization message"); + log_c(rabbitmq, "Not expecting initialization message"); std::abort(); } else if constexpr (std::is_same_v) { RabbitMQOrderHandler::handle_incoming_market_order( - engine_manager, clients, arg + engine_manager, clients, std::forward(arg) ); } }, - incoming_message + std::move(incoming_message) ); } } diff --git a/exchange/src/rabbitmq/consumer/RabbitMQConsumer.hpp b/exchange/src/exchange/rabbitmq/consumer/RabbitMQConsumer.hpp similarity index 71% rename from exchange/src/rabbitmq/consumer/RabbitMQConsumer.hpp rename to exchange/src/exchange/rabbitmq/consumer/RabbitMQConsumer.hpp index 36c0214d..e48249af 100644 --- a/exchange/src/rabbitmq/consumer/RabbitMQConsumer.hpp +++ b/exchange/src/exchange/rabbitmq/consumer/RabbitMQConsumer.hpp @@ -1,8 +1,8 @@ #pragma once -#include "client_manager/client_manager.hpp" -#include "matching/manager/engine_manager.hpp" -#include "utils/messages.hpp" +#include "exchange/tickers/manager/ticker_manager.hpp" +#include "exchange/traders/trader_manager.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" #include #include @@ -21,7 +21,7 @@ class RabbitMQConsumer { * messages from the exchange */ static void handle_incoming_messages( - manager::ClientManager& clients, engine_manager::Manager& engine_manager + manager::ClientManager& clients, engine_manager::EngineManager& engine_manager ); private: diff --git a/exchange/src/rabbitmq/interface.md b/exchange/src/exchange/rabbitmq/interface.md similarity index 100% rename from exchange/src/rabbitmq/interface.md rename to exchange/src/exchange/rabbitmq/interface.md diff --git a/exchange/src/rabbitmq/order_handler/RabbitMQOrderHandler.cpp b/exchange/src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.cpp similarity index 58% rename from exchange/src/rabbitmq/order_handler/RabbitMQOrderHandler.cpp rename to exchange/src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.cpp index a83a29f6..3caa9562 100644 --- a/exchange/src/rabbitmq/order_handler/RabbitMQOrderHandler.cpp +++ b/exchange/src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.cpp @@ -1,15 +1,16 @@ #include "RabbitMQOrderHandler.hpp" -#include "logging.hpp" -#include "rabbitmq/publisher/RabbitMQPublisher.hpp" +#include "exchange/logging.hpp" +#include "exchange/rabbitmq/publisher/RabbitMQPublisher.hpp" +#include "exchange/tickers/manager/ticker_manager.hpp" namespace nutc { namespace rabbitmq { void RabbitMQOrderHandler::handle_incoming_market_order( - engine_manager::Manager& engine_manager, manager::ClientManager& clients, - MarketOrder& order + engine_manager::EngineManager& engine_manager, manager::ClientManager& clients, + MarketOrder&& order ) { std::string buffer; @@ -25,54 +26,37 @@ RabbitMQOrderHandler::handle_incoming_market_order( buffer.replace(pos2, replace2.length(), R"("side":"ask")"); } - log_i(rabbitmq, "Received market order: {}", buffer); + // log_i(rabbitmq, "Received market order: {}", buffer); std::optional> engine = engine_manager.get_engine(order.ticker); - if (!engine.has_value()) { - log_w( - matching, "Received order for unknown ticker {}. Discarding order", - order.ticker - ); - return; - } - auto [matches, ob_updates] = engine.value().get().match_order(order, clients); + assert(engine.has_value()); // TODO: FOR TESTING PURPOSES ONLY + std::string client_id = order.client_id; + auto [matches, ob_updates] = + engine.value().get().match_order(std::move(order), clients); for (const auto& match : matches) { std::string buyer_id = match.buyer_id; std::string seller_id = match.seller_id; RabbitMQPublisher::broadcast_account_update(clients, match); - log_i( + /*log_i( matching, "Matched order with price {} and quantity {}", match.price, match.quantity - ); + );*/ } - for (const auto& update : ob_updates) { + /*for (const auto& update : ob_updates) { log_i( rabbitmq, "New ObUpdate with ticker {} price {} quantity {} side {}", update.ticker, update.price, update.quantity, update.side == messages::SIDE::BUY ? "BUY" : "ASK" ); - } + }*/ if (!matches.empty()) { RabbitMQPublisher::broadcast_matches(clients, matches); } if (!ob_updates.empty()) { - RabbitMQPublisher::broadcast_ob_updates(clients, ob_updates, order.client_id); + RabbitMQPublisher::broadcast_ob_updates(clients, ob_updates, client_id); } } -void -RabbitMQOrderHandler::add_liquidity_to_ticker( - manager::ClientManager& clients, engine_manager::Manager& engine_manager, - const std::string& ticker, float quantity, float price -) -{ - engine_manager.add_initial_liquidity(ticker, quantity, price); - messages::ObUpdate update{ticker, messages::SIDE::SELL, price, quantity}; - std::vector vec{}; - vec.push_back(update); - RabbitMQPublisher::broadcast_ob_updates(clients, vec, ""); -} - } // namespace rabbitmq } // namespace nutc diff --git a/exchange/src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp b/exchange/src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp new file mode 100644 index 00000000..1880afc3 --- /dev/null +++ b/exchange/src/exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "exchange/tickers/manager/ticker_manager.hpp" +#include "exchange/traders/trader_manager.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" + +#include + +namespace nutc { +namespace rabbitmq { +class RabbitMQOrderHandler { +public: + static void handle_incoming_market_order( + engine_manager::EngineManager& engine_manager, manager::ClientManager& clients, + messages::MarketOrder&& order + ); +}; + +} // namespace rabbitmq +} // namespace nutc diff --git a/exchange/src/rabbitmq/publisher/RabbitMQPublisher.cpp b/exchange/src/exchange/rabbitmq/publisher/RabbitMQPublisher.cpp similarity index 57% rename from exchange/src/rabbitmq/publisher/RabbitMQPublisher.cpp rename to exchange/src/exchange/rabbitmq/publisher/RabbitMQPublisher.cpp index 7722f2db..c6e6f5ce 100644 --- a/exchange/src/rabbitmq/publisher/RabbitMQPublisher.cpp +++ b/exchange/src/exchange/rabbitmq/publisher/RabbitMQPublisher.cpp @@ -1,7 +1,8 @@ #include "RabbitMQPublisher.hpp" -#include "logging.hpp" -#include "rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" +#include "exchange/logging.hpp" +#include "exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" +#include "exchange/traders/trader_types.hpp" namespace nutc { namespace rabbitmq { @@ -38,22 +39,19 @@ RabbitMQPublisher::broadcast_matches( const manager::ClientManager& clients, const std::vector& matches ) { - auto broadcast_to_client = [&](const std::pair& pair - ) { + const auto& active_clients = clients.get_traders(); + for (const auto& [id, trader] : active_clients) { for (const auto& match : matches) { - const auto& [id, client] = pair; - - if (!client.active) + if (trader->get_type() == manager::BOT) + continue; + if (!trader->is_active()) continue; std::string buffer; glz::write(match, buffer); publish_message(id, buffer); } - }; - - const auto& active_clients = clients.get_clients(); - std::for_each(active_clients.begin(), active_clients.end(), broadcast_to_client); + } } void @@ -62,12 +60,12 @@ RabbitMQPublisher::broadcast_ob_updates( const std::vector& updates, const std::string& ignore_uid ) { - auto broadcast_to_client = [&](const std::pair& pair - ) { - const auto& [id, client] = pair; - - if (!client.active || id == ignore_uid) { - return; + const auto& traders = clients.get_traders(); + for (const auto& [id, trader] : traders) { + if (trader->get_type() == manager::BOT) + continue; + if (!trader->is_active() || id == ignore_uid) { + continue; } for (const auto& update : updates) { @@ -75,10 +73,7 @@ RabbitMQPublisher::broadcast_ob_updates( glz::write(update, buffer); publish_message(id, buffer); } - }; - - const auto& active_clients = clients.get_clients(); - std::for_each(active_clients.begin(), active_clients.end(), broadcast_to_client); + } } void @@ -89,21 +84,22 @@ RabbitMQPublisher::broadcast_account_update( const std::string& buyer_id = match.buyer_id; const std::string& seller_id = match.seller_id; - messages::AccountUpdate buyer_update = { - match.ticker, messages::SIDE::BUY, match.price, - match.quantity, clients.get_capital(buyer_id), - }; - messages::AccountUpdate seller_update = { - match.ticker, messages::SIDE::SELL, match.price, - match.quantity, clients.get_capital(seller_id), + auto send_message = [&](const std::unique_ptr& trader, + messages::SIDE side) { + if (trader->get_type() == manager::BOT) + return; + if (!trader->is_active()) + return; + messages::AccountUpdate update = { + match.ticker, side, match.price, match.quantity, trader->get_capital() + }; + std::string buffer; + glz::write(update, buffer); + publish_message(trader->get_id(), buffer); }; - std::string buyer_buffer; - std::string seller_buffer; - glz::write(buyer_update, buyer_buffer); - glz::write(seller_update, seller_buffer); - publish_message(buyer_id, buyer_buffer); - publish_message(seller_id, seller_buffer); + send_message(clients.get_trader(buyer_id), messages::SIDE::BUY); + send_message(clients.get_trader(seller_id), messages::SIDE::SELL); } } // namespace rabbitmq diff --git a/exchange/src/rabbitmq/publisher/RabbitMQPublisher.hpp b/exchange/src/exchange/rabbitmq/publisher/RabbitMQPublisher.hpp similarity index 88% rename from exchange/src/rabbitmq/publisher/RabbitMQPublisher.hpp rename to exchange/src/exchange/rabbitmq/publisher/RabbitMQPublisher.hpp index 0025ad0a..342e23c1 100644 --- a/exchange/src/rabbitmq/publisher/RabbitMQPublisher.hpp +++ b/exchange/src/exchange/rabbitmq/publisher/RabbitMQPublisher.hpp @@ -1,7 +1,7 @@ #pragma once -#include "client_manager/client_manager.hpp" -#include "utils/messages.hpp" +#include "exchange/traders/trader_manager.hpp" +#include "shared/messages_exchange_to_wrapper.hpp" #include diff --git a/exchange/src/rabbitmq/queue_manager/RabbitMQQueueManager.cpp b/exchange/src/exchange/rabbitmq/queue_manager/RabbitMQQueueManager.cpp similarity index 97% rename from exchange/src/rabbitmq/queue_manager/RabbitMQQueueManager.cpp rename to exchange/src/exchange/rabbitmq/queue_manager/RabbitMQQueueManager.cpp index 6569dae7..32949530 100644 --- a/exchange/src/rabbitmq/queue_manager/RabbitMQQueueManager.cpp +++ b/exchange/src/exchange/rabbitmq/queue_manager/RabbitMQQueueManager.cpp @@ -1,6 +1,6 @@ #include "RabbitMQQueueManager.hpp" -#include "logging.hpp" +#include "exchange/logging.hpp" namespace nutc { namespace rabbitmq { diff --git a/exchange/src/rabbitmq/queue_manager/RabbitMQQueueManager.hpp b/exchange/src/exchange/rabbitmq/queue_manager/RabbitMQQueueManager.hpp similarity index 100% rename from exchange/src/rabbitmq/queue_manager/RabbitMQQueueManager.hpp rename to exchange/src/exchange/rabbitmq/queue_manager/RabbitMQQueueManager.hpp diff --git a/exchange/src/rabbitmq/rabbitmq.hpp b/exchange/src/exchange/rabbitmq/rabbitmq.hpp similarity index 100% rename from exchange/src/rabbitmq/rabbitmq.hpp rename to exchange/src/exchange/rabbitmq/rabbitmq.hpp diff --git a/exchange/src/randomness/brownian.cpp b/exchange/src/exchange/theo/brownian.cpp similarity index 85% rename from exchange/src/randomness/brownian.cpp rename to exchange/src/exchange/theo/brownian.cpp index 66fb089b..b616e258 100644 --- a/exchange/src/randomness/brownian.cpp +++ b/exchange/src/exchange/theo/brownian.cpp @@ -1,7 +1,7 @@ /** * @file brownian.cpp * @author Andrew Li (andrewli@u.northwestern.edu) - * @brief Brownian motion randomness to simulate market chaos + * @brief Brownian motion theo to simulate market chaos * @version 0.1 * @date 2024-01-12 * @@ -11,7 +11,7 @@ #include "brownian.hpp" -constexpr double BROWNIAN_MOTION_DEVIATION = 0.4; +constexpr double BROWNIAN_MOTION_DEVIATION = 0.1; namespace nutc { namespace stochastic { diff --git a/exchange/src/randomness/brownian.hpp b/exchange/src/exchange/theo/brownian.hpp similarity index 76% rename from exchange/src/randomness/brownian.hpp rename to exchange/src/exchange/theo/brownian.hpp index a84e5776..513b9006 100644 --- a/exchange/src/randomness/brownian.hpp +++ b/exchange/src/exchange/theo/brownian.hpp @@ -1,7 +1,7 @@ /** * @file brownian.hpp * @author Andrew Li (andrewli@u.northwestern.edu) - * @brief Brownian motion randomness to simulate market chaos + * @brief Brownian motion theo to simulate market chaos * @version 0.1 * @date 2024-01-12 * @@ -22,6 +22,12 @@ class BrownianMotion { std::mt19937 random_number_generator_; public: + [[nodiscard]] double + get_price() const + { + return cur_value_; + } + // Default constructor for BrownianMotion, takes nothing explicit BrownianMotion() : cur_value_(0) { @@ -35,15 +41,8 @@ class BrownianMotion { random_number_generator_ = std::mt19937(seed); } - // Constructor for BrownianMotion, takes a seed and initial value - explicit BrownianMotion(const unsigned int seed, const double initial_value) : - cur_value_(initial_value) - { - random_number_generator_ = std::mt19937(seed); - } - // Generates and returns the next price based on previous prices - [[nodiscard]] double generate_next_price(); + double generate_next_price(); // Force set the current price void diff --git a/exchange/src/exchange/tick_manager/tick_manager.cpp b/exchange/src/exchange/tick_manager/tick_manager.cpp new file mode 100644 index 00000000..204bc909 --- /dev/null +++ b/exchange/src/exchange/tick_manager/tick_manager.cpp @@ -0,0 +1,118 @@ +#include "tick_manager.hpp" + +#include + +namespace nutc { +namespace ticks { + +auto +TickManager::notify_tick_() // NOLINT +{ + auto start = std::chrono::high_resolution_clock::now(); + for (TickObserver* observer : first_observers_) { + observer->on_tick(current_tick_); + } + + for (TickObserver* observer : second_observers_) { + observer->on_tick(current_tick_); + } + + for (TickObserver* observer : third_observers_) { + observer->on_tick(current_tick_); + } + + for (TickObserver* observer : fourth_observers_) { + observer->on_tick(current_tick_); + } + auto end = std::chrono::high_resolution_clock::now(); + return end - start; +} + +TickManager::tick_metrics_t +TickManager::get_tick_metrics() const +{ + std::priority_queue> temp_queue; + for (const auto& tick_time : last_1000_tick_times_) { + temp_queue.push(tick_time); + } + + milliseconds median_tick_ms = milliseconds(0); + milliseconds top_1p_ms = milliseconds(0); + milliseconds top_5p_ms = milliseconds(0); + milliseconds top_10p_ms = milliseconds(0); + milliseconds top_50p_ms = milliseconds(0); + milliseconds total_ms = milliseconds(0); + + size_t num_ticks = last_1000_tick_times_.size(); + + if (num_ticks < 100) { + return {milliseconds(0), milliseconds(0), milliseconds(0), + milliseconds(0), milliseconds(0), milliseconds(0)}; + } + + for (size_t i = 0; i < num_ticks / 100; i++) { + top_1p_ms += temp_queue.top(); + top_5p_ms += temp_queue.top(); + top_10p_ms += temp_queue.top(); + top_50p_ms += temp_queue.top(); + total_ms += temp_queue.top(); + temp_queue.pop(); + } + + for (size_t i = num_ticks / 100; i < num_ticks / 20; i++) { + top_5p_ms += temp_queue.top(); + top_10p_ms += temp_queue.top(); + top_50p_ms += temp_queue.top(); + total_ms += temp_queue.top(); + temp_queue.pop(); + } + + for (size_t i = num_ticks / 20; i < num_ticks / 10; i++) { + top_10p_ms += temp_queue.top(); + top_50p_ms += temp_queue.top(); + total_ms += temp_queue.top(); + temp_queue.pop(); + } + + for (size_t i = num_ticks / 10; i < num_ticks / 2; i++) { + top_50p_ms += temp_queue.top(); + total_ms += temp_queue.top(); + temp_queue.pop(); + } + median_tick_ms = temp_queue.top(); + + for (size_t i = num_ticks / 2; i < num_ticks; i++) { + total_ms += temp_queue.top(); + temp_queue.pop(); + } + + return { + 100 * top_1p_ms / num_ticks, + 20 * top_5p_ms / num_ticks, + 10 * top_10p_ms / num_ticks, + 2 * top_50p_ms / num_ticks, + median_tick_ms, + total_ms / num_ticks + }; +} + +void +TickManager::run_() +{ + using namespace std::chrono; + auto next_tick = steady_clock::now(); + + while (running_) { + next_tick += delay_time_; + std::this_thread::sleep_until(next_tick); + current_tick_++; + auto time = notify_tick_(); + last_1000_tick_times_.push_front(duration_cast(time)); + if (last_1000_tick_times_.size() > 1000) { + last_1000_tick_times_.pop_back(); + } + } +} + +} // namespace ticks +} // namespace nutc diff --git a/exchange/src/exchange/tick_manager/tick_manager.hpp b/exchange/src/exchange/tick_manager/tick_manager.hpp new file mode 100644 index 00000000..048a3d2f --- /dev/null +++ b/exchange/src/exchange/tick_manager/tick_manager.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include "exchange/logging.hpp" +#include "tick_observer.hpp" + +#include +#include + +#include +#include + +namespace nutc { +namespace ticks { + +enum class PRIORITY { first, second, third, fourth }; + +using std::chrono::milliseconds; + +class TickManager { +public: + [[nodiscard]] uint64_t + get_current_tick() const + { + return current_tick_; + } + + void + attach( + TickObserver* observer, PRIORITY priority, const std::string& name = "UNNAMED" + ) + { + log_i( + tick_manager, "Tick engine registered observer {} with priority {}", name, + static_cast(priority) + ); + switch (priority) { + case PRIORITY::first: + first_observers_.push_back(observer); + break; + case PRIORITY::second: + second_observers_.push_back(observer); + break; + case PRIORITY::third: + third_observers_.push_back(observer); + break; + case PRIORITY::fourth: + fourth_observers_.push_back(observer); + break; + } + } + + void + detach(TickObserver* observer, PRIORITY priority) + { + switch (priority) { + case PRIORITY::first: + first_observers_.remove(observer); + break; + case PRIORITY::second: + second_observers_.remove(observer); + break; + case PRIORITY::third: + third_observers_.remove(observer); + break; + case PRIORITY::fourth: + fourth_observers_.remove(observer); + break; + } + } + + struct tick_metrics_t { + milliseconds top_1p_ms; + milliseconds top_5p_ms; + milliseconds top_10p_ms; + milliseconds top_50p_ms; + milliseconds median_tick_ms; + milliseconds avg_tick_ms; + }; + + [[nodiscard]] tick_metrics_t get_tick_metrics() const; + + void + start() + { + running_ = true; + tick_thread_ = std::thread(&TickManager::run_, this); + } + + void + stop() + { + running_ = false; + tick_thread_.join(); + } + +private: + milliseconds delay_time_; + std::atomic running_; + std::thread tick_thread_; + std::list first_observers_; + std::list second_observers_; + std::list third_observers_; + std::list fourth_observers_; + static constexpr uint16_t MS_PER_SECOND = 1000; + + std::deque last_1000_tick_times_; + + explicit TickManager(uint16_t start_tick_rate) : + delay_time_(milliseconds(MS_PER_SECOND / start_tick_rate)) + {} + + auto notify_tick_(); + void run_(); + +public: + TickManager(const TickManager&) = delete; + TickManager(TickManager&&) = delete; + TickManager& operator=(const TickManager&) = delete; + TickManager& operator=(TickManager&&) = delete; + + static TickManager& + get_instance(uint16_t start_tick_rate = 0) + { + static bool has_been_initialized = false; + if (start_tick_rate == 0) { + assert(has_been_initialized); + } + has_been_initialized = true; + + static TickManager manager(start_tick_rate); + return manager; + } + +private: + uint64_t current_tick_ = 0; +}; +} // namespace ticks +} // namespace nutc diff --git a/exchange/src/exchange/tick_manager/tick_observer.hpp b/exchange/src/exchange/tick_manager/tick_observer.hpp new file mode 100644 index 00000000..800befee --- /dev/null +++ b/exchange/src/exchange/tick_manager/tick_observer.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace nutc { +namespace ticks { + +class TickObserver { +public: + virtual ~TickObserver() = default; + + virtual void on_tick(uint64_t new_tick) = 0; +}; +} // namespace ticks +} // namespace nutc diff --git a/exchange/src/exchange/tickers/engine/engine.cpp b/exchange/src/exchange/tickers/engine/engine.cpp new file mode 100644 index 00000000..e0bfec35 --- /dev/null +++ b/exchange/src/exchange/tickers/engine/engine.cpp @@ -0,0 +1,262 @@ +#include "engine.hpp" + +#include "exchange/tickers/engine/order_storage.hpp" +#include "exchange/traders/trader_types.hpp" +#include "exchange/utils/logger/logger.hpp" + +#include +#include + +namespace nutc { +namespace matching { + +// TODO(anyone): unit test for added orders +on_tick_result_t +Engine::on_tick(uint64_t new_tick, uint8_t order_expire_age) +{ + current_tick_ = new_tick; + uint64_t removed_tick_age = new_tick - order_expire_age; + + while (!orders_by_tick_.empty()) { + uint64_t earliest_tick = orders_by_tick_.begin()->first; + if (earliest_tick > removed_tick_age) { + break; + } + std::queue& order_ids = orders_by_tick_.at(earliest_tick); + while (!order_ids.empty()) { + uint64_t order_id = order_ids.front(); + order_ids.pop(); + + if (orders_by_id_.find(order_id) == orders_by_id_.end()) { + continue; + } + + double price = orders_by_id_.at(order_id).price; + SIDE side = orders_by_id_.at(order_id).side; + if (side == SIDE::BUY) + bids_.erase(order_index{price, order_id}); + else + asks_.erase(order_index{price, order_id}); + removed_orders_.push_back(std::move(orders_by_id_.at(order_id))); + orders_by_id_.erase(order_id); + } + orders_by_tick_.erase(earliest_tick); + } + + std::vector return_vec = std::move(removed_orders_); + removed_orders_.clear(); + + std::vector added_orders = std::move(added_orders_); + added_orders_.clear(); + + std::vector matched_orders = std::move(matched_orders_); + matched_orders_.clear(); + + return {return_vec, added_orders, matched_orders}; +} + +void +add_ob_update(std::vector& vec, StoredOrder& order, double quantity) +{ + vec.push_back(ObUpdate{order.ticker, order.side, order.price, quantity}); +} + +bool +insufficient_capital(const StoredOrder& order) +{ + double capital = order.trader->get_capital(); + double order_value = order.price * order.quantity; + return order.side == SIDE::BUY && order_value > capital; +} + +bool +insufficient_holdings(const StoredOrder& order) +{ + double holdings = order.trader->get_holdings(order.ticker); + return order.side == SIDE::SELL && order.quantity > holdings; +} + +match_result_t +Engine::match_order(MarketOrder&& order, manager::ClientManager& manager) +{ + match_result_t result; + StoredOrder stored_order(std::move(order), current_tick_); + + if (insufficient_capital(stored_order)) { + removed_orders_.push_back(stored_order); + return result; + } + + if (insufficient_holdings(stored_order)) { + removed_orders_.push_back(stored_order); + return result; + } + + add_order(stored_order); + + match_result_t res = attempt_matches_(manager, stored_order); + + events::Logger& logger = events::Logger::get_logger(); + + // Log information from res + for (const auto& match : res.matches) { + logger.log_event(match); + } + + for (const auto& ob_update : res.ob_updates) { + logger.log_event(ob_update); + } + + return res; +} + +constexpr bool +is_close_to_zero(double value, double epsilon = std::numeric_limits::epsilon()) +{ + return std::fabs(value) < epsilon; +} + +constexpr bool +is_same_value( + double value1, double value2, + double epsilon = std::numeric_limits::epsilon() +) +{ + return std::fabs(value1 - value2) < epsilon; +} + +double +get_match_quantity( + const StoredOrder& passive_order, const StoredOrder& aggressive_order +) +{ + return std::min(passive_order.quantity, aggressive_order.quantity); +} + +SIDE +get_aggressive_side(const StoredOrder& order1, const StoredOrder& order2) +{ + return order1.order_index > order2.order_index ? order1.side : order2.side; +} + +match_result_t +Engine::attempt_matches_( // NOLINT (cognitive-complexity-*) + manager::ClientManager& manager, StoredOrder& aggressive_order +) +{ + match_result_t result; + double aggressive_quantity = aggressive_order.quantity; + uint64_t aggressive_index = aggressive_order.order_index; + + while (can_match_orders_()) { + StoredOrder& sell_order_ref = + get_top_order_(SIDE::SELL).value().get(); // NOLINT(*) + StoredOrder& buy_order_ref = + get_top_order_(SIDE::BUY).value().get(); // NOLINT(*) + + double quantity_to_match = get_match_quantity(buy_order_ref, sell_order_ref); + SIDE aggressive_side = get_aggressive_side(sell_order_ref, buy_order_ref); + + double price_to_match = + aggressive_side == SIDE::BUY ? sell_order_ref.price : buy_order_ref.price; + + std::string buyer_id = buy_order_ref.trader->get_id(); + std::string seller_id = sell_order_ref.trader->get_id(); + + Match to_match{sell_order_ref.ticker, aggressive_side, price_to_match, + quantity_to_match, buyer_id, seller_id}; + + std::optional match_failure = manager.validate_match(to_match); + if (match_failure.has_value()) { + SIDE side = match_failure.value(); + if (side == SIDE::BUY) { + bids_.erase(bids_.begin()); + orders_by_id_.erase(buy_order_ref.order_index); + removed_orders_.push_back(buy_order_ref); + } + else { + asks_.erase(asks_.begin()); + orders_by_id_.erase(sell_order_ref.order_index); + removed_orders_.push_back(sell_order_ref); + } + continue; + } + + // Now that we know the match is valid, we can make copies of the order and + // delete them from the tables + // This could be optimized, but it's good for now + StoredOrder sell_order = sell_order_ref; + StoredOrder buy_order = buy_order_ref; + + removed_orders_.push_back(sell_order); + removed_orders_.push_back(buy_order); + + matched_orders_.push_back(Match{ + "", aggressive_side, price_to_match, quantity_to_match, buyer_id, seller_id + }); + + orders_by_id_.erase(buy_order_ref.order_index); + orders_by_id_.erase(sell_order_ref.order_index); + bids_.erase(bids_.begin()); + asks_.erase(asks_.begin()); + + buy_order.quantity -= quantity_to_match; + sell_order.quantity -= quantity_to_match; + + events::Logger& logger = events::Logger::get_logger(); + logger.log_event(to_match); + + result.matches.push_back(to_match); + + bool sell_aggressive = sell_order.order_index == aggressive_index; + bool buy_aggressive = buy_order.order_index == aggressive_index; + + if (buy_aggressive) + aggressive_quantity -= quantity_to_match; + else + add_ob_update(result.ob_updates, buy_order, 0); + + if (sell_aggressive) + aggressive_quantity -= quantity_to_match; + else + add_ob_update(result.ob_updates, sell_order, 0); + + if (!is_close_to_zero(buy_order.quantity)) { + if (!buy_aggressive) + add_ob_update(result.ob_updates, buy_order, buy_order.quantity); + add_order(buy_order); + added_orders_.push_back(buy_order); + } + + if (!is_close_to_zero(sell_order.quantity)) { + if (!sell_aggressive) + add_ob_update(result.ob_updates, sell_order, sell_order.quantity); + add_order(sell_order); + added_orders_.push_back(sell_order); + } + + auto update_traders = [&](const std::unique_ptr& trader, + SIDE side) { + if (side == SIDE::BUY) { + trader->modify_capital(-quantity_to_match * price_to_match); + trader->modify_holdings(buy_order.ticker, quantity_to_match); + } + else { + trader->modify_capital(quantity_to_match * price_to_match); + trader->modify_holdings(buy_order.ticker, -quantity_to_match); + } + }; + + update_traders(buy_order.trader, SIDE::BUY); + update_traders(sell_order.trader, SIDE::SELL); + } + + if (aggressive_quantity > 0) { + add_ob_update(result.ob_updates, aggressive_order, aggressive_quantity); + } + + return result; +} + +} // namespace matching +} // namespace nutc diff --git a/exchange/src/exchange/tickers/engine/engine.hpp b/exchange/src/exchange/tickers/engine/engine.hpp new file mode 100644 index 00000000..5bc878f6 --- /dev/null +++ b/exchange/src/exchange/tickers/engine/engine.hpp @@ -0,0 +1,175 @@ +#pragma once + +#include "exchange/traders/trader_manager.hpp" +#include "order_storage.hpp" +#include "shared/messages_exchange_to_wrapper.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" + +#include +#include +#include +#include + +using MarketOrder = nutc::messages::MarketOrder; +using ObUpdate = nutc::messages::ObUpdate; +using Match = nutc::messages::Match; +using SIDE = nutc::messages::SIDE; + +// this class is very messy and should be refactored at some point +// there's minimal abstraction to support high performance, but this makes the code look +// like garbage + +namespace nutc { +/** + * @brief Handles matching for an arbitrary ticker + */ +namespace matching { + +// Later, we can combine these +struct match_result_t { + std::vector matches; + std::vector ob_updates; +}; + +struct on_tick_result_t { + std::vector removed_orders; + std::vector added_orders; + std::vector matched_orders; +}; + +class Engine { +public: + /** + * @brief Matches the given order against the current order book. + * @param aggressive_order The order to match against the order book. + * @param manager ClientManager to verify validity of orders/matches (correct + * funds/holdings) + * @return a MatchResult containing all matches and a vector containing the + * orderbook updates + */ + match_result_t match_order(MarketOrder&& order, manager::ClientManager& manager); + + std::pair + get_spread_nums() const + { + return {asks_.size(), bids_.size()}; + } + + std::pair + get_spread() const + { + if (asks_.empty() || bids_.empty()) [[unlikely]] { + return {0, 0}; + } + return {asks_.begin()->price, bids_.rbegin()->price}; + } + + double + get_midprice() + { + if (asks_.empty() || bids_.empty()) [[unlikely]] { + return last_midprice_; + } + last_midprice_ = (asks_.begin()->price + bids_.rbegin()->price) / 2; + return last_midprice_; + } + + void + add_order(const MarketOrder& order) + { + return add_order(StoredOrder( + manager::ClientManager::get_instance().get_trader(order.client_id), + order.side, order.ticker, order.quantity, order.price, this->current_tick_ + )); + } + + void + set_initial_price(double price) + { + last_midprice_ = price; + } + + void + add_order(const StoredOrder& stored_order) + { + switch (stored_order.side) { + case SIDE::BUY: + bids_.insert({stored_order.price, stored_order.order_index}); + break; + case SIDE::SELL: + asks_.insert({stored_order.price, stored_order.order_index}); + } + + orders_by_id_.emplace(stored_order.order_index, stored_order); + orders_by_tick_[stored_order.tick].push(stored_order.order_index); + } + + // Called every tick + on_tick_result_t on_tick(uint64_t new_tick, uint8_t order_expire_age); + +private: + uint64_t current_tick_ = 0; + double last_midprice_; + + match_result_t + attempt_matches_(manager::ClientManager& manager, StoredOrder& aggressive_order); + + // both map/sort price, order_index + std::set bids_; + std::set asks_; + + // order index -> order + std::unordered_map orders_by_id_; + + // tick -> queue of order ids + std::map> orders_by_tick_; + + std::vector removed_orders_{}; + std::vector added_orders_{}; + std::vector matched_orders_{}; + + template + std::optional> + get_order_from_set_(std::set& order_set) + { + if (order_set.empty()) { + return std::nullopt; + } + + auto order_id = order_set.begin()->index; + while (orders_by_id_.find(order_id) == orders_by_id_.end()) { + order_set.erase(order_set.begin()); + if (order_set.empty()) { + return std::nullopt; + } + order_id = order_set.begin()->index; + } + return orders_by_id_.at(order_id); + } + + std::optional> + get_top_order_(SIDE side) + { + switch (side) { + case SIDE::BUY: + return get_order_from_set_(bids_); + case SIDE::SELL: + return get_order_from_set_(asks_); + default: + throw std::invalid_argument("Unknown side"); + } + } + + bool + can_match_orders_() + { + auto bid_order = get_top_order_(SIDE::BUY); + auto ask_order = get_top_order_(SIDE::SELL); + if (!bid_order.has_value() || !ask_order.has_value()) { + return false; + } + return bid_order.value().get().can_match(ask_order.value().get()); + } +}; +} // namespace matching +} // namespace nutc diff --git a/exchange/src/exchange/tickers/engine/order_storage.hpp b/exchange/src/exchange/tickers/engine/order_storage.hpp new file mode 100644 index 00000000..3f94bd00 --- /dev/null +++ b/exchange/src/exchange/tickers/engine/order_storage.hpp @@ -0,0 +1,151 @@ +#pragma once +#include "exchange/traders/trader_manager.hpp" +#include "exchange/traders/trader_types.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" + +#include + +#include + +namespace nutc { +namespace matching { + +using SIDE = messages::SIDE; + +struct StoredOrder { + const std::unique_ptr& trader; + std::string ticker; + SIDE side; + double price; + double quantity; + uint64_t tick; + + // Used to sort orders by time created + uint64_t order_index; + + StoredOrder() = default; + + static uint64_t + get_and_increment_global_index() + { + static uint64_t global_index = 0; + return global_index++; + } + + StoredOrder( + const std::unique_ptr& trader, SIDE side, + std::string ticker, double quantity, double price, uint64_t tick + ) : + trader(trader), + ticker(std::move(ticker)), side(side), price(price), quantity(quantity), + tick(tick), order_index(get_and_increment_global_index()) + {} + + explicit StoredOrder(messages::MarketOrder&& other, uint64_t tick) : + + trader(manager::ClientManager::get_instance().get_trader(other.client_id)), + ticker(std::move(other.ticker)), side(other.side), price(other.price), + quantity(other.quantity), tick(tick), + order_index(get_and_increment_global_index()) + {} + + bool + operator==(const StoredOrder& other) const + { + return trader->get_id() == other.trader->get_id() && ticker == other.ticker + && side == other.side && util::is_close_to_zero(price - other.price) + && util::is_close_to_zero(quantity - other.quantity); + } + + // toString + [[nodiscard]] std::string + to_string() const + { + std::string side_str = side == SIDE::BUY ? "BUY" : "SELL"; + return fmt::format( + "StoredOrder(client_id={}, side={}, ticker={}, quantity={}, " + "price={})", + trader->get_id(), side_str, ticker, quantity, price + ); + } + + int + operator<=>(const StoredOrder& other) const + { + // assuming both sides are same + // otherwise, this shouldn't even be called + if (util::is_close_to_zero(this->price - other.price)) { + if (this->order_index > other.order_index) + return -1; + if (this->order_index < other.order_index) + return 1; + return 0; + } + if (this->side == SIDE::BUY) { + if (this->price < other.price) + return -1; + if (this->price > other.price) + return 1; + return 0; + } + if (this->side == SIDE::SELL) { + if (this->price > other.price) + return -1; + if (this->price < other.price) + return 1; + return 0; + } + return 0; + } + + [[nodiscard]] bool + can_match(const StoredOrder& other) const + { + if (this->side == other.side) [[unlikely]] { + return false; + } + if (this->ticker != other.ticker) [[unlikely]] { + return false; + } + if (this->side == SIDE::BUY && this->price < other.price) { + return false; + } + if (this->side == SIDE::SELL && this->price > other.price) { + return false; + } + return true; + } + + StoredOrder(const StoredOrder& other) = default; +}; + +struct order_index { + double price; + uint64_t index; +}; + +// Want highest first +struct bid_comparator { + bool + operator()(const order_index& lhs, const order_index& rhs) const + { + if (lhs.price != rhs.price) { + return lhs.price > rhs.price; + } + return lhs.index < rhs.index; + } +}; + +// Want lowest first +struct ask_comparator { + bool + operator()(const order_index& lhs, const order_index& rhs) const + { + if (lhs.price != rhs.price) { + return lhs.price < rhs.price; + } + return lhs.index < rhs.index; + } +}; +} // namespace matching +} // namespace nutc diff --git a/exchange/src/exchange/tickers/manager/ticker_manager.cpp b/exchange/src/exchange/tickers/manager/ticker_manager.cpp new file mode 100644 index 00000000..a7794e92 --- /dev/null +++ b/exchange/src/exchange/tickers/manager/ticker_manager.cpp @@ -0,0 +1,40 @@ +#include "ticker_manager.hpp" + +namespace nutc { +namespace engine_manager { +matching::Engine& +EngineManager::get_engine(const std::string& ticker) +{ + auto engine = engines_.find(ticker); + assert(engine != engines_.end()); + return std::reference_wrapper(engine->second); +} + +void +EngineManager::set_initial_price_(const std::string& ticker, double price) +{ + auto engine = engines_.find(ticker); + assert(engine != engines_.end()); + engine->second.set_initial_price(price); +} + +void +EngineManager::add_engine(const std::string& ticker, double starting_price) +{ + engines_.emplace(ticker, matching::Engine()); + set_initial_price_(ticker, starting_price); + bot_containers_.emplace( + std::piecewise_construct, std::forward_as_tuple(ticker), + std::forward_as_tuple(ticker, starting_price) + ); +} + +// for testing +void +EngineManager::add_engine(const std::string& ticker) +{ + engines_.emplace(ticker, matching::Engine()); +} + +} // namespace engine_manager +} // namespace nutc diff --git a/exchange/src/exchange/tickers/manager/ticker_manager.hpp b/exchange/src/exchange/tickers/manager/ticker_manager.hpp new file mode 100644 index 00000000..c8d5620f --- /dev/null +++ b/exchange/src/exchange/tickers/manager/ticker_manager.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include "exchange/bots/bot_container.hpp" +#include "exchange/config.h" +#include "exchange/tick_manager/tick_observer.hpp" +#include "exchange/tickers/engine/engine.hpp" +#include "exchange/tickers/engine/order_storage.hpp" + +#include + +namespace nutc { +namespace engine_manager { + +class EngineManager : public nutc::ticks::TickObserver { +public: + matching::Engine& get_engine(const std::string& ticker); + + bots::BotContainer& + get_bot_container(const std::string& ticker) + { + return bot_containers_.at(ticker); + } + + void add_engine(const std::string& ticker, double starting_price); + void add_engine(const std::string& ticker); + + void + on_tick(uint64_t new_tick) override + { + if (new_tick < ORDER_EXPIRATION_TIME) + return; + for (auto& [ticker, engine] : engines_) { + auto [removed, added, matched] = + engine.on_tick(new_tick, ORDER_EXPIRATION_TIME); + + for (matching::StoredOrder& order : added) { + if (order.trader->get_type() != manager::TraderType::BOT) + continue; + bot_containers_.at(order.ticker) + .process_order_add( + order.trader->get_id(), order.side, order.price * order.quantity + ); + } + + for (matching::StoredOrder& order : removed) { + if (order.trader->get_type() != manager::TraderType::BOT) + continue; + bot_containers_.at(order.ticker) + .process_order_expiration( + order.trader->get_id(), order.side, order.price * order.quantity + ); + } + + for (Match& order : matched) { + // TODO(stevenewald): check if bot beforehand? + bot_containers_.at(ticker).process_order_match(order); + } + } + } + + // For testing + void + reset() + { + engines_.clear(); + bot_containers_.clear(); + } + +private: + std::map engines_; + std::unordered_map bot_containers_; + EngineManager() = default; + void set_initial_price_(const std::string& ticker, double price); + +public: + // fuck it, everything's a singleton + + static EngineManager& + get_instance() + { + static EngineManager instance; + return instance; + } + + EngineManager(EngineManager const&) = delete; + EngineManager operator=(EngineManager const&) = delete; + EngineManager(EngineManager&&) = delete; + EngineManager operator=(EngineManager&&) = delete; +}; +} // namespace engine_manager +} // namespace nutc diff --git a/exchange/src/exchange/traders/trader_manager.cpp b/exchange/src/exchange/traders/trader_manager.cpp new file mode 100644 index 00000000..849dc953 --- /dev/null +++ b/exchange/src/exchange/traders/trader_manager.cpp @@ -0,0 +1,25 @@ +#include "trader_manager.hpp" + +namespace nutc { +namespace manager { + +std::optional +ClientManager::validate_match(const messages::Match& match) const +{ + double trade_value = match.price * match.quantity; + double remaining_capital = get_trader(match.buyer_id)->get_capital() - trade_value; + + bool insufficient_holdings = + get_trader(match.seller_id)->get_holdings(match.ticker) - match.quantity < 0; + + if (remaining_capital < 0) [[unlikely]] + return messages::SIDE::BUY; + + if (insufficient_holdings) [[unlikely]] + return messages::SIDE::SELL; + + return std::nullopt; +} + +} // namespace manager +} // namespace nutc diff --git a/exchange/src/exchange/traders/trader_manager.hpp b/exchange/src/exchange/traders/trader_manager.hpp new file mode 100644 index 00000000..f72163fc --- /dev/null +++ b/exchange/src/exchange/traders/trader_manager.hpp @@ -0,0 +1,103 @@ +#pragma once +// keep track of active users and account information +#include "shared/messages_exchange_to_wrapper.hpp" +#include "trader_types.hpp" + +#include + +#include +#include +#include + +namespace nutc { +namespace manager { + +class ClientManager { + std::unordered_map> traders_; + +public: + void + add_remote_trader(const std::string& user_id, const std::string& algo_id) + { + traders_.emplace(user_id, std::make_unique(user_id, algo_id)); + } + + void + add_local_trader(const std::string& algo_id) + { + traders_.emplace(algo_id, std::make_unique(algo_id)); + } + + // returns internal id + [[nodiscard]] std::string + add_bot_trader() + { + std::unique_ptr bot = std::make_unique(); + std::string bot_id = bot->get_id(); + traders_.emplace(bot_id, std::move(bot)); + return bot_id; + } + + [[nodiscard]] const std::unique_ptr& + get_trader(const std::string& trader_id) const + { + assert(user_exists_(trader_id)); + return traders_.at(trader_id); + } + + [[nodiscard]] std::unique_ptr& + get_trader(const std::string& trader_id) + { + assert(user_exists_(trader_id)); + return traders_.at(trader_id); + } + + std::unordered_map>& + get_traders() + { + return traders_; + } + + const std::unordered_map>& + get_traders() const + { + return traders_; + } + + [[nodiscard]] std::optional + validate_match(const messages::Match& match) const; + + // Primarily for testing + void + reset() + { + traders_.clear(); + } + +private: + bool + user_exists_(const std::string& user_id) const + { + return traders_.contains(user_id); + } + + ClientManager() = default; + ~ClientManager() = default; + +public: + // Singleton + static ClientManager& + get_instance() + { + static ClientManager instance; + return instance; + } + + ClientManager(const ClientManager&) = delete; + ClientManager(ClientManager&&) = delete; + ClientManager& operator=(const ClientManager&) = delete; + ClientManager& operator=(ClientManager&&) = delete; +}; + +} // namespace manager +} // namespace nutc diff --git a/exchange/src/exchange/traders/trader_types.hpp b/exchange/src/exchange/traders/trader_types.hpp new file mode 100644 index 00000000..01c4c8d7 --- /dev/null +++ b/exchange/src/exchange/traders/trader_types.hpp @@ -0,0 +1,213 @@ +#pragma once + +#include +#include +#include +#include + +namespace nutc { +namespace manager { + +enum TraderType { REMOTE, LOCAL, BOT }; + +class GenericTrader { +public: + std::string + get_id() const + { + return USER_ID; + } + + virtual TraderType get_type() const = 0; + + double + get_capital() const + { + return capital_; + } + + bool + is_active() const + { + return is_active_; + } + + void + set_active(bool active) + { + is_active_ = active; + } + + bool + has_start_delay() const + { + return has_start_delay_; + } + + void + set_start_delay(bool start_delay) + { + has_start_delay_ = start_delay; + } + + double + get_holdings(const std::string& ticker) const + { + if (!holdings_.contains(ticker)) + return 0.0; + + return holdings_.at(ticker); + } + + double + modify_holdings(const std::string& ticker, double change_in_holdings) + { + holdings_[ticker] += change_in_holdings; + return holdings_[ticker]; + } + + void + set_capital(double capital) + { + capital_ = capital; + } + + void + modify_capital(double change_in_capital) + { + capital_ += change_in_capital; + } + + virtual void set_pid(pid_t pid) = 0; + virtual pid_t get_pid() const = 0; + + virtual std::string get_algo_id() const = 0; + + explicit GenericTrader(std::string user_id) : USER_ID(std::move(user_id)) {} + + virtual ~GenericTrader() = default; + GenericTrader& operator=(GenericTrader&& other) = delete; + GenericTrader& operator=(const GenericTrader& other) = delete; + GenericTrader(GenericTrader&& other) = default; + GenericTrader(const GenericTrader& other) = delete; + +private: + const std::string USER_ID; + double capital_ = 0; + bool is_active_ = false; + bool has_start_delay_ = true; + std::unordered_map holdings_{}; +}; + +class RemoteTrader : public GenericTrader { +public: + RemoteTrader(std::string user_id, std::string algo_id) : + GenericTrader(std::move(user_id)), algo_id_(std::move(algo_id)) + {} + + constexpr TraderType + get_type() const override + { + return TraderType::REMOTE; + } + + void + set_pid(pid_t pid) override + { + pid_ = pid; + } + + pid_t + get_pid() const override + { + return pid_; + } + + std::string + get_algo_id() const override + { + return algo_id_; + } + +private: + std::string algo_id_; + pid_t pid_{}; +}; + +class LocalTrader : public GenericTrader { +public: + explicit LocalTrader(std::string algo_path) : GenericTrader(std::move(algo_path)) {} + + constexpr TraderType + get_type() const override + { + return TraderType::LOCAL; + } + + std::string + get_algo_id() const override + { + return get_id(); + } + + void + set_pid(pid_t pid) override + { + pid_ = pid; + } + + pid_t + get_pid() const override + { + return pid_; + } + +private: + pid_t pid_{}; +}; + +class BotTrader : public GenericTrader { +public: + BotTrader() : GenericTrader(generate_user_id()) {} + + constexpr TraderType + get_type() const override + { + return TraderType::BOT; + } + + pid_t + get_pid() const override + { + return -1; + } + + void + set_pid(pid_t) override + {} + + std::string + get_algo_id() const override + { + throw std::runtime_error("Not implemented"); + } + +private: + static uint + get_and_increment_user_id() + { + static uint user_id = 0; + return user_id++; + } + + static std::string + generate_user_id() + { + return "BOT_" + std::to_string(get_and_increment_user_id()); + } +}; + +using trader_t = std::variant; + +} // namespace manager +} // namespace nutc diff --git a/exchange/src/utils/doxygen_main.hpp b/exchange/src/exchange/utils/doxygen_main.hpp similarity index 100% rename from exchange/src/utils/doxygen_main.hpp rename to exchange/src/exchange/utils/doxygen_main.hpp diff --git a/exchange/src/utils/logger/logger.cpp b/exchange/src/exchange/utils/logger/logger.cpp similarity index 78% rename from exchange/src/utils/logger/logger.cpp rename to exchange/src/exchange/utils/logger/logger.cpp index 0683720f..4d02b276 100644 --- a/exchange/src/utils/logger/logger.cpp +++ b/exchange/src/exchange/utils/logger/logger.cpp @@ -1,4 +1,7 @@ -#include "utils/logger/logger.hpp" // includes fstream, string, optional +#include "exchange/utils/logger/logger.hpp" // includes fstream, string, optional + +#include "shared/messages_exchange_to_wrapper.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" #include #include @@ -17,6 +20,12 @@ Logger::get_logger() return logger; } +void +Logger::flush() +{ + output_file_.flush(); +} + std::string timestamp_in_ms() { @@ -40,7 +49,8 @@ Logger::log_event(const T& json_message) { // If file is not open, throw an error to the error logger if (!output_file_.is_open()) [[unlikely]] { - log_e(events, "Output file {} not open, unable to log event", get_file_name()); + // log_e(events, "Output file {} not open, unable to log event", + // get_file_name()); return; } @@ -50,10 +60,11 @@ Logger::log_event(const T& json_message) std::string buffer = glz::write_json(json_message_with_ts); // Log to the main log too - log_i(events, "Logging event {}", buffer); + // log_i(events, "Logging event {}", buffer); // Write start of JSON output_file_ << buffer << "\n"; + output_file_.flush(); } // Explicit instantiations diff --git a/exchange/src/utils/logger/logger.hpp b/exchange/src/exchange/utils/logger/logger.hpp similarity index 93% rename from exchange/src/utils/logger/logger.hpp rename to exchange/src/exchange/utils/logger/logger.hpp index ecd1f289..55225e42 100644 --- a/exchange/src/utils/logger/logger.hpp +++ b/exchange/src/exchange/utils/logger/logger.hpp @@ -1,8 +1,9 @@ #pragma once -#include "config.h" -#include "logging.hpp" -#include "utils/messages.hpp" // TYPE should be an enum {AccountUpdate, OrderbookUpdate, TradeUpdate, MarketOrder} +#include "exchange/config.h" +#include "exchange/logging.hpp" + +#include #include #include @@ -57,6 +58,8 @@ class Logger { static Logger& get_logger(); + void flush(); + // Logger(const Logger&) = delete; // Logger(Logger&&) = delete; // Logger& operator=(const Logger&) = delete; diff --git a/exchange/src/matching/engine/engine.cpp b/exchange/src/matching/engine/engine.cpp deleted file mode 100644 index ddd04532..00000000 --- a/exchange/src/matching/engine/engine.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include "engine.hpp" - -#include "utils/logger/logger.hpp" - -#include -#include - -namespace nutc { -namespace matching { - -void -Engine::add_order_without_matching(const MarketOrder& order) -{ - if (order.side == SIDE::BUY) - bids_.push(order); - else - asks_.push(order); -} - -ObUpdate -create_ob_update(const MarketOrder& order, float quantity) -{ - return ObUpdate{order.ticker, order.side, order.price, quantity}; -} - -void -add_ob_update(std::vector& vec, const MarketOrder& order, float quantity) -{ - vec.push_back(create_ob_update(order, quantity)); -} - -std::priority_queue& -Engine::get_orders_(SIDE side) -{ - return side == SIDE::SELL ? this->asks_ : this->bids_; -} - -bool -Engine::insufficient_capital( - const MarketOrder& order, const manager::ClientManager& manager -) -{ - float capital = manager.get_capital(order.client_id); - float order_value = order.price * order.quantity; - return order.side == SIDE::BUY && order_value > capital; -} - -bool -insufficient_holdings(const MarketOrder& order, const manager::ClientManager& manager) -{ - float holdings = manager.get_holdings(order.client_id, order.ticker); - return order.side == SIDE::SELL && order.quantity > holdings; -} - -match_result_t -Engine::match_order(MarketOrder& order, manager::ClientManager& manager) -{ - match_result_t result; - - if (insufficient_capital(order, manager)) { - return result; - } - - if (insufficient_holdings(order, manager)) { - return result; - } - - get_orders_(order.side).push(order); - - match_result_t res = attempt_matches_(manager, order); - - events::Logger& logger = events::Logger::get_logger(); - - // Log information from res - for (const auto& match : res.matches) { - logger.log_event(match); - } - - for (const auto& ob_update : res.ob_updates) { - logger.log_event(ob_update); - } - - return res; -} - -constexpr bool -is_close_to_zero(float value, float epsilon = std::numeric_limits::epsilon()) -{ - return std::fabs(value) < epsilon; -} - -constexpr bool -is_same_value( - float value1, float value2, float epsilon = std::numeric_limits::epsilon() -) -{ - return std::fabs(value1 - value2) < epsilon; -} - -float -Engine::get_match_quantity( - const MarketOrder& passive_order, const MarketOrder& aggressive_order -) -{ - return std::min(passive_order.quantity, aggressive_order.quantity); -} - -std::string -Engine::get_client_id( - SIDE side, const MarketOrder& aggressive, const MarketOrder& passive -) -{ - return side == aggressive.side ? aggressive.client_id : passive.client_id; -} - -SIDE -Engine::get_aggressive_side(const MarketOrder& order1, const MarketOrder& order2) -{ - return order1.order_index > order2.order_index ? order1.side : order2.side; -} - -match_result_t -Engine::attempt_matches_( // NOLINT (cognitive-complexity-*) - manager::ClientManager& manager, const MarketOrder& aggressive_order -) -{ - match_result_t result; - float aggressive_quantity = aggressive_order.quantity; - int64_t aggressive_index = aggressive_order.order_index; - - while (!bids_.empty() && !asks_.empty() && bids_.top().can_match(asks_.top())) { - MarketOrder sell_order = asks_.top(); - MarketOrder buy_order = bids_.top(); - - float quantity_to_match = get_match_quantity(buy_order, sell_order); - SIDE aggressive_side = get_aggressive_side(sell_order, buy_order); - - float price_to_match = - aggressive_side == SIDE::BUY ? sell_order.price : buy_order.price; - - std::string buyer_id = buy_order.client_id; - std::string seller_id = sell_order.client_id; - - Match to_match{sell_order.ticker, aggressive_side, price_to_match, - quantity_to_match, buyer_id, seller_id}; - - std::optional match_failure = manager.validate_match(to_match); - if (match_failure.has_value()) { - SIDE side = match_failure.value(); - if (side == SIDE::BUY) - bids_.pop(); - else - asks_.pop(); - continue; - } - - bids_.pop(); - asks_.pop(); - - buy_order.quantity -= quantity_to_match; - sell_order.quantity -= quantity_to_match; - - events::Logger& logger = events::Logger::get_logger(); - logger.log_event(to_match); - - result.matches.push_back(to_match); - - bool sell_aggressive = sell_order.order_index == aggressive_index; - bool buy_aggressive = buy_order.order_index == aggressive_index; - - if (buy_aggressive) - aggressive_quantity -= quantity_to_match; - else - add_ob_update(result.ob_updates, buy_order, 0); - - if (sell_aggressive) - aggressive_quantity -= quantity_to_match; - else - add_ob_update(result.ob_updates, sell_order, 0); - - if (!is_close_to_zero(buy_order.quantity)) { - if (!buy_aggressive) - add_ob_update(result.ob_updates, buy_order, buy_order.quantity); - bids_.push(buy_order); - } - - if (!is_close_to_zero(sell_order.quantity)) { - if (!sell_aggressive) - add_ob_update(result.ob_updates, sell_order, sell_order.quantity); - asks_.push(sell_order); - } - - manager.modify_capital(buyer_id, -quantity_to_match * price_to_match); - manager.modify_capital(seller_id, quantity_to_match * price_to_match); - manager.modify_holdings(seller_id, buy_order.ticker, -quantity_to_match); - manager.modify_holdings(buyer_id, buy_order.ticker, quantity_to_match); - } - - if (aggressive_quantity > 0) { - add_ob_update(result.ob_updates, aggressive_order, aggressive_quantity); - } - - return result; -} - -} // namespace matching -} // namespace nutc diff --git a/exchange/src/matching/engine/engine.hpp b/exchange/src/matching/engine/engine.hpp deleted file mode 100644 index 1f7a4a10..00000000 --- a/exchange/src/matching/engine/engine.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "client_manager/client_manager.hpp" -#include "utils/messages.hpp" - -#include -#include - -using MarketOrder = nutc::messages::MarketOrder; -using ObUpdate = nutc::messages::ObUpdate; -using Match = nutc::messages::Match; -using SIDE = nutc::messages::SIDE; - -namespace nutc { -/** - * @brief Handles matching for an arbitrary ticker - */ -namespace matching { - -struct match_result_t { - std::vector matches; - std::vector ob_updates; -}; - -class Engine { -public: - /** - * @brief Matches the given order against the current order book. - * @param aggressive_order The order to match against the order book. - * @param manager ClientManager to verify validity of orders/matches (correct - * funds/holdings) - * @return a MatchResult containing all matches and a vector containing the - * orderbook updates - */ - match_result_t match_order(MarketOrder& order, manager::ClientManager& manager); - - void add_order_without_matching(const MarketOrder& order); - -private: - std::priority_queue bids_; - std::priority_queue asks_; - static std::string - get_client_id(SIDE side, const MarketOrder& aggressive, const MarketOrder& passive); - static float get_match_quantity( - const MarketOrder& passive_order, const MarketOrder& aggressive_order - ); - - std::priority_queue& get_orders_(SIDE side); - - match_result_t attempt_matches_( - manager::ClientManager& manager, const MarketOrder& aggressive_order - ); - static SIDE - get_aggressive_side(const MarketOrder& order1, const MarketOrder& order2); - static bool insufficient_capital( - const MarketOrder& order, const manager::ClientManager& manager - ); -}; -} // namespace matching -} // namespace nutc diff --git a/exchange/src/matching/manager/engine_manager.cpp b/exchange/src/matching/manager/engine_manager.cpp deleted file mode 100644 index c6776929..00000000 --- a/exchange/src/matching/manager/engine_manager.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "engine_manager.hpp" - -namespace nutc { -namespace engine_manager { -std::optional -Manager::get_engine(const std::string& ticker) -{ - auto engine = engines_.find(ticker); - if (engine != engines_.end()) { - return std::reference_wrapper(engine->second); - } - return std::nullopt; -} - -void -Manager::add_initial_liquidity(const std::string& ticker, float quantity, float price) -{ - MarketOrder to_add{"SIMULATED", messages::SIDE::SELL, ticker, quantity, price}; - auto engine = engines_.find(ticker); - if (engine != engines_.end()) { - engine->second.add_order_without_matching(to_add); - } -} - -void -Manager::add_engine(const std::string& ticker) -{ - if (engines_.find(ticker) == engines_.end()) { - engines_.emplace(ticker, matching::Engine()); - } -} - -} // namespace engine_manager -} // namespace nutc diff --git a/exchange/src/matching/manager/engine_manager.hpp b/exchange/src/matching/manager/engine_manager.hpp deleted file mode 100644 index c0437893..00000000 --- a/exchange/src/matching/manager/engine_manager.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include "matching/engine/engine.hpp" - -#include -#include - -using engine_ref_t = std::reference_wrapper; - -namespace nutc { -/** - * @brief Manages all matching engines for arbitrary tickers - */ -namespace engine_manager { -/** - * @class Manager - * @brief Manages all matching engines for arbitrary tickers - * @details This class is responsible for creating and managing all matching engines for - * different tickers - */ -class Manager { -public: - /** - * @brief Returns a reference to the engine with the given ticker, if it exists - * @param ticker The ticker of the engine to return - * @return A reference to the engine with the given ticker, if it exists - */ - std::optional get_engine(const std::string& ticker); - - /** - * @brief Adds an engine with the given ticker - * @param ticker The ticker of the engine to add - * @return A reference to the engine with the given ticker - */ - void add_engine(const std::string& ticker); - - /** @brief Adds initial liquidity by creating fake sell orders for a given ticker at - * a given quantity/price - */ - void add_initial_liquidity(const std::string& ticker, float quantity, float price); - -private: - std::map engines_; -}; -} // namespace engine_manager -} // namespace nutc diff --git a/exchange/src/process_spawning/spawning.cpp b/exchange/src/process_spawning/spawning.cpp deleted file mode 100644 index 7aef876e..00000000 --- a/exchange/src/process_spawning/spawning.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "process_spawning/spawning.hpp" - -#include "logging.hpp" -#include "utils/file_operations/file_operations.hpp" - -namespace nutc { -namespace client { - -std::string -quote_id(std::string user_id) -{ - std::replace(user_id.begin(), user_id.end(), '-', ' '); - return user_id; -} - -size_t -spawn_all_clients(nutc::manager::ClientManager& users, SpawnMode mode) -{ - size_t num_clients = 0; - auto spawn_one_client = [&](const std::pair& pair) { - const auto& [id, client] = pair; - const std::string& algo_id = client.algo_id; - - if (client.active) - return; - - log_i(client_spawning, "Spawning client: {}", id); - - pid_t pid = - spawn_client(quote_id(id), quote_id(algo_id), client.algo_location, mode); - users.set_client_pid(id, pid); - num_clients++; - }; - - const auto& clients = users.get_clients(); - - std::for_each(clients.begin(), clients.end(), spawn_one_client); - - return num_clients; -} - -pid_t -spawn_client( - const std::string& user_id, const std::string& algo_id, - manager::ClientLocation algo_location, SpawnMode spawn_mode -) -{ - using manager::ClientLocation; - - if (algo_location == ClientLocation::LOCAL) { - std::string filepath = algo_id + ".py"; - assert(file_ops::file_exists(filepath)); - } - pid_t pid = fork(); - if (pid == 0) { - std::vector args = { - "NUTC-client", "--uid", user_id, "--algo_id", algo_id - }; - if (algo_location == ClientLocation::LOCAL) { - args.emplace_back("--dev"); - } - if (spawn_mode == SpawnMode::TESTING) { - args.emplace_back("--no-start-delay"); - } - - std::vector c_args; - for (auto& arg : args) { - c_args.emplace_back( // NOLINT(performance-inefficient-vector-operation) - arg.data() - ); - } - c_args.push_back(nullptr); - - execvp(c_args[0], c_args.data()); - - log_e(client_spawning, "Failed to execute NUTC-client"); - - std::abort(); - } - else if (pid < 0) { - log_e(client_spawning, "Failed to fork"); - std::abort(); - } - return pid; -} -} // namespace client -} // namespace nutc diff --git a/exchange/src/process_spawning/spawning.hpp b/exchange/src/process_spawning/spawning.hpp deleted file mode 100644 index 79989bea..00000000 --- a/exchange/src/process_spawning/spawning.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "client_manager/client_manager.hpp" - -#include -#include -#include -#include - -namespace nutc { - -/** @brief Contains all functions related to spawning client processes */ -namespace client { - -enum class SpawnMode { NORMAL, TESTING }; - -/** - * @brief Spawns a client process with the given id - * Forks and execve's a client process with the given id - * Spawns in the binary "NUTC-client", expecting it to be in the $PATH - */ -pid_t spawn_client( - const std::string& user_id, const std::string& algo_id, - manager::ClientLocation algo_location, SpawnMode spawn_mode -); - -/** - * @brief Spawns all clients in the given ClientManager - * @param users The ClientManager to spawn clients for - * @returns the number of clients spawned - */ -size_t spawn_all_clients( - nutc::manager::ClientManager& users, SpawnMode mode = SpawnMode::NORMAL -); - -} // namespace client -} // namespace nutc diff --git a/exchange/src/rabbitmq/order_handler/RabbitMQOrderHandler.hpp b/exchange/src/rabbitmq/order_handler/RabbitMQOrderHandler.hpp deleted file mode 100644 index b231f64d..00000000 --- a/exchange/src/rabbitmq/order_handler/RabbitMQOrderHandler.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "client_manager/client_manager.hpp" -#include "matching/manager/engine_manager.hpp" -#include "utils/messages.hpp" - -#include - -namespace nutc { -namespace rabbitmq { -class RabbitMQOrderHandler { -public: - static void add_liquidity_to_ticker( - manager::ClientManager& clients, engine_manager::Manager& engine_manager, - const std::string& ticker, float quantity, float price - ); - static void handle_incoming_market_order( - engine_manager::Manager& engine_manager, manager::ClientManager& clients, - messages::MarketOrder& order - ); -}; - -} // namespace rabbitmq -} // namespace nutc diff --git a/exchange/src/utils/file_operations/file_operations.cpp b/exchange/src/shared/file_operations/file_operations.cpp similarity index 70% rename from exchange/src/utils/file_operations/file_operations.cpp rename to exchange/src/shared/file_operations/file_operations.cpp index d0c1944c..7ae94a0e 100644 --- a/exchange/src/utils/file_operations/file_operations.cpp +++ b/exchange/src/shared/file_operations/file_operations.cpp @@ -1,10 +1,11 @@ #include "file_operations.hpp" -#include "logging.hpp" - #include #include +#include + +#include #include #include #include @@ -16,21 +17,12 @@ namespace file_ops { void unzip_file(const std::string& src, const std::string& dest) { - if (!file_exists(src)) { - log_e(dev_mode, "Zip file {} does not exist.", src); - return; - } + assert(file_exists(src)); int err = 0; - log_i(dev_mode, "Unzipping file {}", src); zip* zipfile = zip_open(src.c_str(), 0, &err); - if (zipfile == nullptr) { - zip_error_t error; - zip_error_init_with_code(&error, err); - log_e(dev_mode, "Error opening zip file: {}", zip_error_strerror(&error)); - zip_error_fini(&error); - return; - } + + assert(zipfile != nullptr); auto num_entries = static_cast(zip_get_num_entries(zipfile, 0)); for (zip_uint64_t i = 0; i < num_entries; i++) { @@ -39,10 +31,7 @@ unzip_file(const std::string& src, const std::string& dest) zip_stat_index(zipfile, i, 0, &zstat); zip_file* finto = zip_fopen_index(zipfile, i, 0); - if (finto == nullptr) { - log_e(dev_mode, "Error opening file in zip: {}", zip_strerror(zipfile)); - return; - } + assert(finto != nullptr); char* contents = new char[zstat.size]; zip_fread(finto, contents, zstat.size); @@ -68,7 +57,6 @@ create_directory(const std::string& dir) std::filesystem::path dir_path{dir}; if (!std::filesystem::exists(dir_path)) { if (!std::filesystem::create_directory(dir_path)) { - log_e(dev_mode, "Failed to create directory"); return false; } } @@ -85,13 +73,11 @@ file_exists(const std::string& filename) noexcept std::string read_file_content(const std::string& filename) { + assert(file_exists(filename)); std::ifstream file(filename); std::stringstream buffer; - if (!file) { - log_e(dev_mode, "File {} does not exist or could not be opened.", filename); - return ""; - } + assert(file.good()); buffer << file.rdbuf(); return buffer.str(); diff --git a/exchange/src/utils/file_operations/file_operations.hpp b/exchange/src/shared/file_operations/file_operations.hpp similarity index 100% rename from exchange/src/utils/file_operations/file_operations.hpp rename to exchange/src/shared/file_operations/file_operations.hpp diff --git a/exchange/src/shared/messages_exchange_to_wrapper.hpp b/exchange/src/shared/messages_exchange_to_wrapper.hpp new file mode 100644 index 00000000..6ece15b7 --- /dev/null +++ b/exchange/src/shared/messages_exchange_to_wrapper.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "util.hpp" + +#include +#include + +namespace nutc { + +/** + * @brief Contains all types used by glaze and the exchange for orders, matching, + * communication, etc + */ +namespace messages { + +struct StartTime { + int64_t start_time_ns; +}; + +/** + * @brief Sent by exchange to a client to indicate a match has occurred + */ +struct Match { + std::string ticker; + SIDE side; + double price; + double quantity; + std::string buyer_id; + std::string seller_id; +}; + +/** + * @brief Sent by exchange to clients to indicate an orderbook update + */ +struct ObUpdate { + std::string ticker; + SIDE side; + double price; + double quantity; +}; + +/** + * @brief Sent by exchange to clients to indicate an update with their specific account + * This is only sent to the two clients that participated in the trade + */ +struct AccountUpdate { + std::string ticker; + SIDE side; + double price; + double quantity; + double capital_remaining; +}; + +} // namespace messages +} // namespace nutc + +/// \cond +template <> +struct glz::meta { + using T = nutc::messages::ObUpdate; + static constexpr auto value = object( + "security", &T::ticker, "side", &T::side, "price", &T::price, "quantity", + &T::quantity + ); +}; + +/// \cond +template <> +struct glz::meta { + using T = nutc::messages::AccountUpdate; + static constexpr auto value = object( + "capital_remaining", &T::capital_remaining, "ticker", &T::ticker, "side", + &T::side, "price", &T::price, "quantity", &T::quantity + ); +}; + +/// \cond +template <> +struct glz::meta { + using T = nutc::messages::Match; + static constexpr auto value = object( + "ticker", &T::ticker, "buyer_id", &T::buyer_id, "seller_id", &T::seller_id, + "side", &T::side, "price", &T::price, "quantity", &T::quantity + ); +}; + +/// \cond +template <> +struct glz::meta { + using T = nutc::messages::StartTime; + static constexpr auto value = object("start_time_ns", &T::start_time_ns); +}; diff --git a/exchange/src/shared/messages_wrapper_to_exchange.hpp b/exchange/src/shared/messages_wrapper_to_exchange.hpp new file mode 100644 index 00000000..65f0f875 --- /dev/null +++ b/exchange/src/shared/messages_wrapper_to_exchange.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "util.hpp" + +#include +#include + +namespace nutc { +namespace messages { + +/** + * @brief Sent by clients to the exchange to indicate they're initialized and may or may + * not be participating in the competition + */ +struct InitMessage { + std::string client_id; + bool ready; +}; + +struct MarketOrder { + std::string client_id; + SIDE side; + std::string ticker; + double quantity; + double price; +}; + +} // namespace messages +} // namespace nutc + +/// \cond +template <> +struct glz::meta { + using T = nutc::messages::MarketOrder; + static constexpr auto value = object( + "client_id", &T::client_id, "side", &T::side, "ticker", &T::ticker, "quantity", + &T::quantity, "price", &T::price + ); +}; + +/// \cond +template <> +struct glz::meta { + using T = nutc::messages::InitMessage; + static constexpr auto value = + object("client_id", &T::client_id, "ready", &T::ready); +}; diff --git a/exchange/src/shared/util.hpp b/exchange/src/shared/util.hpp new file mode 100644 index 00000000..522924c2 --- /dev/null +++ b/exchange/src/shared/util.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace nutc { +namespace messages { +enum class SIDE { BUY, SELL }; +} + +namespace util { +constexpr bool +is_close_to_zero(double value, double epsilon = 1e-6f) +{ + return std::fabs(value) < epsilon; +} + +} // namespace util +} // namespace nutc diff --git a/exchange/src/utils/messages.hpp b/exchange/src/utils/messages.hpp deleted file mode 100644 index 2ddc9b66..00000000 --- a/exchange/src/utils/messages.hpp +++ /dev/null @@ -1,246 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace nutc { - -/** - * @brief Contains all types used by glaze and the exchange for orders, matching, - * communication, etc - */ -namespace messages { - -enum class SIDE { BUY, SELL }; - -/** - * @brief Sent by clients to the exchange to indicate they're initialized and may or may - * not be participating in the competition - */ -struct InitMessage { - std::string client_id; - bool ready; -}; - -struct StartTime { - long long start_time_ns; -}; - -/** - * @brief Sent by exchange to a client to indicate a match has occurred - */ -struct Match { - std::string ticker; - SIDE side; - float price; - float quantity; - std::string buyer_id; - std::string seller_id; -}; - -inline constexpr bool -is_close_to_zero(float value, float epsilon = 1e-6f) -{ - return std::fabs(value) < epsilon; -} - -/** - * @brief Sent by clients to the exchange to place an order - * TODO: client_id=="SIMULATED" indicates simulated order with no actual - * owner, but this is improper. Instead, it should be an optional - */ -struct MarketOrder { - std::string client_id; - std::string ticker; - SIDE side; - float price; - float quantity; - - // Used to sort orders by time created - long long order_index; - - MarketOrder() { order_index = get_and_increment_global_index(); } - - static long long - get_and_increment_global_index() - { - static long long global_index = 0; - return global_index++; - } - - MarketOrder( - const std::string& client_id, SIDE side, const std::string& ticker, - float quantity, float price - ) : - client_id(client_id), - ticker(ticker), side(side), price(price), quantity(quantity) - { - order_index = get_and_increment_global_index(); - } - - bool - operator==(const MarketOrder& other) const - { - return client_id == other.client_id && ticker == other.ticker - && side == other.side && is_close_to_zero(price - other.price) - && is_close_to_zero(quantity - other.quantity); - } - - // toString - std::string - to_string() const - { - std::string side_str = side == SIDE::BUY ? "BUY" : "SELL"; - return fmt::format( - "MarketOrder(client_id={}, side={}, ticker={}, quantity={}, " - "price={})", - client_id, side_str, ticker, quantity, price - ); - } - - bool - operator<(const MarketOrder& other) const - { - // assuming both sides are same - // otherwise, this shouldn't even be called - if (is_close_to_zero(this->price - other.price)) { - return this->order_index > other.order_index; - } - else if (this->side == SIDE::BUY) { - return this->price < other.price; - } - else if (this->side == SIDE::SELL) { - return this->price > other.price; - } - else { - return false; - } - } - - bool - can_match(const MarketOrder& other) const - { - if (this->side == other.side) [[unlikely]] { - return false; - } - if (this->ticker != other.ticker) [[unlikely]] { - return false; - } - if (this->side == SIDE::BUY && this->price < other.price) { - return false; - } - if (this->side == SIDE::SELL && this->price > other.price) { - return false; - } - return true; - } - - // To ensure we don't increment the client_id - MarketOrder(const MarketOrder& other) : order_index(other.order_index) - { - this->client_id = other.client_id; - this->side = other.side; - this->ticker = other.ticker; - this->quantity = other.quantity; - this->price = other.price; - } - - MarketOrder& - operator=(const MarketOrder& other) - { - if (this == &other) { - return *this; - } - - this->order_index = other.order_index; - this->client_id = other.client_id; - this->side = other.side; - this->ticker = other.ticker; - this->quantity = other.quantity; - this->price = other.price; - - return *this; - } -}; - -/** - * @brief Sent by exchange to clients to indicate an orderbook update - */ -struct ObUpdate { - std::string ticker; - SIDE side; - float price; - float quantity; -}; - -/** - * @brief Sent by exchange to clients to indicate an update with their specific account - * This is only sent to the two clients that participated in the trade - */ -struct AccountUpdate { - std::string ticker; - SIDE side; - float price; - float quantity; - float capital_remaining; -}; - -} // namespace messages -} // namespace nutc - -/// \cond -template <> -struct glz::meta { - using T = nutc::messages::ObUpdate; - static constexpr auto value = object( - "security", &T::ticker, "side", &T::side, "price", &T::price, "quantity", - &T::quantity - ); -}; - -/// \cond -template <> -struct glz::meta { - using T = nutc::messages::AccountUpdate; - static constexpr auto value = object( - "capital_remaining", &T::capital_remaining, "ticker", &T::ticker, "side", - &T::side, "price", &T::price, "quantity", &T::quantity - ); -}; - -/// \cond -template <> -struct glz::meta { - using T = nutc::messages::Match; - static constexpr auto value = object( - "ticker", &T::ticker, "buyer_id", &T::buyer_id, "seller_id", &T::seller_id, - "side", &T::side, "price", &T::price, "quantity", &T::quantity - ); -}; - -/// \cond -template <> -struct glz::meta { - using T = nutc::messages::StartTime; - static constexpr auto value = object("start_time_ns", &T::start_time_ns); -}; - -/// \cond -template <> -struct glz::meta { - using T = nutc::messages::MarketOrder; - static constexpr auto value = object( - "client_id", &T::client_id, "side", &T::side, "ticker", &T::ticker, "quantity", - &T::quantity, "price", &T::price - ); -}; - -/// \cond -template <> -struct glz::meta { - using T = nutc::messages::InitMessage; - static constexpr auto value = - object("client_id", &T::client_id, "ready", &T::ready); -}; diff --git a/wrapper/src/common.hpp b/exchange/src/wrapper/common.hpp similarity index 96% rename from wrapper/src/common.hpp rename to exchange/src/wrapper/common.hpp index ca32da82..ec98216e 100644 --- a/wrapper/src/common.hpp +++ b/exchange/src/wrapper/common.hpp @@ -2,8 +2,8 @@ // Common headers -#include "config.h" -#include "logging.hpp" +#include "wrapper/config.h" +#include "wrapper/logging.hpp" #include #include diff --git a/wrapper/src/config.h.in b/exchange/src/wrapper/config.h similarity index 100% rename from wrapper/src/config.h.in rename to exchange/src/wrapper/config.h diff --git a/wrapper/src/dev_mode/dev_mode.cpp b/exchange/src/wrapper/dev_mode/dev_mode.cpp similarity index 83% rename from wrapper/src/dev_mode/dev_mode.cpp rename to exchange/src/wrapper/dev_mode/dev_mode.cpp index 031d31de..2a452132 100644 --- a/wrapper/src/dev_mode/dev_mode.cpp +++ b/exchange/src/wrapper/dev_mode/dev_mode.cpp @@ -1,5 +1,11 @@ #include "dev_mode.hpp" +#include + +#include +#include +#include + namespace nutc { namespace dev_mode { bool @@ -22,9 +28,7 @@ std::string get_algo_from_file(const std::string& algo_id) { std::string filename = fmt::format("./{}.py", algo_id); - if (!file_exists(filename)) { - throw std::invalid_argument("File not found"); - } + assert(file_exists(filename)); return read_file_content(filename); } } // namespace dev_mode diff --git a/wrapper/src/dev_mode/dev_mode.hpp b/exchange/src/wrapper/dev_mode/dev_mode.hpp similarity index 76% rename from wrapper/src/dev_mode/dev_mode.hpp rename to exchange/src/wrapper/dev_mode/dev_mode.hpp index 62ea2234..8d204fb1 100644 --- a/wrapper/src/dev_mode/dev_mode.hpp +++ b/exchange/src/wrapper/dev_mode/dev_mode.hpp @@ -3,12 +3,6 @@ #include #include -#include -#include -#include -#include -#include - namespace nutc { namespace dev_mode { bool file_exists(const std::string& filename) noexcept; diff --git a/wrapper/src/firebase/firebase.cpp b/exchange/src/wrapper/firebase/firebase.cpp similarity index 98% rename from wrapper/src/firebase/firebase.cpp rename to exchange/src/wrapper/firebase/firebase.cpp index 6b1edde0..01885f42 100644 --- a/wrapper/src/firebase/firebase.cpp +++ b/exchange/src/wrapper/firebase/firebase.cpp @@ -10,8 +10,7 @@ print_algo_info(const glz::json_t& algo, const std::string& algo_id) log_i(wrapper_firebase, "Description: {}", algo["description"].get()); log_i(wrapper_firebase, "Upload date: {}", algo["uploadDate"].get()); log_d( - wrapper_firebase, - "Downloading at url {}", + wrapper_firebase, "Downloading at url {}", algo["downloadURL"].get() ); log_i(wrapper_firebase, "Algo id: {}", algo_id); diff --git a/wrapper/src/firebase/firebase.hpp b/exchange/src/wrapper/firebase/firebase.hpp similarity index 90% rename from wrapper/src/firebase/firebase.hpp rename to exchange/src/wrapper/firebase/firebase.hpp index 66aee651..8bce8ab8 100644 --- a/wrapper/src/firebase/firebase.hpp +++ b/exchange/src/wrapper/firebase/firebase.hpp @@ -1,7 +1,7 @@ #pragma once -#include "config.h" -#include "logging.hpp" +#include "wrapper/config.h" +#include "wrapper/logging.hpp" #include #include diff --git a/wrapper/src/logging.cpp b/exchange/src/wrapper/logging.cpp similarity index 75% rename from wrapper/src/logging.cpp rename to exchange/src/wrapper/logging.cpp index a107695e..21c89809 100644 --- a/wrapper/src/logging.cpp +++ b/exchange/src/wrapper/logging.cpp @@ -1,9 +1,8 @@ -#include "logging.hpp" +#include "wrapper/logging.hpp" -#include "common.hpp" -#include "config.h" +#include "wrapper/common.hpp" +#include "wrapper/config.h" -#include #include #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) \ @@ -30,7 +29,7 @@ using namespace quill; // NOLINT(*-using-namespace) using cc = quill::ConsoleColours; void -init(quill::LogLevel log_level, const std::string& uid) +init(quill::LogLevel log_level) { detail::application_log_level = log_level; @@ -39,10 +38,6 @@ init(quill::LogLevel log_level, const std::string& uid) quill::init_signal_handler(); #endif - // Create the logs directory - if (!std::filesystem::is_directory(LOG_DIR)) - std::filesystem::create_directory(LOG_DIR); - // Create our config object quill::Config cfg; @@ -82,26 +77,6 @@ init(quill::LogLevel log_level, const std::string& uid) cfg.default_handlers.emplace_back(stdout_handler); - // - // Initialize rotating file handler - // - quill::RotatingFileHandlerConfig handler_cfg; - - handler_cfg.set_rotation_max_file_size(LOG_FILE_SIZE); - handler_cfg.set_max_backup_files(LOG_BACKUP_COUNT); - handler_cfg.set_open_mode('w'); - - const std::string log_file = LOG_DIR "/" + uid + ".log"; - auto file_handler = quill::rotating_file_handler(log_file, handler_cfg); - - file_handler->set_pattern( - LOGLINE_FORMAT, - "%Y-%m-%dT%T.%Qms" TZ_FORMAT // ISO 8601 - ); - file_handler->set_log_level(log_level); - - cfg.default_handlers.emplace_back(file_handler); - // Send the config quill::configure(cfg); diff --git a/wrapper/src/logging.hpp b/exchange/src/wrapper/logging.hpp similarity index 95% rename from wrapper/src/logging.hpp rename to exchange/src/wrapper/logging.hpp index b0af8bc7..f4de1417 100644 --- a/wrapper/src/logging.hpp +++ b/exchange/src/wrapper/logging.hpp @@ -1,6 +1,6 @@ #pragma once -#include "config.h" +#include "wrapper/config.h" #include @@ -50,13 +50,13 @@ set_thread_name(const std::string& name) /** * Set up logging. */ -void init(quill::LogLevel log_level = DEFAULT_LOG_LEVEL, const std::string& uid = ""); +void init(quill::LogLevel log_level = DEFAULT_LOG_LEVEL); /** * Set up logging. */ inline void -init(uint8_t verbosity = 0, const std::string& uid = "") +init(uint8_t verbosity = 0) { auto log_level = static_cast(DEFAULT_LOG_LEVEL); @@ -65,7 +65,7 @@ init(uint8_t verbosity = 0, const std::string& uid = "") else // protect from underflow log_level = 0; - init(static_cast(log_level), uid); + init(static_cast(log_level)); } /****************************************************************************** diff --git a/wrapper/src/main.cpp b/exchange/src/wrapper/main.cpp similarity index 79% rename from wrapper/src/main.cpp rename to exchange/src/wrapper/main.cpp index e66c3e4d..8f46d0fe 100644 --- a/wrapper/src/main.cpp +++ b/exchange/src/wrapper/main.cpp @@ -1,12 +1,8 @@ -#include "common.hpp" -#include "dev_mode/dev_mode.hpp" -#include "firebase/firebase.hpp" -#include "pywrapper/pywrapper.hpp" -#include "rabbitmq/rabbitmq.hpp" - -#ifndef NO_GIT_VERSION_TRACKING -# include "git.h" -#endif +#include "wrapper/common.hpp" +#include "wrapper/dev_mode/dev_mode.hpp" +#include "wrapper/firebase/firebase.hpp" +#include "wrapper/pywrapper/pywrapper.hpp" +#include "wrapper/rabbitmq/rabbitmq.hpp" #include #include @@ -15,7 +11,6 @@ #include #include #include -#include struct wrapper_args { uint8_t verbosity; @@ -92,32 +87,12 @@ process_arguments(int argc, const char** argv) } return { - verbosity, - program.get("--uid"), - program.get("--algo_id"), - program.get("--dev"), + verbosity, program.get("--uid"), + program.get("--algo_id"), program.get("--dev"), program.get("--no-start-delay") }; } -static void -log_build_info() -{ - log_i(main, "NUTC Client: Interface to the NUFT Trading Competition"); - -#ifndef NO_GIT_VERSION_TRACKING - - // Git info - log_i(main, "Built from {} on {}", git_Describe(), git_Branch()); - log_d(main, "Commit: \"{}\" at {}", git_CommitSubject(), git_CommitDate()); - log_d(main, "Author: {} <{}>", git_AuthorName(), git_AuthorEmail()); - - if (git_AnyUncommittedChanges()) - log_w(main, "Built from dirty commit!"); - -#endif -} - int main(int argc, const char** argv) { @@ -127,8 +102,7 @@ main(int argc, const char** argv) pybind11::scoped_interpreter guard{}; // Start logging and print build info - nutc::logging::init(verbosity, uid); - log_build_info(); + nutc::logging::init(verbosity); log_i(main, "Starting NUTC Client for UID {}", uid); // Initialize the RMQ connection to the exchange @@ -136,6 +110,7 @@ main(int argc, const char** argv) std::optional algo; if (development_mode) { + log_i(main, "Running in development mode"); algo = nutc::dev_mode::get_algo_from_file(algo_id); } else { diff --git a/wrapper/src/pywrapper/pywrapper.cpp b/exchange/src/wrapper/pywrapper/pywrapper.cpp similarity index 91% rename from wrapper/src/pywrapper/pywrapper.cpp rename to exchange/src/wrapper/pywrapper/pywrapper.cpp index a425938a..34a3613e 100644 --- a/wrapper/src/pywrapper/pywrapper.cpp +++ b/exchange/src/wrapper/pywrapper/pywrapper.cpp @@ -5,7 +5,7 @@ namespace pywrapper { void create_api_module( - std::function + std::function publish_market_order ) { diff --git a/wrapper/src/pywrapper/pywrapper.hpp b/exchange/src/wrapper/pywrapper/pywrapper.hpp similarity index 90% rename from wrapper/src/pywrapper/pywrapper.hpp rename to exchange/src/wrapper/pywrapper/pywrapper.hpp index 39169235..20af66ca 100644 --- a/wrapper/src/pywrapper/pywrapper.hpp +++ b/exchange/src/wrapper/pywrapper/pywrapper.hpp @@ -1,7 +1,7 @@ #pragma once -#include "logging.hpp" -#include "util/messages.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" +#include "wrapper/logging.hpp" #include #include @@ -49,7 +49,7 @@ get_trade_and_account_update_function() * @param publish_market_order The callback function to place market orders */ void create_api_module( - std::function + std::function publish_market_order ); diff --git a/wrapper/src/pywrapper/rate_limiter.cpp b/exchange/src/wrapper/pywrapper/rate_limiter.cpp similarity index 100% rename from wrapper/src/pywrapper/rate_limiter.cpp rename to exchange/src/wrapper/pywrapper/rate_limiter.cpp diff --git a/wrapper/src/pywrapper/rate_limiter.hpp b/exchange/src/wrapper/pywrapper/rate_limiter.hpp similarity index 100% rename from wrapper/src/pywrapper/rate_limiter.hpp rename to exchange/src/wrapper/pywrapper/rate_limiter.hpp diff --git a/wrapper/src/rabbitmq/rabbitmq.cpp b/exchange/src/wrapper/rabbitmq/rabbitmq.cpp similarity index 79% rename from wrapper/src/rabbitmq/rabbitmq.cpp rename to exchange/src/wrapper/rabbitmq/rabbitmq.cpp index 86fb91b8..81d5a1ca 100644 --- a/wrapper/src/rabbitmq/rabbitmq.cpp +++ b/exchange/src/wrapper/rabbitmq/rabbitmq.cpp @@ -1,6 +1,6 @@ #include "rabbitmq.hpp" -#include "logging.hpp" +#include "wrapper/logging.hpp" #include @@ -9,9 +9,7 @@ namespace rabbitmq { bool RabbitMQ::connectToRabbitMQ( - const std::string& hostname, - int port, - const std::string& username, + const std::string& hostname, int port, const std::string& username, const std::string& password ) { @@ -30,13 +28,7 @@ RabbitMQ::connectToRabbitMQ( } amqp_rpc_reply_t reply = amqp_login( - conn, - "/", - 0, - 131072, - 0, - AMQP_SASL_METHOD_PLAIN, - username.c_str(), + conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, username.c_str(), password.c_str() ); if (reply.reply_type != AMQP_RESPONSE_NORMAL) { @@ -51,18 +43,16 @@ RabbitMQ::connectToRabbitMQ( void RabbitMQ::handleIncomingMessages() { - bool keep_running = true; - while (keep_running) { + while (true) { std::variant data = consumeMessage(); std::visit( [&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { - log_i( - wrapper_rabbitmq, - "Received order book update: {}", + /*log_i( + wrapper_rabbitmq, "Received order book update: {}", glz::write_json(std::get(data)) - ); + );*/ ObUpdate update = std::get(data); std::string side = update.side == messages::SIDE::BUY ? "BUY" : "SELL"; @@ -72,11 +62,10 @@ RabbitMQ::handleIncomingMessages() return; } else if constexpr (std::is_same_v) { - log_i( - wrapper_rabbitmq, - "Received match: {}", + /*log_i( + wrapper_rabbitmq, "Received match: {}", glz::write_json(std::get(data)) - ); + );*/ Match match = std::get(data); std::string side = match.side == messages::SIDE::BUY ? "BUY" : "SELL"; @@ -87,18 +76,15 @@ RabbitMQ::handleIncomingMessages() } else if constexpr (std::is_same_v) { AccountUpdate update = std::get(data); - log_i( + /*log_i( wrapper_rabbitmq, "Received account update with capital remaining: {}", update.capital_remaining - ); + );*/ std::string side = update.side == messages::SIDE::BUY ? "BUY" : "SELL"; nutc::pywrapper::get_trade_and_account_update_function()( - update.ticker, - side, - update.price, - update.quantity, + update.ticker, side, update.price, update.quantity, update.capital_remaining ); return; @@ -107,33 +93,27 @@ RabbitMQ::handleIncomingMessages() return; } }, - data + std::move(data) ); } } bool RabbitMQ::publishMarketOrder( - const std::string& client_id, - const std::string& side, - const std::string& ticker, - float quantity, - float price + const std::string& client_id, const std::string& side, const std::string& ticker, + double quantity, double price ) { if (limiter.should_rate_limit()) { return false; } MarketOrder order{ - client_id, - side == "BUY" ? messages::SIDE::BUY : messages::SIDE::SELL, - ticker, - quantity, - price + client_id, side == "BUY" ? messages::SIDE::BUY : messages::SIDE::SELL, ticker, + quantity, price }; std::string message = glz::write_json(order); - log_i(wrapper_rabbitmq, "Publishing order: {}", message); + // log_i(wrapper_rabbitmq, "Publishing order: {}", message); return publishMessage("market_order", message); } @@ -141,14 +121,8 @@ bool RabbitMQ::publishMessage(const std::string& queueName, const std::string& message) { amqp_basic_publish( - conn, - 1, - amqp_cstring_bytes(""), - amqp_cstring_bytes(queueName.c_str()), - 0, - 0, - NULL, - amqp_cstring_bytes(message.c_str()) + conn, 1, amqp_cstring_bytes(""), amqp_cstring_bytes(queueName.c_str()), 0, 0, + NULL, amqp_cstring_bytes(message.c_str()) ); amqp_rpc_reply_t res = amqp_get_rpc_reply(conn); @@ -164,7 +138,7 @@ std::variant RabbitMQ::consumeMessage() { std::string buf = consumeMessageAsString(); - if (buf == "") { + if (buf.empty()) { log_e(wrapper_rabbitmq, "Failed to consume message."); exit(1); } @@ -221,7 +195,7 @@ RabbitMQ::initializeConnection(const std::string& queueName) return false; } - log_i(wrapper_rabbitmq, "Connection established"); + // log_i(wrapper_rabbitmq, "Connection established"); return true; } @@ -230,13 +204,7 @@ bool RabbitMQ::initializeConsume(const std::string& queueName) { amqp_basic_consume( - conn, - 1, - amqp_cstring_bytes(queueName.c_str()), - amqp_empty_bytes, - 0, - 1, - 0, + conn, 1, amqp_cstring_bytes(queueName.c_str()), amqp_empty_bytes, 0, 1, 0, amqp_empty_table ); @@ -263,17 +231,12 @@ RabbitMQ::RabbitMQ(const std::string& id) } } -std::function +std::function RabbitMQ::getMarketFunc(const std::string& id) { return std::bind( - &RabbitMQ::publishMarketOrder, - this, - id, - std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3, - std::placeholders::_4 + &RabbitMQ::publishMarketOrder, this, id, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4 ); } @@ -281,7 +244,7 @@ bool RabbitMQ::publishInit(const std::string& id, bool ready) { std::string message = glz::write_json(InitMessage{id, ready}); - log_i(wrapper_rabbitmq, "Publishing init message: {}", message); + // log_i(wrapper_rabbitmq, "Publishing init message: {}", message); bool rVal = publishMessage("market_order", message); return rVal; } @@ -299,8 +262,7 @@ RabbitMQ::waitForStartTime(bool skip_start_wait) StartTime start = std::get(message); log_i( - wrapper_rabbitmq, - "Received start time: {}, sleeping until then.", + wrapper_rabbitmq, "Received start time: {}, sleeping until then.", start.start_time_ns ); diff --git a/wrapper/src/rabbitmq/rabbitmq.hpp b/exchange/src/wrapper/rabbitmq/rabbitmq.hpp similarity index 88% rename from wrapper/src/rabbitmq/rabbitmq.hpp rename to exchange/src/wrapper/rabbitmq/rabbitmq.hpp index 5c328ee8..e18356cb 100644 --- a/wrapper/src/rabbitmq/rabbitmq.hpp +++ b/exchange/src/wrapper/rabbitmq/rabbitmq.hpp @@ -1,8 +1,9 @@ #pragma once -#include "pywrapper/pywrapper.hpp" -#include "pywrapper/rate_limiter.hpp" -#include "util/messages.hpp" +#include "shared/messages_exchange_to_wrapper.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" +#include "wrapper/pywrapper/pywrapper.hpp" +#include "wrapper/pywrapper/rate_limiter.hpp" #include @@ -84,7 +85,7 @@ class RabbitMQ { * @param uid The unique identifier for the client * @returns A function that takes the order parameters and publishes the order */ - std::function + std::function getMarketFunc(const std::string& uid); void waitForStartTime(bool skip_start_wait); @@ -105,9 +106,7 @@ class RabbitMQ { [[nodiscard]] bool initializeConnection(const std::string& queueName); [[nodiscard]] bool initializeConsume(const std::string& queueName); [[nodiscard]] bool connectToRabbitMQ( - const std::string& hostname, - int port, - const std::string& username, + const std::string& hostname, int port, const std::string& username, const std::string& password ); @@ -116,11 +115,8 @@ class RabbitMQ { publishMessage(const std::string& queueName, const std::string& message); [[nodiscard]] bool initializeQueue(const std::string& queueName); [[nodiscard]] bool publishMarketOrder( - const std::string& client_uid, - const std::string& side, - const std::string& ticker, - float quantity, - float price + const std::string& client_uid, const std::string& side, + const std::string& ticker, double quantity, double price ); std::string consumeMessageAsString(); diff --git a/wrapper/src/util/doxygen_main.hpp b/exchange/src/wrapper/util/doxygen_main.hpp similarity index 100% rename from wrapper/src/util/doxygen_main.hpp rename to exchange/src/wrapper/util/doxygen_main.hpp diff --git a/exchange/test/CMakeLists.txt b/exchange/test/CMakeLists.txt index 63f0f6db..d3baa729 100644 --- a/exchange/test/CMakeLists.txt +++ b/exchange/test/CMakeLists.txt @@ -20,10 +20,14 @@ add_executable(NUTC_tests src/unit/matching/invalid_orders.cpp src/unit/matching/many_orders.cpp + src/unit/expiration/basic_expiration.cpp + src/unit/logging/logging_orders.cpp - src/integration/basic_rmq.cpp + src/unit/misc/tick_manager.cpp + src/integration/basic_algo.cpp + src/integration/time_based.cpp ) target_include_directories( @@ -34,7 +38,7 @@ target_include_directories( target_link_libraries( NUTC_tests PRIVATE - NUTC24_lib + EXCHANGE_lib GTest::gtest_main ) diff --git a/exchange/test/src/integration/basic_algo.cpp b/exchange/test/src/integration/basic_algo.cpp index 1b0b8c82..1dd55307 100644 --- a/exchange/test/src/integration/basic_algo.cpp +++ b/exchange/test/src/integration/basic_algo.cpp @@ -1,9 +1,9 @@ -#include "rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" -#include "rabbitmq/consumer/RabbitMQConsumer.hpp" -#include "rabbitmq/order_handler/RabbitMQOrderHandler.hpp" +#include "exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" +#include "exchange/rabbitmq/consumer/RabbitMQConsumer.hpp" +#include "exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" #include "test_utils/macros.hpp" #include "test_utils/process.hpp" -#include "utils/messages.hpp" #include @@ -26,28 +26,39 @@ class IntegrationBasicAlgo : public ::testing::Test { { nutc::testing_utils::kill_all_processes(users_); rmq::RabbitMQConnectionManager::reset_instance(); + users_.reset(); } - nutc::manager::ClientManager users_; // NOLINT(*) - nutc::engine_manager::Manager engine_manager_; // NOLINT(*) + ClientManager& users_ = nutc::manager::ClientManager::get_instance(); // NOLINT(*) + nutc::engine_manager::EngineManager& engine_manager_ = + nutc::engine_manager::EngineManager::get_instance(); // NOLINT(*) }; TEST_F(IntegrationBasicAlgo, InitialLiquidity) { std::vector names{"test_algos/buy_tsla_at_100"}; - nutc::testing_utils::initialize_testing_clients(users_, names); + if (!nutc::testing_utils::initialize_testing_clients(users_, names)) { + FAIL() << "Failed to initialize testing clients"; + } // want to see if it buys engine_manager_.add_engine("TSLA"); - rmq::RabbitMQOrderHandler::add_liquidity_to_ticker( - users_, engine_manager_, "TSLA", 100, 100 // NOLINT (magic-number-*) + + std::string user_id = users_.add_bot_trader(); + users_.get_trader(user_id)->modify_holdings("TSLA", 1000); // NOLINT + + rmq::RabbitMQOrderHandler::handle_incoming_market_order( + engine_manager_, users_, + nutc::messages::MarketOrder{ + user_id, nutc::messages::SIDE::SELL, "TSLA", 100, 100 + } ); auto mess = rmq::RabbitMQConsumer::consume_message(); - EXPECT_TRUE(std::holds_alternative(mess)); + ASSERT_TRUE(std::holds_alternative(mess)); nutc::messages::MarketOrder actual = std::get(mess); - EXPECT_EQ_MARKET_ORDER( + ASSERT_EQ_MARKET_ORDER( actual, "test_algos/buy_tsla_at_100", "TSLA", nutc::messages::SIDE::BUY, 100, 10 ); } @@ -55,14 +66,22 @@ TEST_F(IntegrationBasicAlgo, InitialLiquidity) TEST_F(IntegrationBasicAlgo, OnTradeUpdate) { std::vector names{"test_algos/buy_tsla_on_trade"}; - nutc::testing_utils::initialize_testing_clients(users_, names); + if (!nutc::testing_utils::initialize_testing_clients(users_, names)) { + FAIL() << "Failed to initialize testing clients"; + } engine_manager_.add_engine("TSLA"); engine_manager_.add_engine("APPL"); - rmq::RabbitMQOrderHandler::add_liquidity_to_ticker( - users_, engine_manager_, "TSLA", 100, 100 // NOLINT (magic-number-*) - ); + std::string user_id = users_.add_bot_trader(); + users_.get_trader(user_id)->modify_holdings("TSLA", 1000); // NOLINT + + rmq::RabbitMQOrderHandler::handle_incoming_market_order( + engine_manager_, users_, + nutc::messages::MarketOrder{ + user_id, nutc::messages::SIDE::SELL, "TSLA", 100, 100 + } + ); // NOLINT // obupdate triggers one user to place a BUY order of 10 TSLA at 100 auto mess1 = rmq::RabbitMQConsumer::consume_message(); @@ -70,13 +89,13 @@ TEST_F(IntegrationBasicAlgo, OnTradeUpdate) nutc::messages::MarketOrder actual_mo = std::get(mess1); - EXPECT_EQ_MARKET_ORDER( + ASSERT_EQ_MARKET_ORDER( actual_mo, "test_algos/buy_tsla_on_trade", "TSLA", nutc::messages::SIDE::BUY, 102, 10 ); rmq::RabbitMQOrderHandler::handle_incoming_market_order( - engine_manager_, users_, actual_mo + engine_manager_, users_, std::move(actual_mo) ); // on_trade_match triggers one user to place a BUY order of 1 TSLA at 100 @@ -84,7 +103,7 @@ TEST_F(IntegrationBasicAlgo, OnTradeUpdate) EXPECT_TRUE(std::holds_alternative(mess2)); nutc::messages::MarketOrder actual2 = std::get(mess2); - EXPECT_EQ_MARKET_ORDER( + ASSERT_EQ_MARKET_ORDER( actual2, "test_algos/buy_tsla_on_trade", "APPL", nutc::messages::SIDE::BUY, 100, 1 ); @@ -93,14 +112,22 @@ TEST_F(IntegrationBasicAlgo, OnTradeUpdate) TEST_F(IntegrationBasicAlgo, OnAccountUpdate) { std::vector names{"test_algos/buy_tsla_on_account"}; - nutc::testing_utils::initialize_testing_clients(users_, names); + if (!nutc::testing_utils::initialize_testing_clients(users_, names)) { + FAIL() << "Failed to initialize testing clients"; + } engine_manager_.add_engine("TSLA"); engine_manager_.add_engine("APPL"); - rmq::RabbitMQOrderHandler::add_liquidity_to_ticker( - users_, engine_manager_, "TSLA", 100, 100 // NOLINT (magic-number-*) - ); + std::string user_id = users_.add_bot_trader(); + users_.get_trader(user_id)->modify_holdings("TSLA", 1000); // NOLINT + + rmq::RabbitMQOrderHandler::handle_incoming_market_order( + engine_manager_, users_, + nutc::messages::MarketOrder{ + user_id, nutc::messages::SIDE::SELL, "TSLA", 100, 100 + } + ); // NOLINT // obupdate triggers one user to place a BUY order of 10 TSLA at 102 auto mess1 = rmq::RabbitMQConsumer::consume_message(); @@ -108,13 +135,13 @@ TEST_F(IntegrationBasicAlgo, OnAccountUpdate) nutc::messages::MarketOrder actual_mo = std::get(mess1); - EXPECT_EQ_MARKET_ORDER( + ASSERT_EQ_MARKET_ORDER( actual_mo, "test_algos/buy_tsla_on_account", "TSLA", nutc::messages::SIDE::BUY, 102, 10 ); rmq::RabbitMQOrderHandler::handle_incoming_market_order( - engine_manager_, users_, actual_mo + engine_manager_, users_, std::move(actual_mo) ); // on_trade_match triggers one user to place a BUY order of 1 TSLA at 100 @@ -122,7 +149,7 @@ TEST_F(IntegrationBasicAlgo, OnAccountUpdate) EXPECT_TRUE(std::holds_alternative(mess2)); nutc::messages::MarketOrder actual2 = std::get(mess2); - EXPECT_EQ_MARKET_ORDER( + ASSERT_EQ_MARKET_ORDER( actual2, "test_algos/buy_tsla_on_account", "APPL", nutc::messages::SIDE::BUY, 100, 1 ); diff --git a/exchange/test/src/integration/basic_rmq.cpp b/exchange/test/src/integration/basic_rmq.cpp deleted file mode 100644 index 12628aa2..00000000 --- a/exchange/test/src/integration/basic_rmq.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// #include "local_algos/dev_mode.hpp" -#include "algos/dev_mode/dev_mode.hpp" -#include "process_spawning/spawning.hpp" -#include "rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" -#include "test_utils/process.hpp" - -#include - -namespace rmq = nutc::rabbitmq; - -class IntegrationBasic : public ::testing::Test { -protected: - void - SetUp() override - { - auto& rmq_conn = rmq::RabbitMQConnectionManager::get_instance(); - - if (!rmq_conn.connected_to_rabbitmq()) { - FAIL() << "Failed to connect to rabbitmq"; - } - } - - void - TearDown() override - { - nutc::testing_utils::kill_all_processes(users_); - rmq::RabbitMQConnectionManager::reset_instance(); - } - - nutc::manager::ClientManager users_; // NOLINT (*) -}; - -TEST_F(IntegrationBasic, InitialLiquidity) -{ - nutc::algo_mgmt::DevModeAlgoManager algo_manager = - nutc::algo_mgmt::DevModeAlgoManager(1); - algo_manager.initialize_files(); - algo_manager.initialize_client_manager(users_); - nutc::client::spawn_all_clients(users_); -} diff --git a/exchange/test/src/integration/time_based.cpp b/exchange/test/src/integration/time_based.cpp new file mode 100644 index 00000000..c08af706 --- /dev/null +++ b/exchange/test/src/integration/time_based.cpp @@ -0,0 +1,69 @@ +#include "exchange/rabbitmq/connection_manager/RabbitMQConnectionManager.hpp" +#include "exchange/rabbitmq/consumer/RabbitMQConsumer.hpp" +#include "exchange/rabbitmq/order_handler/RabbitMQOrderHandler.hpp" +#include "test_utils/macros.hpp" +#include "test_utils/process.hpp" + +#include + +#include + +namespace rmq = nutc::rabbitmq; + +class IntegrationBasicAlgo : public ::testing::Test { +protected: + void + SetUp() override + { + auto& rmq_conn = rmq::RabbitMQConnectionManager::get_instance(); + + if (!rmq_conn.connected_to_rabbitmq()) { + FAIL() << "Failed to connect to rabbitmq"; + } + } + + void + TearDown() override + { + nutc::testing_utils::kill_all_processes(users_); + rmq::RabbitMQConnectionManager::reset_instance(); + } + + nutc::manager::ClientManager& users_ = + nutc::manager::ClientManager::get_instance(); // NOLINT(*) + nutc::engine_manager::EngineManager& engine_manager_ = + nutc::engine_manager::EngineManager::get_instance(); // NOLINT(*) +}; + +TEST_F(IntegrationBasicAlgo, AlgoStartDelay) +{ + std::vector names{"test_algos/buy_tsla_at_100"}; + if (!nutc::testing_utils::initialize_testing_clients( + users_, names, /*has_delay=*/true + )) { + FAIL() << "Failed to initialize testing clients"; + } + + auto start = std::chrono::high_resolution_clock::now(); + + engine_manager_.add_engine("TSLA"); + std::string user_id = users_.add_bot_trader(); + users_.get_trader(user_id)->modify_holdings("TSLA", 1000); // NOLINT + + rmq::RabbitMQOrderHandler::handle_incoming_market_order( + engine_manager_, users_, + nutc::messages::MarketOrder{ + user_id, nutc::messages::SIDE::SELL, "TSLA", 100, 100 + } + ); // NOLINT + + auto mess = rmq::RabbitMQConsumer::consume_message(); + + auto end = std::chrono::high_resolution_clock::now(); + const int64_t duration_ms = + std::chrono::duration_cast(end - start).count(); + const double wait_time_ms = CLIENT_WAIT_SECS * 1000; + + EXPECT_GE(duration_ms, wait_time_ms); + EXPECT_LE(duration_ms, wait_time_ms + 1000); +} diff --git a/exchange/test/src/test_utils/macros.cpp b/exchange/test/src/test_utils/macros.cpp index dcc9e936..0d288947 100644 --- a/exchange/test/src/test_utils/macros.cpp +++ b/exchange/test/src/test_utils/macros.cpp @@ -1,20 +1,22 @@ #include "macros.hpp" +#include "exchange/traders/trader_manager.hpp" + namespace nutc { namespace testing_utils { bool -is_nearly_equal(float f_a, float f_b, float epsilon) +is_nearly_equal(double f_a, double f_b, double epsilon) { - float abs_a = std::fabs(f_a); - float abs_b = std::fabs(f_b); - float diff = std::fabs(f_a - f_b); + double abs_a = std::fabs(f_a); + double abs_b = std::fabs(f_b); + double diff = std::fabs(f_a - f_b); return diff <= ((abs_a < abs_b ? abs_b : abs_a) * epsilon); } bool validate_match( const Match& match, const std::string& ticker, const std::string& buyer_id, - const std::string& seller_id, messages::SIDE side, float price, float quantity + const std::string& seller_id, messages::SIDE side, double price, double quantity ) { return match.ticker == ticker && match.buyer_id == buyer_id @@ -25,8 +27,8 @@ validate_match( bool validate_ob_update( - const ObUpdate& update, const std::string& ticker, messages::SIDE side, float price, - float quantity + const ObUpdate& update, const std::string& ticker, messages::SIDE side, + double price, double quantity ) { return update.ticker == ticker && update.side == side @@ -37,7 +39,7 @@ validate_ob_update( bool validate_market_order( const MarketOrder& update, const std::string& client_id, const std::string& ticker, - messages::SIDE side, float price, float quantity + messages::SIDE side, double price, double quantity ) { return update.client_id == client_id && update.ticker == ticker @@ -45,11 +47,5 @@ validate_market_order( && is_nearly_equal(update.quantity, quantity); } -void -add_client_simple(manager::ClientManager& manager, const std::string& client_id) -{ - manager.add_client(client_id, client_id, manager::ClientLocation::LOCAL); -} - } // namespace testing_utils } // namespace nutc diff --git a/exchange/test/src/test_utils/macros.hpp b/exchange/test/src/test_utils/macros.hpp index eebd824e..5f5f402c 100644 --- a/exchange/test/src/test_utils/macros.hpp +++ b/exchange/test/src/test_utils/macros.hpp @@ -1,6 +1,7 @@ -#include "client_manager/client_manager.hpp" -#include "matching/engine/engine.hpp" -#include "utils/logger/logger.hpp" +#include "exchange/tickers/engine/engine.hpp" +#include "exchange/tickers/manager/ticker_manager.hpp" +#include "exchange/traders/trader_manager.hpp" +#include "exchange/utils/logger/logger.hpp" #include @@ -9,35 +10,34 @@ using MarketOrder = nutc::messages::MarketOrder; using Logger = nutc::events::Logger; using ObUpdate = nutc::messages::ObUpdate; using ClientManager = nutc::manager::ClientManager; +using EngineManager = nutc::engine_manager::EngineManager; using SIDE = nutc::messages::SIDE; namespace nutc { namespace testing_utils { bool is_nearly_equal( - float f_a, float f_b, float epsilon = std::numeric_limits::epsilon() + double f_a, double f_b, double epsilon = std::numeric_limits::epsilon() ); bool validate_match( const Match& match, const std::string& ticker, const std::string& buyer_id, - const std::string& seller_id, messages::SIDE side, float price, float quantity + const std::string& seller_id, messages::SIDE side, double price, double quantity ); bool validate_ob_update( - const ObUpdate& update, const std::string& ticker, messages::SIDE side, float price, - float quantity + const ObUpdate& update, const std::string& ticker, messages::SIDE side, + double price, double quantity ); bool validate_market_order( const MarketOrder& update, const std::string& client_id, const std::string& ticker, - messages::SIDE side, float price, float quantity + messages::SIDE side, double price, double quantity ); -void add_client_simple(manager::ClientManager& manager, const std::string& client_id); - } // namespace testing_utils } // namespace nutc -#define EXPECT_EQ_MATCH(/* NOLINT(cppcoreguidelines-macro-usage) */ \ +#define ASSERT_EQ_MATCH(/* NOLINT(cppcoreguidelines-macro-usage) */ \ match, ticker_, buyer_id_, seller_id_, side_, price_, \ quantity_ \ ) \ @@ -58,7 +58,7 @@ void add_client_simple(manager::ClientManager& manager, const std::string& clien << ", price = " << (match).price << ", quantity = " << (match).quantity; \ } while (0) -#define EXPECT_EQ_OB_UPDATE(/* NOLINT(cppcoreguidelines-macro-usage) */ \ +#define ASSERT_EQ_OB_UPDATE(/* NOLINT(cppcoreguidelines-macro-usage) */ \ update, ticker_, side_, price_, quantity_ \ ) \ do { \ @@ -74,7 +74,7 @@ void add_client_simple(manager::ClientManager& manager, const std::string& clien << ", price = " << (update).price << ", quantity = " << (update).quantity; \ } while (0) -#define EXPECT_EQ_MARKET_ORDER(/* NOLINT (cppcoreguidelines-macro-usage) */ \ +#define ASSERT_EQ_MARKET_ORDER(/* NOLINT (cppcoreguidelines-macro-usage) */ \ update, client_id_, ticker_, side_, price_, quantity_ \ ) \ do { \ @@ -85,7 +85,8 @@ void add_client_simple(manager::ClientManager& manager, const std::string& clien << "Expected market order with client_id = " << (client_id_) \ << ", ticker =" << (ticker_) << ", side = " << static_cast(side_) \ << ", price = " << (price_) << ", quantity = " << (quantity_) \ - << ". Actual update: ticker = " << (update).ticker \ + << ". Actual update: client_id = " << (update).client_id \ + << ", ticker = " << (update).ticker \ << ", side = " << static_cast((update).side) \ << ", price = " << (update).price << ", quantity = " << (update).quantity; \ } while (0) diff --git a/exchange/test/src/test_utils/process.cpp b/exchange/test/src/test_utils/process.cpp index 460812c7..9dc1250c 100644 --- a/exchange/test/src/test_utils/process.cpp +++ b/exchange/test/src/test_utils/process.cpp @@ -1,10 +1,12 @@ #include "process.hpp" -#include "algos/dev_mode/dev_mode.hpp" -#include "process_spawning/spawning.hpp" -#include "rabbitmq/client_manager/RabbitMQClientManager.hpp" +#include "exchange/algos/dev_mode/dev_mode.hpp" +#include "exchange/config.h" +#include "exchange/logging.hpp" +#include "exchange/process_spawning/spawning.hpp" +#include "exchange/rabbitmq/client_manager/RabbitMQClientManager.hpp" -#include +#include namespace nutc { namespace testing_utils { @@ -12,25 +14,43 @@ namespace testing_utils { void kill_all_processes(const manager::ClientManager& users) { - for (const auto& [_, client] : users.get_clients()) { - kill(client.pid, SIGKILL); + for (const auto& [id, trader] : users.get_traders()) { + auto pid = trader->get_pid(); + if (pid != -1) + kill(pid, SIGKILL); } } -void +bool initialize_testing_clients( - nutc::manager::ClientManager& users, const std::vector& algo_filenames + nutc::manager::ClientManager& users, const std::vector& algo_filenames, + bool has_delay ) { - using algo_mgmt::DevModeAlgoManager; - using nutc::client::SpawnMode; - - DevModeAlgoManager algo_manager = - DevModeAlgoManager(algo_filenames.size(), algo_filenames); - algo_manager.initialize_client_manager(users); - size_t num_users = spawn_all_clients(users, SpawnMode::TESTING); - rabbitmq::RabbitMQClientManager::wait_for_clients(users, num_users); - rabbitmq::RabbitMQClientManager::send_start_time(users, CLIENT_WAIT_SECS); + auto init_clients = [&]() { + using algo_mgmt::DevModeAlgoManager; + + DevModeAlgoManager algo_manager = DevModeAlgoManager(algo_filenames); + algo_manager.initialize_client_manager(users); + for (auto& [_, trader] : users.get_traders()) { + if (trader->get_type() == manager::LOCAL) { + trader->set_capital(1000000); + trader->set_start_delay(has_delay); + } + } + size_t num_users = spawning::spawn_all_clients(users); + rabbitmq::RabbitMQClientManager::wait_for_clients(users, num_users); + rabbitmq::RabbitMQClientManager::send_start_time(users, CLIENT_WAIT_SECS); + logging::init(quill::LogLevel::Info); + }; + + // Make sure clients are initialized within 100ms + // This is just for testing utils, so it's okay + + auto future = std::async(std::launch::async, init_clients); + return ( + future.wait_for(std::chrono::milliseconds(100)) != std::future_status::timeout + ); } } // namespace testing_utils } // namespace nutc diff --git a/exchange/test/src/test_utils/process.hpp b/exchange/test/src/test_utils/process.hpp index 1070d714..1078291c 100644 --- a/exchange/test/src/test_utils/process.hpp +++ b/exchange/test/src/test_utils/process.hpp @@ -1,14 +1,15 @@ #pragma once -#include "client_manager/client_manager.hpp" +#include "exchange/traders/trader_manager.hpp" namespace nutc { namespace testing_utils { void kill_all_processes(const manager::ClientManager& users); -void initialize_testing_clients( - nutc::manager::ClientManager& users, const std::vector& algo_filenames +[[nodiscard]] bool initialize_testing_clients( + nutc::manager::ClientManager& users, const std::vector& algo_filenames, + bool has_delay = false ); } // namespace testing_utils diff --git a/exchange/test/src/unit/expiration/basic_expiration.cpp b/exchange/test/src/unit/expiration/basic_expiration.cpp new file mode 100644 index 00000000..0085359e --- /dev/null +++ b/exchange/test/src/unit/expiration/basic_expiration.cpp @@ -0,0 +1,63 @@ +#include "exchange/config.h" +#include "exchange/tickers/engine/engine.hpp" +#include "test_utils/macros.hpp" + +#include + +using nutc::messages::SIDE::BUY; +using nutc::messages::SIDE::SELL; + +class UnitOrderExpiration : public ::testing::Test { +protected: + static constexpr const int DEFAULT_QUANTITY = 1000; + + void + SetUp() override + { + manager_.add_local_trader("ABC"); + manager_.add_local_trader("DEF"); + + manager_.get_trader("ABC")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("DEF")->modify_capital(STARTING_CAPITAL); + + manager_.get_trader("ABC")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("DEF")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + } + + ClientManager& manager_ = nutc::manager::ClientManager::get_instance(); // NOLINT(*) + Engine engine_{}; // NOLINT (*) + + nutc::matching::match_result_t + add_to_engine_(MarketOrder order) + { + return engine_.match_order(std::move(order), manager_); + } +}; + +TEST_F(UnitOrderExpiration, SimpleNoMatch) +{ + MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; + MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); + + ASSERT_EQ(1, engine_.on_tick(1, 1).removed_orders.size()); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 1); +} + +TEST_F(UnitOrderExpiration, IncrementTick) +{ + engine_.on_tick(1, 10); + + MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; + MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; + + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(1, engine_.on_tick(2, 1).removed_orders.size()); +} diff --git a/exchange/test/src/unit/logging/logging_orders.cpp b/exchange/test/src/unit/logging/logging_orders.cpp index 66dbcf8b..56881a73 100644 --- a/exchange/test/src/unit/logging/logging_orders.cpp +++ b/exchange/test/src/unit/logging/logging_orders.cpp @@ -1,7 +1,6 @@ -#include "config.h" -#include "matching/engine/engine.hpp" +#include "exchange/config.h" +#include "exchange/tickers/engine/engine.hpp" #include "test_utils/macros.hpp" -#include "utils/messages.hpp" #include @@ -15,22 +14,29 @@ class UnitLoggingOrders : public ::testing::Test { void SetUp() override { - using nutc::testing_utils::add_client_simple; + manager_.add_local_trader("ABC"); + manager_.add_local_trader("DEF"); - add_client_simple(manager_, "ABC"); - add_client_simple(manager_, "DEF"); + manager_.get_trader("ABC")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("DEF")->modify_capital(STARTING_CAPITAL); - manager_.modify_holdings("ABC", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("DEF", "ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("ABC")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("DEF")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); } - ClientManager manager_; // NOLINT(*) - Engine engine_; // NOLINT(*) + ClientManager& manager_ = nutc::manager::ClientManager::get_instance(); // NOLINT(*) + Engine engine_; // NOLINT(*) + + nutc::matching::match_result_t + add_to_engine_(MarketOrder order) + { + return engine_.match_order(std::move(order), manager_); + } }; TEST_F(UnitLoggingOrders, LogMarketOrders) { - manager_.modify_capital("ABC", -STARTING_CAPITAL); + manager_.get_trader("ABC")->modify_capital(-STARTING_CAPITAL); MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; @@ -46,8 +52,8 @@ TEST_F(UnitLoggingOrders, LogMatches) MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); + auto [matches, ob_updates] = add_to_engine_(order1); + auto [matches2, ob_updates2] = add_to_engine_(order2); auto& logger = Logger::get_logger(); EXPECT_NO_FATAL_FAILURE(logger.log_event(matches2.at(0))); diff --git a/exchange/test/src/unit/matching/basic_matching.cpp b/exchange/test/src/unit/matching/basic_matching.cpp index 5a179abc..971edd0d 100644 --- a/exchange/test/src/unit/matching/basic_matching.cpp +++ b/exchange/test/src/unit/matching/basic_matching.cpp @@ -1,6 +1,5 @@ -#include "matching/engine/engine.hpp" +#include "exchange/tickers/engine/engine.hpp" #include "test_utils/macros.hpp" -#include "utils/messages.hpp" #include @@ -14,33 +13,55 @@ class UnitBasicMatching : public ::testing::Test { void SetUp() override { - using nutc::testing_utils::add_client_simple; + manager_.add_local_trader("ABC"); + manager_.add_local_trader("DEF"); - add_client_simple(manager_, "ABC"); - add_client_simple(manager_, "DEF"); + manager_.get_trader("ABC")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("DEF")->modify_capital(STARTING_CAPITAL); - manager_.modify_holdings("ABC", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("DEF", "ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("ABC")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("DEF")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); } - ClientManager manager_; // NOLINT (*) - Engine engine_; // NOLINT (*) + void + TearDown() override + { + manager_.reset(); + SetUp(); + } + + ClientManager& manager_ = nutc::manager::ClientManager::get_instance(); // NOLINT(*) + Engine engine_{}; // NOLINT (*) + + nutc::matching::match_result_t + add_to_engine_(MarketOrder order) + { + return engine_.match_order(std::move(order), manager_); // NOLINT(*) + } }; +TEST_F(UnitBasicMatching, SimpleAddRemove) +{ + double capital_1 = manager_.get_trader("ABC")->get_capital(); + manager_.get_trader("ABC")->modify_capital(100); + double capital_2 = manager_.get_trader("ABC")->get_capital(); + ASSERT_EQ(capital_1 + 100, capital_2); +} + TEST_F(UnitBasicMatching, SimpleMatch) { MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 1); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 0); - EXPECT_EQ_MATCH(matches2.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 1); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 0); + ASSERT_EQ_MATCH(matches2.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); } TEST_F(UnitBasicMatching, CorrectBuyPricingOrder) @@ -52,32 +73,32 @@ TEST_F(UnitBasicMatching, CorrectBuyPricingOrder) MarketOrder sell1{"DEF", SELL, "ETHUSD", 1, 1}; // Place cheapest buy orders first, then most expensive - auto [matches1, ob_updates1] = engine_.match_order(buy1, manager_); - auto [matches3, ob_updates3] = engine_.match_order(buy3, manager_); - auto [matches2, ob_updates2] = engine_.match_order(buy2, manager_); - auto [matches4, ob_updates4] = engine_.match_order(buy4, manager_); - EXPECT_EQ(ob_updates1.size(), 1); - EXPECT_EQ(matches1.size(), 0); - EXPECT_EQ_OB_UPDATE(ob_updates1.at(0), "ETHUSD", BUY, 1, 1); - EXPECT_EQ(ob_updates3.size(), 1); - EXPECT_EQ(matches3.size(), 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", BUY, 3, 1); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 2, 1); - EXPECT_EQ(ob_updates4.size(), 1); - EXPECT_EQ(matches4.size(), 0); - EXPECT_EQ_OB_UPDATE(ob_updates4.at(0), "ETHUSD", BUY, 4, 1); - - auto [matches5, ob_updates5] = engine_.match_order(sell1, manager_); - EXPECT_EQ(ob_updates5.size(), 1); - EXPECT_EQ(matches5.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates5.at(0), "ETHUSD", BUY, 4, 0); - - auto [matches6, ob_updates6] = engine_.match_order(sell1, manager_); - EXPECT_EQ(ob_updates6.size(), 1); - EXPECT_EQ(matches6.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates6.at(0), "ETHUSD", BUY, 3, 0); + auto [matches1, ob_updates1] = add_to_engine_(buy1); + auto [matches3, ob_updates3] = add_to_engine_(buy3); + auto [matches2, ob_updates2] = add_to_engine_(buy2); + auto [matches4, ob_updates4] = add_to_engine_(buy4); + ASSERT_EQ(ob_updates1.size(), 1); + ASSERT_EQ(matches1.size(), 0); + ASSERT_EQ_OB_UPDATE(ob_updates1.at(0), "ETHUSD", BUY, 1, 1); + ASSERT_EQ(ob_updates3.size(), 1); + ASSERT_EQ(matches3.size(), 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", BUY, 3, 1); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 2, 1); + ASSERT_EQ(ob_updates4.size(), 1); + ASSERT_EQ(matches4.size(), 0); + ASSERT_EQ_OB_UPDATE(ob_updates4.at(0), "ETHUSD", BUY, 4, 1); + + auto [matches5, ob_updates5] = add_to_engine_(sell1); + ASSERT_EQ(ob_updates5.size(), 1); + ASSERT_EQ(matches5.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates5.at(0), "ETHUSD", BUY, 4, 0); + + auto [matches6, ob_updates6] = add_to_engine_(sell1); + ASSERT_EQ(ob_updates6.size(), 1); + ASSERT_EQ(matches6.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates6.at(0), "ETHUSD", BUY, 3, 0); } TEST_F(UnitBasicMatching, NoMatchThenMatchBuy) @@ -85,12 +106,15 @@ TEST_F(UnitBasicMatching, NoMatchThenMatchBuy) MarketOrder order1{"ABC", SELL, "ETHUSD", 1, DEFAULT_QUANTITY}; MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; MarketOrder order3{"DEF", BUY, "ETHUSD", 1, 2}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - auto [matches3, ob_updates3] = engine_.match_order(order3, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(matches3.size(), 1); + auto [matches, ob_updates] = add_to_engine_(order1); + auto [matches2, ob_updates2] = add_to_engine_(order2); + auto [matches3, ob_updates3] = add_to_engine_(order3); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ(ob_updates2.size(), 1); + // ASSERT_EQ(ob_updates3.size(), 1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(matches3.size(), 1); } TEST_F(UnitBasicMatching, NoMatchThenMatchSell) @@ -101,47 +125,47 @@ TEST_F(UnitBasicMatching, NoMatchThenMatchSell) constexpr int HALF_QUANTITY = DEFAULT_QUANTITY / 2; MarketOrder order3{"DEF", SELL, "ETHUSD", 1, HALF_QUANTITY}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - auto [matches3, ob_updates3] = engine_.match_order(order1, manager_); - auto [matches4, ob_updates4] = engine_.match_order(order3, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(matches3.size(), 0); - EXPECT_EQ(matches4.size(), 1); + auto [matches, ob_updates] = add_to_engine_(order1); + auto [matches2, ob_updates2] = add_to_engine_(order2); + auto [matches3, ob_updates3] = add_to_engine_(order1); + auto [matches4, ob_updates4] = add_to_engine_(order3); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(matches3.size(), 0); + ASSERT_EQ(matches4.size(), 1); } TEST_F(UnitBasicMatching, PassivePriceMatch) { MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 2}; MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 2, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 1); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 2, 0); - EXPECT_EQ_MATCH(matches2.at(0), "ETHUSD", "ABC", "DEF", SELL, 2, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 2, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 1); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 2, 0); + ASSERT_EQ_MATCH(matches2.at(0), "ETHUSD", "ABC", "DEF", SELL, 2, 1); } TEST_F(UnitBasicMatching, PartialFill) { MarketOrder order1{"ABC", BUY, "ETHUSD", 2, 1}; MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 2); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 1); - EXPECT_EQ(ob_updates2.size(), 2); - EXPECT_EQ_MATCH(matches2.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(1), "ETHUSD", BUY, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 2); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 1); + ASSERT_EQ(ob_updates2.size(), 2); + ASSERT_EQ_MATCH(matches2.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(1), "ETHUSD", BUY, 1, 1); } TEST_F(UnitBasicMatching, MultipleFill) @@ -149,23 +173,23 @@ TEST_F(UnitBasicMatching, MultipleFill) MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order3{"DEF", SELL, "ETHUSD", 2, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 1); - - auto [matches3, ob_updates3] = engine_.match_order(order3, manager_); - EXPECT_EQ(matches3.size(), 2); - EXPECT_EQ(ob_updates3.size(), 2); - EXPECT_EQ_MATCH(matches3.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); - EXPECT_EQ_MATCH(matches3.at(1), "ETHUSD", "ABC", "DEF", SELL, 1, 1); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", BUY, 1, 0); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 1); + + auto [matches3, ob_updates3] = add_to_engine_(order3); + ASSERT_EQ(matches3.size(), 2); + ASSERT_EQ(ob_updates3.size(), 2); + ASSERT_EQ_MATCH(matches3.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); + ASSERT_EQ_MATCH(matches3.at(1), "ETHUSD", "ABC", "DEF", SELL, 1, 1); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", BUY, 1, 0); } TEST_F(UnitBasicMatching, MultiplePartialFill) @@ -173,72 +197,72 @@ TEST_F(UnitBasicMatching, MultiplePartialFill) MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order3{"DEF", SELL, "ETHUSD", 3, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 1); - - auto [matches3, ob_updates3] = engine_.match_order(order3, manager_); - EXPECT_EQ(matches3.size(), 2); - EXPECT_EQ(ob_updates3.size(), 3); - EXPECT_EQ_MATCH(matches3.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); - EXPECT_EQ_MATCH(matches3.at(1), "ETHUSD", "ABC", "DEF", SELL, 1, 1); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(2), "ETHUSD", SELL, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", BUY, 1, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", BUY, 1, 1); + + auto [matches3, ob_updates3] = add_to_engine_(order3); + ASSERT_EQ(matches3.size(), 2); + ASSERT_EQ(ob_updates3.size(), 3); + ASSERT_EQ_MATCH(matches3.at(0), "ETHUSD", "ABC", "DEF", SELL, 1, 1); + ASSERT_EQ_MATCH(matches3.at(1), "ETHUSD", "ABC", "DEF", SELL, 1, 1); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(2), "ETHUSD", SELL, 1, 1); } TEST_F(UnitBasicMatching, SimpleMatchReversed) { MarketOrder order1{"ABC", SELL, "ETHUSD", 1, 1}; MarketOrder order2{"DEF", BUY, "ETHUSD", 1, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 1); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 0); - EXPECT_EQ_MATCH(matches2.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 1); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 0); + ASSERT_EQ_MATCH(matches2.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); } TEST_F(UnitBasicMatching, PassivePriceMatchReversed) { MarketOrder order1{"ABC", SELL, "ETHUSD", 1, 1}; MarketOrder order2{"DEF", BUY, "ETHUSD", 1, 2}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 1); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ(matches2.at(0).price, 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 0); - EXPECT_EQ_MATCH(matches2.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 1); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ(matches2.at(0).price, 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 0); + ASSERT_EQ_MATCH(matches2.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); } TEST_F(UnitBasicMatching, PartialFillReversed) { MarketOrder order1{"ABC", SELL, "ETHUSD", 2, 1}; MarketOrder order2{"DEF", BUY, "ETHUSD", 1, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 2); - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 1); - EXPECT_EQ(ob_updates2.size(), 2); - EXPECT_EQ_MATCH(matches2.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(1), "ETHUSD", SELL, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 2); + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 1); + ASSERT_EQ(ob_updates2.size(), 2); + ASSERT_EQ_MATCH(matches2.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(1), "ETHUSD", SELL, 1, 1); } TEST_F(UnitBasicMatching, MultipleFillReversed) @@ -246,23 +270,23 @@ TEST_F(UnitBasicMatching, MultipleFillReversed) MarketOrder order1{"ABC", SELL, "ETHUSD", 1, 1}; MarketOrder order2{"ABC", SELL, "ETHUSD", 1, 1}; MarketOrder order3{"DEF", BUY, "ETHUSD", 2, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 1); - - auto [matches3, ob_updates3] = engine_.match_order(order3, manager_); - EXPECT_EQ(matches3.size(), 2); - EXPECT_EQ(ob_updates3.size(), 2); - EXPECT_EQ_MATCH(matches3.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); - EXPECT_EQ_MATCH(matches3.at(1), "ETHUSD", "DEF", "ABC", BUY, 1, 1); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", SELL, 1, 0); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 1); + + auto [matches3, ob_updates3] = add_to_engine_(order3); + ASSERT_EQ(matches3.size(), 2); + ASSERT_EQ(ob_updates3.size(), 2); + ASSERT_EQ_MATCH(matches3.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + ASSERT_EQ_MATCH(matches3.at(1), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", SELL, 1, 0); } TEST_F(UnitBasicMatching, MultiplePartialFillReversed) @@ -270,22 +294,22 @@ TEST_F(UnitBasicMatching, MultiplePartialFillReversed) MarketOrder order1{"ABC", SELL, "ETHUSD", 1, 1}; MarketOrder order2{"ABC", SELL, "ETHUSD", 1, 1}; MarketOrder order3{"DEF", BUY, "ETHUSD", 3, 1}; - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); - - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 1); - - auto [matches3, ob_updates3] = engine_.match_order(order3, manager_); - EXPECT_EQ(matches3.size(), 2); - EXPECT_EQ(ob_updates3.size(), 3); - EXPECT_EQ_MATCH(matches3.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); - EXPECT_EQ_MATCH(matches3.at(1), "ETHUSD", "DEF", "ABC", BUY, 1, 1); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(ob_updates3.at(2), "ETHUSD", BUY, 1, 1); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates.at(0), "ETHUSD", SELL, 1, 1); + + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2.at(0), "ETHUSD", SELL, 1, 1); + + auto [matches3, ob_updates3] = add_to_engine_(order3); + ASSERT_EQ(matches3.size(), 2); + ASSERT_EQ(ob_updates3.size(), 3); + ASSERT_EQ_MATCH(matches3.at(0), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + ASSERT_EQ_MATCH(matches3.at(1), "ETHUSD", "DEF", "ABC", BUY, 1, 1); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(0), "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(1), "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(ob_updates3.at(2), "ETHUSD", BUY, 1, 1); } diff --git a/exchange/test/src/unit/matching/invalid_orders.cpp b/exchange/test/src/unit/matching/invalid_orders.cpp index 447e3a4e..68c93834 100644 --- a/exchange/test/src/unit/matching/invalid_orders.cpp +++ b/exchange/test/src/unit/matching/invalid_orders.cpp @@ -1,7 +1,8 @@ -#include "config.h" -#include "matching/engine/engine.hpp" +#include "exchange/config.h" +#include "exchange/tickers/engine/engine.hpp" +#include "shared/messages_exchange_to_wrapper.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" #include "test_utils/macros.hpp" -#include "utils/messages.hpp" #include @@ -15,123 +16,132 @@ class UnitInvalidOrders : public ::testing::Test { void SetUp() override { - using nutc::testing_utils::add_client_simple; + manager_.add_local_trader("ABC"); + manager_.add_local_trader("DEF"); - add_client_simple(manager_, "ABC"); - add_client_simple(manager_, "DEF"); - manager_.modify_holdings("ABC", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("DEF", "ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("ABC")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("DEF")->modify_capital(STARTING_CAPITAL); + + manager_.get_trader("ABC")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("DEF")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); } - ClientManager manager_; // NOLINT (*) - Engine engine_; // NOLINT (*) + ClientManager& manager_ = nutc::manager::ClientManager::get_instance(); // NOLINT(*) + Engine engine_{}; // NOLINT (*) + + nutc::matching::match_result_t + add_to_engine_(MarketOrder order) + { + return engine_.match_order(std::move(order), manager_); + } }; TEST_F(UnitInvalidOrders, SimpleInvalidFunds) { - manager_.modify_capital("ABC", -STARTING_CAPITAL); + manager_.get_trader("ABC")->modify_capital(-STARTING_CAPITAL); std::optional err = manager_.validate_match(Match{"ETHUSD", SELL, 1, 1, "ABC", "DEF"}); if (err.has_value()) - EXPECT_EQ(err.value(), BUY); + ASSERT_EQ(err.value(), BUY); else FAIL() << "Match should have failed"; } TEST_F(UnitInvalidOrders, RemoveThenAddFunds) { - manager_.modify_capital("ABC", -STARTING_CAPITAL); + manager_.get_trader("ABC")->modify_capital(-STARTING_CAPITAL); MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; // Thrown out - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 0); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 0); // Kept, but not matched - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2[0], "ETHUSD", SELL, 1, 1); + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2[0], "ETHUSD", SELL, 1, 1); - manager_.modify_capital("ABC", STARTING_CAPITAL); + manager_.get_trader("ABC")->modify_capital(STARTING_CAPITAL); // Kept, but not matched - auto [matches3, ob_updates3] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches3.size(), 0); - EXPECT_EQ(ob_updates3.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates3[0], "ETHUSD", SELL, 1, 1); + auto [matches3, ob_updates3] = add_to_engine_(order2); + ASSERT_EQ(matches3.size(), 0); + ASSERT_EQ(ob_updates3.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates3[0], "ETHUSD", SELL, 1, 1); // Kept and matched - auto [matches4, ob_updates4] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches4.size(), 1); - EXPECT_EQ(ob_updates4.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates4[0], "ETHUSD", SELL, 1, 0); - EXPECT_EQ_MATCH(matches4.at(0), "ETHUSD", "ABC", "DEF", BUY, 1, 1); + auto [matches4, ob_updates4] = add_to_engine_(order1); + ASSERT_EQ(matches4.size(), 1); + ASSERT_EQ(ob_updates4.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates4[0], "ETHUSD", SELL, 1, 0); + ASSERT_EQ_MATCH(matches4.at(0), "ETHUSD", "ABC", "DEF", BUY, 1, 1); } TEST_F(UnitInvalidOrders, MatchingInvalidFunds) { - manager_.modify_capital("ABC", -STARTING_CAPITAL); + manager_.get_trader("ABC")->modify_capital(-STARTING_CAPITAL); MarketOrder order1{"ABC", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"DEF", SELL, "ETHUSD", 1, 1}; // Thrown out - auto [matches, ob_updates] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches.size(), 0); - EXPECT_EQ(ob_updates.size(), 0); + auto [matches, ob_updates] = add_to_engine_(order1); + ASSERT_EQ(matches.size(), 0); + ASSERT_EQ(ob_updates.size(), 0); // Kept, but not matched - auto [matches2, ob_updates2] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(ob_updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(ob_updates2[0], "ETHUSD", SELL, 1, 1); + auto [matches2, ob_updates2] = add_to_engine_(order2); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(ob_updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(ob_updates2[0], "ETHUSD", SELL, 1, 1); } TEST_F(UnitInvalidOrders, SimpleManyInvalidOrder) { - using nutc::testing_utils::add_client_simple; + manager_.add_local_trader("A"); + manager_.add_local_trader("B"); + manager_.add_local_trader("C"); + manager_.add_local_trader("D"); - add_client_simple(manager_, "A"); - add_client_simple(manager_, "B"); - add_client_simple(manager_, "C"); - add_client_simple(manager_, "D"); + manager_.get_trader("A")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("C")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("D")->modify_capital(STARTING_CAPITAL); - manager_.modify_capital("B", -STARTING_CAPITAL); - manager_.modify_holdings("A", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("B", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("C", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("D", "ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("A")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("B")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("C")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("D")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); MarketOrder order1{"A", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"B", BUY, "ETHUSD", 1, 1}; MarketOrder order3{"C", BUY, "ETHUSD", 1, 1}; MarketOrder order4{"D", SELL, "ETHUSD", 3, 1}; - auto [matches1, updates1] = engine_.match_order(order1, manager_); - auto [matches2, updates2] = engine_.match_order(order2, manager_); - auto [matches3, updates3] = engine_.match_order(order3, manager_); + auto [matches1, updates1] = add_to_engine_(order1); + auto [matches2, updates2] = add_to_engine_(order2); + auto [matches3, updates3] = add_to_engine_(order3); - EXPECT_EQ(matches1.size(), 0); - EXPECT_EQ(updates1.size(), 1); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(updates2.size(), 0); - EXPECT_EQ(matches3.size(), 0); - EXPECT_EQ(updates3.size(), 1); + ASSERT_EQ(matches1.size(), 0); + ASSERT_EQ(updates1.size(), 1); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(updates2.size(), 0); + ASSERT_EQ(matches3.size(), 0); + ASSERT_EQ(updates3.size(), 1); // Should match two orders and throw out the invalid order (2) - auto [matches4, updates4] = engine_.match_order(order4, manager_); - EXPECT_EQ(matches4.size(), 2); - EXPECT_EQ(updates4.size(), 3); + auto [matches4, updates4] = add_to_engine_(order4); + ASSERT_EQ(matches4.size(), 2); + ASSERT_EQ(updates4.size(), 3); - EXPECT_EQ_MATCH(matches4[0], "ETHUSD", "A", "D", SELL, 1, 1); - EXPECT_EQ_MATCH(matches4[1], "ETHUSD", "C", "D", SELL, 1, 1); + ASSERT_EQ_MATCH(matches4[0], "ETHUSD", "A", "D", SELL, 1, 1); + ASSERT_EQ_MATCH(matches4[1], "ETHUSD", "C", "D", SELL, 1, 1); - EXPECT_EQ_OB_UPDATE(updates4[0], "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(updates4[1], "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(updates4[2], "ETHUSD", SELL, 1, 1); + ASSERT_EQ_OB_UPDATE(updates4[0], "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(updates4[1], "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(updates4[2], "ETHUSD", SELL, 1, 1); } diff --git a/exchange/test/src/unit/matching/many_orders.cpp b/exchange/test/src/unit/matching/many_orders.cpp index e05bbb2c..67371570 100644 --- a/exchange/test/src/unit/matching/many_orders.cpp +++ b/exchange/test/src/unit/matching/many_orders.cpp @@ -1,5 +1,6 @@ +#include "shared/messages_exchange_to_wrapper.hpp" +#include "shared/messages_wrapper_to_exchange.hpp" #include "test_utils/macros.hpp" -#include "utils/messages.hpp" #include @@ -13,21 +14,30 @@ class UnitManyOrders : public ::testing::Test { void SetUp() override { - using nutc::testing_utils::add_client_simple; + manager_.add_local_trader("A"); + manager_.add_local_trader("B"); + manager_.add_local_trader("C"); + manager_.add_local_trader("D"); + + manager_.get_trader("A")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("B")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("C")->modify_capital(STARTING_CAPITAL); + manager_.get_trader("D")->modify_capital(STARTING_CAPITAL); + + manager_.get_trader("A")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("B")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("C")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + manager_.get_trader("D")->modify_holdings("ETHUSD", DEFAULT_QUANTITY); + } - add_client_simple(manager_, "A"); - add_client_simple(manager_, "B"); - add_client_simple(manager_, "C"); - add_client_simple(manager_, "D"); + ClientManager& manager_ = nutc::manager::ClientManager::get_instance(); // NOLINT(*) + Engine engine_{}; // NOLINT (*) - manager_.modify_holdings("A", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("B", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("C", "ETHUSD", DEFAULT_QUANTITY); - manager_.modify_holdings("D", "ETHUSD", DEFAULT_QUANTITY); + nutc::matching::match_result_t + add_to_engine_(MarketOrder order) + { + return engine_.match_order(std::move(order), manager_); } - - ClientManager manager_; // NOLINT (*) - Engine engine_; // NOLINT (*) }; TEST_F(UnitManyOrders, CorrectTimePriority) @@ -37,23 +47,23 @@ TEST_F(UnitManyOrders, CorrectTimePriority) MarketOrder order3{"C", SELL, "ETHUSD", 1, 1}; MarketOrder order4{"D", BUY, "ETHUSD", 1, 1}; - auto [matches1, updates1] = engine_.match_order(order1, manager_); - auto [matches2, updates2] = engine_.match_order(order2, manager_); - engine_.add_order_without_matching(order3); - auto [matches3, updates3] = engine_.match_order(order4, manager_); - EXPECT_EQ(matches1.size(), 0); - EXPECT_EQ(updates1.size(), 1); - EXPECT_EQ_OB_UPDATE(updates1[0], "ETHUSD", BUY, 1, 1); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(updates2.size(), 1); - EXPECT_EQ_OB_UPDATE(updates2[0], "ETHUSD", BUY, 1, 1); - - EXPECT_EQ(matches3.size(), 1); - EXPECT_EQ(updates3.size(), 3); - EXPECT_EQ_OB_UPDATE(updates3[0], "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(updates3[1], "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(updates3[2], "ETHUSD", BUY, 1, 1); - EXPECT_EQ_MATCH(matches3[0], "ETHUSD", "A", "C", SELL, 1, 1); + auto [matches1, updates1] = add_to_engine_(order1); + auto [matches2, updates2] = add_to_engine_(order2); + engine_.add_order(order3); + auto [matches3, updates3] = add_to_engine_(order4); + ASSERT_EQ(matches1.size(), 0); + ASSERT_EQ(updates1.size(), 1); + ASSERT_EQ_OB_UPDATE(updates1[0], "ETHUSD", BUY, 1, 1); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(updates2.size(), 1); + ASSERT_EQ_OB_UPDATE(updates2[0], "ETHUSD", BUY, 1, 1); + + ASSERT_EQ(matches3.size(), 1); + ASSERT_EQ(updates3.size(), 3); + ASSERT_EQ_OB_UPDATE(updates3[0], "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(updates3[1], "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(updates3[2], "ETHUSD", BUY, 1, 1); + ASSERT_EQ_MATCH(matches3[0], "ETHUSD", "A", "C", SELL, 1, 1); } TEST_F(UnitManyOrders, OnlyMatchesOne) @@ -61,18 +71,18 @@ TEST_F(UnitManyOrders, OnlyMatchesOne) MarketOrder order1{"A", BUY, "ETHUSD", 1, 1}; MarketOrder order2{"B", SELL, "ETHUSD", 1, 1}; - auto [matches1, updates1] = engine_.match_order(order1, manager_); - auto [matches2, updates2] = engine_.match_order(order1, manager_); - EXPECT_EQ(matches1.size(), 0); - EXPECT_EQ(updates1.size(), 1); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(updates2.size(), 1); - - auto [matches3, updates3] = engine_.match_order(order2, manager_); - EXPECT_EQ(matches3.size(), 1); - EXPECT_EQ(updates3.size(), 1); - EXPECT_EQ_MATCH(matches3[0], "ETHUSD", "A", "B", SELL, 1, 1); - EXPECT_EQ_OB_UPDATE(updates3[0], "ETHUSD", BUY, 1, 0); + auto [matches1, updates1] = add_to_engine_(order1); + auto [matches2, updates2] = add_to_engine_(order1); + ASSERT_EQ(matches1.size(), 0); + ASSERT_EQ(updates1.size(), 1); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(updates2.size(), 1); + + auto [matches3, updates3] = add_to_engine_(order2); + ASSERT_EQ(matches3.size(), 1); + ASSERT_EQ(updates3.size(), 1); + ASSERT_EQ_MATCH(matches3[0], "ETHUSD", "A", "B", SELL, 1, 1); + ASSERT_EQ_OB_UPDATE(updates3[0], "ETHUSD", BUY, 1, 0); } TEST_F(UnitManyOrders, SimpleManyOrder) @@ -82,28 +92,28 @@ TEST_F(UnitManyOrders, SimpleManyOrder) MarketOrder order3{"C", BUY, "ETHUSD", 1, 1}; MarketOrder order4{"D", SELL, "ETHUSD", 3, 1}; - auto [matches1, updates1] = engine_.match_order(order1, manager_); - auto [matches2, updates2] = engine_.match_order(order2, manager_); - auto [matches3, updates3] = engine_.match_order(order3, manager_); + auto [matches1, updates1] = add_to_engine_(order1); + auto [matches2, updates2] = add_to_engine_(order2); + auto [matches3, updates3] = add_to_engine_(order3); - EXPECT_EQ(matches1.size(), 0); - EXPECT_EQ(updates1.size(), 1); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(updates2.size(), 1); - EXPECT_EQ(matches3.size(), 0); - EXPECT_EQ(updates3.size(), 1); + ASSERT_EQ(matches1.size(), 0); + ASSERT_EQ(updates1.size(), 1); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(updates2.size(), 1); + ASSERT_EQ(matches3.size(), 0); + ASSERT_EQ(updates3.size(), 1); - auto [matches4, updates4] = engine_.match_order(order4, manager_); - EXPECT_EQ(matches4.size(), 3); - EXPECT_EQ(updates4.size(), 3); + auto [matches4, updates4] = add_to_engine_(order4); + ASSERT_EQ(matches4.size(), 3); + ASSERT_EQ(updates4.size(), 3); - EXPECT_EQ_MATCH(matches4[0], "ETHUSD", "A", "D", SELL, 1, 1); - EXPECT_EQ_MATCH(matches4[1], "ETHUSD", "B", "D", SELL, 1, 1); - EXPECT_EQ_MATCH(matches4[2], "ETHUSD", "C", "D", SELL, 1, 1); + ASSERT_EQ_MATCH(matches4[0], "ETHUSD", "A", "D", SELL, 1, 1); + ASSERT_EQ_MATCH(matches4[1], "ETHUSD", "B", "D", SELL, 1, 1); + ASSERT_EQ_MATCH(matches4[2], "ETHUSD", "C", "D", SELL, 1, 1); - EXPECT_EQ_OB_UPDATE(updates4[0], "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(updates4[1], "ETHUSD", BUY, 1, 0); - EXPECT_EQ_OB_UPDATE(updates4[2], "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(updates4[0], "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(updates4[1], "ETHUSD", BUY, 1, 0); + ASSERT_EQ_OB_UPDATE(updates4[2], "ETHUSD", BUY, 1, 0); } TEST_F(UnitManyOrders, PassiveAndAggressivePartial) @@ -113,27 +123,27 @@ TEST_F(UnitManyOrders, PassiveAndAggressivePartial) MarketOrder order3{"C", BUY, "ETHUSD", 2, 3}; MarketOrder order4{"D", BUY, "ETHUSD", 10, 4}; // NOLINT (*) - auto [matches1, updates1] = engine_.match_order(order1, manager_); - auto [matches2, updates2] = engine_.match_order(order2, manager_); - auto [matches3, updates3] = engine_.match_order(order3, manager_); - auto [matches4, updates4] = engine_.match_order(order4, manager_); - - EXPECT_EQ(matches1.size(), 0); - EXPECT_EQ(updates1.size(), 1); - EXPECT_EQ(matches2.size(), 0); - EXPECT_EQ(updates2.size(), 1); - EXPECT_EQ(matches3.size(), 2); - EXPECT_EQ(updates3.size(), 3); - EXPECT_EQ(matches4.size(), 1); - EXPECT_EQ(updates4.size(), 2); - - EXPECT_EQ_MATCH(matches3[0], "ETHUSD", "C", "A", BUY, 1, 1); - EXPECT_EQ_MATCH(matches3[1], "ETHUSD", "C", "B", BUY, 1, 1); - EXPECT_EQ_OB_UPDATE(updates3[0], "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(updates3[1], "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(updates3[2], "ETHUSD", SELL, 1, 9); - - EXPECT_EQ_MATCH(matches4[0], "ETHUSD", "D", "B", BUY, 1, 9); - EXPECT_EQ_OB_UPDATE(updates4[0], "ETHUSD", SELL, 1, 0); - EXPECT_EQ_OB_UPDATE(updates4[1], "ETHUSD", BUY, 4, 1); + auto [matches1, updates1] = add_to_engine_(order1); + auto [matches2, updates2] = add_to_engine_(order2); + auto [matches3, updates3] = add_to_engine_(order3); + auto [matches4, updates4] = add_to_engine_(order4); + + ASSERT_EQ(matches1.size(), 0); + ASSERT_EQ(updates1.size(), 1); + ASSERT_EQ(matches2.size(), 0); + ASSERT_EQ(updates2.size(), 1); + ASSERT_EQ(matches3.size(), 2); + ASSERT_EQ(updates3.size(), 3); + ASSERT_EQ(matches4.size(), 1); + ASSERT_EQ(updates4.size(), 2); + + ASSERT_EQ_MATCH(matches3[0], "ETHUSD", "C", "A", BUY, 1, 1); + ASSERT_EQ_MATCH(matches3[1], "ETHUSD", "C", "B", BUY, 1, 1); + ASSERT_EQ_OB_UPDATE(updates3[0], "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(updates3[1], "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(updates3[2], "ETHUSD", SELL, 1, 9); + + ASSERT_EQ_MATCH(matches4[0], "ETHUSD", "D", "B", BUY, 1, 9); + ASSERT_EQ_OB_UPDATE(updates4[0], "ETHUSD", SELL, 1, 0); + ASSERT_EQ_OB_UPDATE(updates4[1], "ETHUSD", BUY, 4, 1); } diff --git a/exchange/test/src/unit/misc/tick_manager.cpp b/exchange/test/src/unit/misc/tick_manager.cpp new file mode 100644 index 00000000..d1f1b810 --- /dev/null +++ b/exchange/test/src/unit/misc/tick_manager.cpp @@ -0,0 +1,72 @@ +#include "exchange/tick_manager/tick_manager.hpp" + +#include "exchange/tick_manager/tick_observer.hpp" + +#include + +// NOLINTBEGIN + +class UnitTickManagerTest : public ::testing::Test { + using TickManager = nutc::ticks::TickManager; + +protected: + using PRIORITY = nutc::ticks::PRIORITY; + static constexpr uint16_t START_TICK_RATE = 100; + + void + SetUp() override + {} + + TickManager& manager_ = TickManager::get_instance(START_TICK_RATE); +}; + +TEST_F(UnitTickManagerTest, SingletonInstance) +{ + manager_.start(); + auto& instance1 = nutc::ticks::TickManager::get_instance(60); + auto& instance2 = nutc::ticks::TickManager::get_instance(30); + ASSERT_EQ(std::addressof(instance1), std::addressof(instance2)); + manager_.stop(); +} + +class TestObserver : public nutc::ticks::TickObserver { +public: + void + on_tick(uint64_t tick) override + { + tick_count_++; + current_tick_ = tick; + } + + uint32_t tick_count_ = 0; + uint64_t current_tick_ = 0; + + uint32_t + get_tick_count() + { + return tick_count_; + } + + uint64_t + get_current_tick() + { + return current_tick_; + } +}; + +TEST_F(UnitTickManagerTest, AttachDetachObserver) +{ + TestObserver observer; + manager_.attach(&observer, PRIORITY::first); + manager_.start(); + // wait for 100 ms, should be around 10 ticks + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + manager_.stop(); + manager_.detach(&observer, PRIORITY::first); + ASSERT_GE(observer.get_tick_count(), 8); + ASSERT_LE(observer.get_tick_count(), 12); + ASSERT_GE(observer.get_current_tick(), 8); + ASSERT_LE(observer.get_current_tick(), 12); +} + +// NOLINTEND diff --git a/sandbox-server/analyzer/analyzer.go b/sandbox-server/analyzer/analyzer.go new file mode 100644 index 00000000..130af80a --- /dev/null +++ b/sandbox-server/analyzer/analyzer.go @@ -0,0 +1,69 @@ +package analyzer + +import ( + "bufio" + "encoding/json" + "io" + "reflect" +) + +func extractKeysFromStruct(v interface{}) []string { + val := reflect.ValueOf(v) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + keys := []string{} + for i := 0; i < val.Type().NumField(); i++ { + keys = append(keys, val.Type().Field(i).Tag.Get("json")) + } + return keys +} + +func isType(data map[string]interface{}, v interface{}) bool { + keys := extractKeysFromStruct(v) + for _, key := range keys { + if _, ok := data[key]; !ok { + return false + } + } + return true +} + +func Analyze(file io.Reader, user_id string) (string, error) { + scanner := bufio.NewScanner(file) + var log_entries []LogEntry + + for scanner.Scan() { + var entry LogEntry + if err := json.Unmarshal([]byte(scanner.Text()), &entry); err != nil { + return "", err + } + log_entries = append(log_entries, entry) + } + + if err := scanner.Err(); err != nil { + return "", err + } + + var matches []LogEntry + var updates []LogEntry + + for _, entry := range log_entries { + var data = entry.Data.(map[string]interface{}) + + if isType(data, Match{}) { + if data["buyer_id"] == user_id || data["seller_id"] == user_id { + matches = append(matches, entry) + } + } else if isType(data, ObUpdate{}) { + updates = append(updates, entry) + } + } + + wrapper := LogResult{Matches: matches, ObUpdates: updates} + outputJSON, err := json.MarshalIndent(wrapper, "", " ") + if err != nil { + return "", err + } + return string(outputJSON), nil +} diff --git a/sandbox-server/analyzer/messages.go b/sandbox-server/analyzer/messages.go new file mode 100644 index 00000000..f929c937 --- /dev/null +++ b/sandbox-server/analyzer/messages.go @@ -0,0 +1,47 @@ +package analyzer + +import ( + "strings" + "time" +) + +type Timestamp struct { + time.Time +} + +func (ct *Timestamp) UnmarshalJSON(b []byte) error { + const layout = "2006-01-02 15:04:05.000" + s := strings.Trim(string(b), `"`) + t, err := time.Parse(layout, s) + if err != nil { + return err + } + ct.Time = t + return nil +} + +type Match struct { + Ticker string `json:"ticker"` + BuyerId string `json:"buyer_id"` + SellerId string `json:"seller_id"` + Side int `json:"side"` + Price float64 `json:"price"` + Quantity float64 `json:"quantity"` +} + +type ObUpdate struct { + Security string `json:"security"` + Side int `json:"side"` + Price float64 `json:"price"` + Quantity float64 `json:"quantity"` +} + +type LogEntry struct { + Timestamp Timestamp `json:"timestamp"` + Data interface{} `json:"data"` +} + +type LogResult struct { + Matches []LogEntry `json:"matches"` + ObUpdates []LogEntry `json:"ob_updates"` +} diff --git a/sandbox-server/go.mod b/sandbox-server/go.mod index 837d1291..a839886a 100644 --- a/sandbox-server/go.mod +++ b/sandbox-server/go.mod @@ -4,6 +4,11 @@ go 1.21.1 require github.com/docker/docker v24.0.7+incompatible +require ( + github.com/google/go-cmp v0.6.0 // indirect + golang.org/x/sync v0.6.0 // indirect +) + require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/distribution/reference v0.5.0 // indirect @@ -19,9 +24,9 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.8.4 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.6.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/time v0.4.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.6.0 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/sandbox-server/go.sum b/sandbox-server/go.sum index 097bdb5b..774cb4b5 100644 --- a/sandbox-server/go.sum +++ b/sandbox-server/go.sum @@ -17,11 +17,10 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= @@ -54,22 +53,22 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= -golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= diff --git a/sandbox-server/main.go b/sandbox-server/main.go index 615a5e2b..b3c35190 100644 --- a/sandbox-server/main.go +++ b/sandbox-server/main.go @@ -1,21 +1,31 @@ package main import ( + "bytes" "context" + "encoding/json" "fmt" + "io" "log" + "mime/multipart" "net/http" + "net/url" + "sandbox-server/analyzer" "strings" "time" "unicode" + "archive/tar" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" gonanoid "github.com/matoous/go-nanoid/v2" ) -const dockerTimeout = time.Minute * 10 +const dockerTimeout = time.Minute * 1 +const firebaseStorageUrl = "https://firebasestorage.googleapis.com/v0/b/nutc-web.appspot.com/o" +const firebaseApiKey = "AIzaSyCo2l3x2DMhg5CaNy1Pyvknk_GK8v34iUc" const port = "8081" @@ -32,7 +42,6 @@ func main() { if err := server.ListenAndServe(); err != nil { log.Fatal("Failed to start server\n") } - } func algoTestingHandler(w http.ResponseWriter, r *http.Request) { @@ -94,8 +103,42 @@ func algoTestingHandler(w http.ResponseWriter, r *http.Request) { } go func() { + defer cli.ContainerStop(context.Background(), resp.ID, container.StopOptions{}) + time.Sleep(dockerTimeout) - cli.ContainerStop(context.Background(), resp.ID, container.StopOptions{}) + + reader, _, err := cli.CopyFromContainer(context.Background(), resp.ID, "logs/structured.log") + if err != nil { + fmt.Printf("%s", err.Error()) + return + } + defer reader.Close() + + tarReader := tar.NewReader(reader) + + _, err = tarReader.Next() + if err != nil { + fmt.Printf("%s", err.Error()) + return + } + + out_file, err := analyzer.Analyze(tarReader, user_id) + + if err != nil { + fmt.Printf("%s", err.Error()) + } + + download_token, err := uploadLogFile(user_id, algo_id, firebaseApiKey, out_file) + if err != nil { + fmt.Printf("%s", err.Error()) + } + + file_url := fmt.Sprintf("%s/%s?alt=media&token=%s", firebaseStorageUrl, url.PathEscape("logs/"+user_id+"/"+algo_id+".log"), download_token) + + err = addLogFileUrlToUser(user_id, algo_id, file_url) + if err != nil { + fmt.Printf("%s", err.Error()) + } }() fmt.Fprintf(w, "Container %s started successfully with user_id: %s and algo_id: %s\n", container_name, user_id, algo_id) @@ -109,3 +152,97 @@ func isValidID(id string) bool { } return true } + +func uploadLogFile(user_id, algo_id, apiKey, file_str string) (string, error) { + reader := strings.NewReader(file_str) + var buffer bytes.Buffer + + writer := multipart.NewWriter(&buffer) + + fileName := fmt.Sprintf("logs/%s/%s.log", user_id, algo_id) + + part, err := writer.CreateFormFile("file", fileName) + if err != nil { + return "", fmt.Errorf("writer.CreateFormFile: %v", err) + } + + if _, err := io.Copy(part, reader); err != nil { + return "", fmt.Errorf("io.Copy: %v", err) + } + + if err := writer.Close(); err != nil { + return "", fmt.Errorf("writer.Close: %v", err) + } + + req, err := http.NewRequest("POST", firebaseStorageUrl+"?uploadType=media&name="+fileName, &buffer) + if err != nil { + return "", fmt.Errorf("http.NewRequest: %v", err) + } + + req.Header.Set("Authorization", "Bearer "+apiKey) + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("client.Do: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("upload failed with status: %v", resp.Status) + } + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("ioutil.ReadAll: %v", err) + } + + // Unmarshal the JSON response + var jsonResponse map[string]interface{} + if err := json.Unmarshal(bodyBytes, &jsonResponse); err != nil { + return "", fmt.Errorf("json.Unmarshal: %v", err) + } + + downloadToken, ok := jsonResponse["downloadTokens"].(string) + if !ok { + return "", fmt.Errorf("download token not found in response") + } + + return downloadToken, nil +} + +// Add log file url to user algo in firebase database +func addLogFileUrlToUser(user_id, algo_id, file_url string) error { + type UserData struct { + SandboxLogFileUrl string `json:"sandboxLogFileURL"` + } + + data := UserData{file_url} + + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("json.Marshal: %v", err) + } + + req, err := http.NewRequest("PATCH", "https://nutc-web-default-rtdb.firebaseio.com/users/"+user_id+"/algos/"+algo_id+".json", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("http.NewRequest: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("client.Do: %v", err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("upload failed with status: %v", resp.Status) + } + + return nil +} diff --git a/web/app/dash/algoType.tsx b/web/app/dash/algoType.tsx index 4db4eafa..4fb6b293 100644 --- a/web/app/dash/algoType.tsx +++ b/web/app/dash/algoType.tsx @@ -5,6 +5,7 @@ export default interface AlgorithmType { fileIdKey: string; name: string; description: string; + sandboxLogFileURL?: string; lintFailureMessage?: string; lintSuccessMessage?: string; } diff --git a/web/app/dash/submissions/[id]/page.tsx b/web/app/dash/submissions/[id]/page.tsx index e9446fd7..a22c693c 100644 --- a/web/app/dash/submissions/[id]/page.tsx +++ b/web/app/dash/submissions/[id]/page.tsx @@ -1,11 +1,54 @@ "use client"; import { useUserInfo } from "@/app/login/auth/context"; -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { useRouter } from "next/navigation"; import React from "react"; +import { + Bar, + BarChart, + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import { ref, getBytes } from "firebase/storage"; +import { useFirebase } from "@/app/firebase/context"; + +interface SandboxData { + matches?: { + timestamp: string; + data: { + buyer_id: string; + price: number; + quantity: number; + seller_id: string; + side: number; + ticker: string; + }; + }[]; + ob_updates?: { + timestamp: string; + data: { + price: number; + quantity: number; + security: string; + side: number; + }; + }[]; +} + export default function Page({ params }: { params: { id: string } }) { const userInfo = useUserInfo(); const router = useRouter(); + + const [sandboxData, setSandboxData] = React.useState(); + + const { storage } = useFirebase(); + useEffect(() => { if (!userInfo?.user) { return; @@ -16,7 +59,23 @@ export default function Page({ params }: { params: { id: string } }) { if (!userInfo?.user?.algos?.has(params.id)) { router.push("/dash"); } - }); + + const sandboxLogFileURL = userInfo.user.algos!.get( + params.id, + )!.sandboxLogFileURL; + + if (!sandboxLogFileURL) { + return; + } + + const reference = ref(storage, sandboxLogFileURL); + + getBytes(reference).then(bytes => { + const decoder = new TextDecoder("utf-8"); + const text = decoder.decode(bytes); + setSandboxData(JSON.parse(text)); + }); + }, [userInfo, params.id, router, storage]); const formatNewLines = (str: string) => { const LINES = str.split("\n"); @@ -26,26 +85,213 @@ export default function Page({ params }: { params: { id: string } }) { {index < LINES.length - 1 &&
} )); - } - + }; const algoDetails = userInfo?.user?.algos?.get(params.id); const lintFailureMessage = algoDetails?.lintFailureMessage; const lintSuccessMessage = algoDetails?.lintSuccessMessage; const stringToRender = lintFailureMessage || lintSuccessMessage || ""; - if(stringToRender === "") { - return ( -
-

Waiting on output...

-
+ return ( +
+ {stringToRender === "" ? ( +

Waiting on output from linter...

+ ) : ( +
Linter output: {formatNewLines(stringToRender)}
+ )} + +
+ + {sandboxData && } +
+ ); +} + +function SandboxDashboard(props: { sandboxData: SandboxData }) { + const duration = useMemo(() => { + if (props.sandboxData.matches == null) { + return 0; + } + + const first = new Date(props.sandboxData.matches[0].timestamp); + const last = new Date( + props.sandboxData.matches[props.sandboxData.matches.length - 1].timestamp, ); - } else { - // TODO: add styling - return ( -
- {formatNewLines(stringToRender)} -
+ return (last.getTime() - first.getTime()) / 1000; + }, [props.sandboxData]); + + const totalVolumeTraded = useMemo(() => { + if (props.sandboxData.matches == null) { + return 0; + } + return props.sandboxData.matches.reduce( + (acc, match) => acc + match.data.quantity, + 0, ); - } + }, [props.sandboxData]); + + return ( +
+

+ Results from sandboxed run: +

+
+
+
+
+
+ Total Volume Traded +
+
+ {totalVolumeTraded} +
+
+
+
+ +
+
+
+
+ Trial time (s) +
+
+ {duration} +
+
+
+
+
+ +
+
+

Volume Traded by Ticker

+ +
+
+

Average Trade Price by Ticker

+ +
+
+

Matches per Second

+ +
+
+
+ ); +} + +function MatchesPerSecondChart(props: { data: SandboxData }) { + const dataForChart = useMemo(() => { + if (props.data.matches == null) { + return []; + } + + const matchesPerSecond = new Map(); + + props.data.matches.forEach(match => { + const date = new Date(match.timestamp); + const second = date.toISOString().split(".")[0] + "Z"; // Remove milliseconds + matchesPerSecond.set(second, (matchesPerSecond.get(second) || 0) + 1); + }); + + const dataForChart = Array.from(matchesPerSecond, ([timestamp, count]) => ({ + timestamp, + count, + })); + + return dataForChart; + }, [props.data]); + + const formatXAxis = (timestamp: string) => { + const date = new Date(timestamp); + return date.toISOString().split("T")[1].split(".")[0]; + }; + + return ( + + + + + + + + + + + ); +} + +function VolumeByTickerChart(props: { data: SandboxData }) { + const volumeByTickerData = useMemo(() => { + if (props.data.matches == null) { + return []; + } + + const volumeByTicker: { [key: string]: number } = {}; + + props.data.matches.forEach((match: any) => { + const { ticker } = match.data; + const quantity = match.data.quantity; + + if (ticker in volumeByTicker) { + volumeByTicker[ticker] += quantity; + } else { + volumeByTicker[ticker] = quantity; + } + }); + + return Object.entries(volumeByTicker).map(([name, volume]) => ({ + name, + volume, + })); + }, [props.data]); + + return ( + + + + + + + + + + + ); +} + +function AverageTradePriceByTickerChart(props: { data: SandboxData }) { + const averageTradePriceByTicker = useMemo(() => { + if (props.data.matches == null) { + return []; + } + const sums: Record = {}; + const quantities: Record = {}; + + props.data.matches.forEach(match => { + const { ticker, price, quantity } = match.data; + if (!sums[ticker]) sums[ticker] = 0; + if (!quantities[ticker]) quantities[ticker] = 0; + sums[ticker] += price * quantity; + quantities[ticker] += quantity; + }); + return Object.keys(sums).map(ticker => ({ + name: ticker, + averagePrice: sums[ticker] / quantities[ticker], + })); + }, [props.data]); + + return ( + + + + + + + + + + + ); } diff --git a/web/package.json b/web/package.json index 57848eb2..7b716798 100644 --- a/web/package.json +++ b/web/package.json @@ -29,6 +29,7 @@ "postcss": "8.4.27", "react": "18.2.0", "react-dom": "18.2.0", + "recharts": "^2.11.0", "sharp": "^0.32.6", "sweetalert2": "^11.7.23", "tailwindcss": "3.3.3", diff --git a/wrapper/.clang-format b/wrapper/.clang-format deleted file mode 100644 index 9d31b83f..00000000 --- a/wrapper/.clang-format +++ /dev/null @@ -1,189 +0,0 @@ ---- -Language: Cpp -AccessModifierOffset: -4 -AlignAfterOpenBracket: BlockIndent -AlignArrayOfStructures: Left -AlignConsecutiveAssignments: None -AlignConsecutiveBitFields: None -AlignConsecutiveDeclarations: None -AlignConsecutiveMacros: AcrossEmptyLines -AlignEscapedNewlines: Right -AlignOperands: Align -AlignTrailingComments: true -AllowAllArgumentsOnNextLine: true -AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: Empty -AllowShortCaseLabelsOnASingleLine: false -AllowShortEnumsOnASingleLine: true -AllowShortFunctionsOnASingleLine: Inline -AllowShortIfStatementsOnASingleLine: Never -AllowShortLambdasOnASingleLine: All -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterReturnType: AllDefinitions -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: Yes -AttributeMacros: - - __capability -BasedOnStyle: "LLVM" -BinPackArguments: false -BinPackParameters: false -BitFieldColonSpacing: Both -BraceWrapping: - AfterCaseLabel: false - AfterClass: false - AfterControlStatement: Never - AfterEnum: false - AfterFunction: true - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - AfterExternBlock: false - BeforeCatch: false - BeforeElse: true - BeforeLambdaBody: false - BeforeWhile: false - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: true - SplitEmptyNamespace: true -BreakAfterJavaFieldAnnotations: true -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom -BreakBeforeConceptDeclarations: true -BreakBeforeTernaryOperators: true -BreakConstructorInitializers: AfterColon -BreakInheritanceList: AfterColon -BreakStringLiterals: true -ColumnLimit: 88 -CommentPragmas: "^( IWYU pragma:| NOLINT)" -CompactNamespaces: false -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 -Cpp11BracedListStyle: true -DeriveLineEnding: false -DerivePointerAlignment: false -DisableFormat: false -EmptyLineAfterAccessModifier: Never -EmptyLineBeforeAccessModifier: LogicalBlock -ExperimentalAutoDetectBinPacking: false -FixNamespaceComments: true -ForEachMacros: - - foreach - - Q_FOREACH - - BOOST_FOREACH -IfMacros: - - KJ_IF_MAYBE -IncludeBlocks: Regroup -IncludeCategories: - # Headers in "" with extension. - - Regex: '"([A-Za-z0-9.\Q/-_\E])+"' - Priority: 1 - CaseSensitive: false - # Headers in <> from libraries. - - Regex: "^(<(gsl|catch2))" - Priority: 2 - CaseSensitive: false - # C headers - - Regex: '' - Priority: 4 - CaseSensitive: false - # Headers in <> without extension. - - Regex: '<([A-Za-z0-9\Q/-_\E])+>' - Priority: 5 - CaseSensitive: false - # Headers in <> with extension. - - Regex: '<([A-Za-z0-9.\Q/-_\E])+>' - Priority: 3 - CaseSensitive: false -IncludeIsMainRegex: "(Test)?$" -IncludeIsMainSourceRegex: "" -IndentAccessModifiers: false -IndentCaseLabels: true -IndentCaseBlocks: true -IndentExternBlock: AfterExternBlock -IndentGotoLabels: false -IndentPPDirectives: AfterHash -IndentRequires: false -IndentWidth: 4 -IndentWrappedFunctionNames: false -KeepEmptyLinesAtTheStartOfBlocks: false -LambdaBodyIndentation: Signature -MacroBlockBegin: "" -MacroBlockEnd: "" -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -PackConstructorInitializers: NextLine -PenaltyBreakAssignment: 2 -PenaltyBreakBeforeFirstCallParameter: 19 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakOpenParenthesis: 0 -PenaltyBreakString: 1000 -PenaltyBreakTemplateDeclaration: 10 -PenaltyExcessCharacter: 1000000 -PenaltyIndentedWhitespace: 0 -PenaltyReturnTypeOnItsOwnLine: 60 -PointerAlignment: Left -PPIndentWidth: 2 -QualifierAlignment: Leave -ReferenceAlignment: Pointer -ReflowComments: true -RemoveBracesLLVM: false -SeparateDefinitionBlocks: Always -ShortNamespaceLines: 1 -SortIncludes: CaseInsensitive -SortUsingDeclarations: true -SpaceAfterCStyleCast: false -SpaceAfterLogicalNot: false -SpaceAfterTemplateKeyword: true -SpaceAroundPointerQualifiers: Default -SpaceBeforeAssignmentOperators: true -SpaceBeforeCaseColon: false -SpaceBeforeCpp11BracedList: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true -SpaceBeforeParens: ControlStatementsExceptControlMacros -# Ignored as SpaceBeforeParens != Custom -SpaceBeforeParensOptions: - AfterControlStatements: true - AfterForeachMacros: true - AfterFunctionDefinitionName: false - AfterFunctionDeclarationName: false - AfterIfMacros: true - AfterOverloadedOperator: false - BeforeNonEmptyParentheses: false -SpaceBeforeRangeBasedForLoopColon: true -SpaceBeforeSquareBrackets: false -SpaceInEmptyBlock: false -SpaceInEmptyParentheses: false -SpacesInAngles: Never -SpacesBeforeTrailingComments: 1 -SpacesInConditionalStatement: false -SpacesInContainerLiterals: false -SpacesInCStyleCastParentheses: false -SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Latest -StatementAttributeLikeMacros: - - Q_EMIT -StatementMacros: - - Q_UNUSED - - QT_REQUIRE_VERSION - - wxBEGIN_EVENT_TABLE - - wxEND_EVENT_TABLE - - EVT_MENU -TabWidth: 4 -UseCRLF: false -UseTab: Never -WhitespaceSensitiveMacros: - - STRINGIZE - - PP_STRINGIZE - - BOOST_PP_STRINGIZE - - NS_SWIFT_NAME - - CF_SWIFT_NAME ---- - diff --git a/wrapper/.clang-tidy b/wrapper/.clang-tidy deleted file mode 100644 index d9de2cc8..00000000 --- a/wrapper/.clang-tidy +++ /dev/null @@ -1,134 +0,0 @@ ---- -Checks: "\ - boost-*,\ - bugprone-*,\ - cert-*,\ - clang-analyzer-*,\ - clang-diagnostic-*,\ - concurrency-*,\ - cppcoreguidelines-*,\ - google-*,\ - hicpp-*,\ - llvm-*,\ - misc-*,\ - modernize-*,\ - performance-*,\ - portability-*,\ - readability-*,\ - fuchsia-multiple-inheritance,\ - fuchsia-trailing-return,\ - fuchsia-virtual-inheritance,\ - google-runtime-int,\ - -hicpp-braces-around-statements,\ - -google-readability-braces-around-statements,\ - -llvm-header-guard,\ - -hicpp-no-array-decay,\ - -modernize-concat-nested-namespaces,\ - -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\ - -modernize-use-trailing-return-type,\ - -cppcoreguidelines-owning-memory,\ - -hicpp-named-parameter,\ - -readability-named-parameter,\ - -readability-uppercase-literal-suffix,\ - -readability-implicit-bool-conversion,\ - -hicpp-uppercase-literal-suffix,\ - -misc-use-anonymous-namespace,\ - -bugprone-easily-swappable-parameters,\ - -*-reinterpret-cast,\ - -cppcoreguidelines-avoid-const-or-ref-data-members" -WarningsAsErrors: false -AnalyzeTemporaryDtors: false -FormatStyle: file -HeaderFilterRegex: "(^config.h|.*\\.hpp)$" -CheckOptions: - llvm-else-after-return.WarnOnConditionVariables: "false" - modernize-loop-convert.MinConfidence: reasonable - modernize-replace-auto-ptr.IncludeStyle: llvm - modernize-pass-by-value.IncludeStyle: llvm - google-readability-namespace-comments.ShortNamespaceLines: "10" - google-readability-namespace-comments.SpacesBeforeComments: "2" - cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: "true" - readability-braces-around-statements.ShortStatementLines: 3 - cert-err33-c.CheckedFunctions: "::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;" - modernize-loop-convert.MaxCopySize: "16" - cert-dcl16-c.NewSuffixes: "L;LL;LU;LLU" - cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: "false" - cert-str34-c.DiagnoseSignedUnsignedCharComparisons: "false" - modernize-use-nullptr.NullMacros: "NULL" - llvm-qualified-auto.AddConstToQualified: "false" - modernize-loop-convert.NamingStyle: CamelCase - llvm-else-after-return.WarnOnUnfixable: "false" - google-readability-function-size.StatementThreshold: "800" - bugprone-argument-comment.StrictMode: "true" - # Prefer using enum classes with 2 values for parameters instead of bools - bugprone-argument-comment.CommentBoolLiterals: "true" - bugprone-misplaced-widening-cast.CheckImplicitCasts: "true" - bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression: "true" - bugprone-suspicious-string-compare.WarnOnLogicalNotComparison: "true" - readability-simplify-boolean-expr.ChainedConditionalReturn: "true" - readability-simplify-boolean-expr.ChainedConditionalAssignment: "true" - readability-uniqueptr-delete-release.PreferResetCall: "true" - readability-function-cognitive-complexity.IgnoreMacros: "true" - cppcoreguidelines-init-variables.MathHeader: "" - cppcoreguidelines-narrowing-conversions.PedanticMode: "true" - readability-else-after-return.WarnOnUnfixable: "true" - readability-else-after-return.WarnOnConditionVariables: "true" - readability-inconsistent-declaration-parameter-name.Strict: "true" - readability-qualified-auto.AddConstToQualified: "true" - readability-redundant-access-specifiers.CheckFirstDeclaration: "true" - readability-identifier-naming.AbstractClassCase: "CamelCase" - readability-identifier-naming.ClassCase: "CamelCase" - readability-identifier-naming.ClassConstantCase: "lower_case" - readability-identifier-naming.ClassMemberCase: "lower_case" - readability-identifier-naming.ClassMethodCase: "lower_case" - readability-identifier-naming.ConstantCase: "UPPER_CASE" - readability-identifier-naming.ConstantMemberCase: "UPPER_CASE" - readability-identifier-naming.ConstantParameterCase: "lower_case" - readability-identifier-naming.ConstantPointerParameterCase: "lower_case" - readability-identifier-naming.ConstexprFunctionCase: "lower_case" - readability-identifier-naming.ConstexprMethodCase: "lower_case" - readability-identifier-naming.ConstexprVariableCase: "UPPER_CASE" - readability-identifier-naming.EnumCase: "CamelCase" - readability-identifier-naming.EnumConstantCase: "UPPER_CASE" - readability-identifier-naming.FunctionCase: "lower_case" - readability-identifier-naming.GlobalConstantCase: "UPPER_CASE" - readability-identifier-naming.GlobalConstantPointerCase: "UPPER_CASE" - readability-identifier-naming.GlobalFunctionCase: "lower_case" - readability-identifier-naming.GlobalPointerCase: "lower_case" - readability-identifier-naming.GlobalVariableCase: "lower_case" - readability-identifier-naming.InlineNamespaceCase: "lower_case" - readability-identifier-naming.LocalConstantCase: "lower_case" - readability-identifier-naming.LocalConstantPointerCase: "lower_case" - readability-identifier-naming.LocalPointerCase: "lower_case" - readability-identifier-naming.LocalVariableCase: "lower_case" - readability-identifier-naming.MacroDefinitionCase: "UPPER_CASE" - readability-identifier-naming.MemberCase: "lower_case" - readability-identifier-naming.MethodCase: "lower_case" - readability-identifier-naming.NamespaceCase: "lower_case" - readability-identifier-naming.ParameterCase: "lower_case" - readability-identifier-naming.ParameterPackCase: "lower_case" - readability-identifier-naming.PointerParameterCase: "lower_case" - readability-identifier-naming.PrivateMemberCase: "lower_case" - readability-identifier-naming.PrivateMemberSuffix: "_" - readability-identifier-naming.PrivateMethodCase: "lower_case" - readability-identifier-naming.PrivateMethodSuffix: "_" - readability-identifier-naming.ProtectedMemberCase: "lower_case" - readability-identifier-naming.ProtectedMemberSuffix: "_" - readability-identifier-naming.ProtectedMethodCase: "lower_case" - readability-identifier-naming.ProtectedMethodSuffix: "_" - readability-identifier-naming.PublicMemberCase: "lower_case" - readability-identifier-naming.PublicMethodCase: "lower_case" - readability-identifier-naming.ScopedEnumConstantCase: "lower_case" - readability-identifier-naming.StaticConstantCase: "lower_case" - readability-identifier-naming.StaticVariableCase: "lower_case" - readability-identifier-naming.StructCase: "lower_case" - readability-identifier-naming.TemplateParameterCase: "CamelCase" - readability-identifier-naming.TemplateTemplateParameterCase: "CamelCase" - readability-identifier-naming.TypeAliasCase: "lower_case" - readability-identifier-naming.TypedefCase: "lower_case" - readability-identifier-naming.TypeTemplateParameterCase: "CamelCase" - readability-identifier-naming.UnionCase: "lower_case" - readability-identifier-naming.ValueTemplateParameterCase: "CamelCase" - readability-identifier-naming.VariableCase: "lower_case" - readability-identifier-naming.VirtualMethodCase: "lower_case" ---- diff --git a/wrapper/.clangd b/wrapper/.clangd deleted file mode 100644 index fd829e02..00000000 --- a/wrapper/.clangd +++ /dev/null @@ -1,2 +0,0 @@ -CompileFlags: - CompilationDatabase: "build/dev" diff --git a/wrapper/.codespellignore b/wrapper/.codespellignore deleted file mode 100644 index 0e50ea7c..00000000 --- a/wrapper/.codespellignore +++ /dev/null @@ -1 +0,0 @@ -ws diff --git a/wrapper/.codespellrc b/wrapper/.codespellrc deleted file mode 100644 index 69ff0039..00000000 --- a/wrapper/.codespellrc +++ /dev/null @@ -1,7 +0,0 @@ -[codespell] -builtin = clear,rare,en-GB_to_en-US,names,informal,code -check-filenames = -check-hidden = -skip = */.git,*/build,*/prefix,*/conan,*/logs,*/3rd-party -quiet-level = 2 -ignore-words = .codespellignore diff --git a/wrapper/.gitignore b/wrapper/.gitignore deleted file mode 100644 index 95809f71..00000000 --- a/wrapper/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -.idea/ -.vs/ -.vscode/ -build/ -cmake-build-*/ -conan/ -prefix/ -**/.DS_Store -CMakeLists.txt.user -CMakeUserPresets.json -logs/ -compile_commands.json -html -latex -Doxyfile.bak -algos/ diff --git a/wrapper/BUILDING-DEPRECATED.md b/wrapper/BUILDING-DEPRECATED.md deleted file mode 100644 index c8b36a56..00000000 --- a/wrapper/BUILDING-DEPRECATED.md +++ /dev/null @@ -1,67 +0,0 @@ - -# Building NUTC-Client Guide -## Dependencies -1. Conan -- `pip install conan` -- `dnf install cmake` -- `dnf install go-task` -- `dnf install perl` -- `conan profile detect` -- Copy `.conan2/profiles/default` to `.conan2/profiles/cpp20` -- Change gnu17 to gnu20 -2. Install dependencies -- `mkdir build && cd build` -- `conan install .. -s build_type=Debug -b missing -pr cpp20 -pr:b cpp20` -3. Change `CMakeUserPresets.json` the following: -``` -{ - "version": 2, - "cmakeMinimumRequired": { - "major": 3, - "minor": 14, - "patch": 0 - }, - "configurePresets": [ - { - "name": "dev", - "binaryDir": "${sourceDir}/build/dev", - "inherits": ["dev-mode", "conan", "ci-darwin"], - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug" - } - } - ], - "buildPresets": [ - { - "name": "dev", - "configurePreset": "dev", - "configuration": "Debug" - } - ], - "testPresets": [ - { - "name": "dev", - "configurePreset": "dev", - "configuration": "Debug", - "output": { - "outputOnFailure": true - } - } - ] -} -``` -4. `cd .. && go-task init` - - -## Building -`go-task build` - -## Doxygen -This repository uses Doxygen for documentation purposes. To generate and display: - -1. `doxygen Doxyfile` -2. `open html/index.html` - - -## Running -NUTC-Client is not designed to be a standalone executable. It should be executed with NUTC24 (the exchange) via `task run`. diff --git a/wrapper/CMakeLists.txt b/wrapper/CMakeLists.txt deleted file mode 100644 index ab523d89..00000000 --- a/wrapper/CMakeLists.txt +++ /dev/null @@ -1,121 +0,0 @@ -cmake_minimum_required(VERSION 3.14) - -include(FetchContent) -include(cmake/prelude.cmake) - -project( - NUTC-client - VERSION 0.1.0 - DESCRIPTION "Client for the Northwestern University Trading Competition." - HOMEPAGE_URL "https://example.com/" - LANGUAGES CXX -) - -include(cmake/project-is-top-level.cmake) -include(cmake/variables.cmake) - -configure_file(src/config.h.in config.h) - -# ---- Load Dependencies ---- - -# Conan -find_package(fmt REQUIRED) # String formatting -find_package(quill REQUIRED) # Logging - -find_package(argparse REQUIRED) # Argument parsing -find_package(rabbitmq-c REQUIRED) -find_package(CURL REQUIRED) -find_package(glaze REQUIRED) -find_package(Python 3.11 COMPONENTS Interpreter Development EXACT REQUIRED) -find_package(PythonLibs 3.11 EXACT REQUIRED) -find_package(pybind11 REQUIRED) - -# Git version tracking -if(USE_GIT_VERSION_TRACKING) - FetchContent_Declare(cmake_git_version_tracking - GIT_REPOSITORY https://github.com/andrew-hardin/cmake-git-version-tracking.git - GIT_TAG 9b5fc5088b4089ff2adc20d607976b9923e3d737 - ) - FetchContent_MakeAvailable(cmake_git_version_tracking) -endif() - -# ---- Declare library ---- - -add_library( - NUTC-client_lib OBJECT - src/rabbitmq/rabbitmq.cpp - src/firebase/firebase.cpp - src/pywrapper/pywrapper.cpp - src/dev_mode/dev_mode.cpp - src/pywrapper/rate_limiter.cpp - # Utils - src/logging.cpp -) - -target_include_directories( - NUTC-client_lib ${warning_guard} - PUBLIC - "$" - "$" -) - -target_compile_features(NUTC-client_lib PUBLIC cxx_std_20) - -target_link_libraries(NUTC-client_lib PRIVATE fmt::fmt) -target_link_libraries(NUTC-client_lib PRIVATE quill::quill) -target_link_libraries(NUTC-client_lib PRIVATE rabbitmq::rabbitmq-static) -target_link_libraries(NUTC-client_lib PRIVATE CURL::libcurl) -target_link_libraries(NUTC-client_lib PRIVATE glaze::glaze) -target_link_libraries(NUTC-client_lib PRIVATE pybind11::pybind11) -target_link_libraries(NUTC-client_lib PRIVATE Python::Python) -target_link_libraries(NUTC-client_lib PRIVATE ${PYTHON_LIBRARIES}) - - - -# ---- Declare executable ---- - -add_executable(NUTC-client_exe src/main.cpp) -add_executable(NUTC-client::exe ALIAS NUTC-client_exe) - -set_property(TARGET NUTC-client_exe PROPERTY OUTPUT_NAME NUTC-client) - -target_compile_features(NUTC-client_exe PRIVATE cxx_std_20) - -target_link_libraries(NUTC-client_exe PRIVATE NUTC-client_lib) -target_link_libraries(NUTC-client_exe PRIVATE fmt::fmt) -target_link_libraries(NUTC-client_exe PRIVATE quill::quill) - -target_link_libraries(NUTC-client_exe PRIVATE argparse::argparse) -target_link_libraries(NUTC-client_exe PRIVATE rabbitmq::rabbitmq-static) -target_link_libraries(NUTC-client_exe PRIVATE CURL::libcurl) -target_link_libraries(NUTC-client_exe PRIVATE glaze::glaze) -target_link_libraries(NUTC-client_exe PRIVATE pybind11::pybind11) -target_link_libraries(NUTC-client_exe PRIVATE Python::Python) -target_link_libraries(NUTC-client_exe PRIVATE ${PYTHON_LIBRARIES}) - -if(USE_GIT_VERSION_TRACKING) - target_link_libraries(NUTC-client_exe PRIVATE cmake_git_version_tracking) -else() - target_compile_definitions(NUTC-client_lib PRIVATE -DNO_GIT_VERSION_TRACKING) - target_compile_definitions(NUTC-client_exe PRIVATE -DNO_GIT_VERSION_TRACKING) -endif() - - -# ---- Install rules ---- - -if(NOT CMAKE_SKIP_INSTALL_RULES) - include(cmake/install-rules.cmake) -endif() - -# ---- Developer mode ---- - -if(NOT NUTC-client_DEVELOPER_MODE) - return() -elseif(NOT PROJECT_IS_TOP_LEVEL) - message( - AUTHOR_WARNING - "Developer mode is intended for developers of NUTC-client" - ) -endif() - -include(cmake/dev-mode.cmake) diff --git a/wrapper/CMakePresets.json b/wrapper/CMakePresets.json deleted file mode 100644 index 7d8bacf8..00000000 --- a/wrapper/CMakePresets.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "version": 2, - "cmakeMinimumRequired": { - "major": 3, - "minor": 14, - "patch": 0 - }, - "configurePresets": [ - { - "name": "cmake-pedantic", - "hidden": true, - "warnings": { - "dev": true, - "deprecated": true, - "uninitialized": true, - "unusedCli": true, - "systemVars": false - }, - "errors": { - "dev": false, - "deprecated": true - } - }, - { - "name": "dev-mode", - "hidden": true, - "inherits": "cmake-pedantic", - "cacheVariables": { - "NUTC-client_DEVELOPER_MODE": "ON" - } - }, - { - "name": "conan", - "hidden": true, - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan/conan_toolchain.cmake", - "CMAKE_POLICY_DEFAULT_CMP0091": "NEW" - } - }, - { - "name": "cppcheck", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr;--platform=native" - } - }, - { - "name": "clang-tidy", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/" - } - }, - { - "name": "ci-std", - "description": "This preset makes sure the project actually builds with at least the specified standard", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_EXTENSIONS": "OFF", - "CMAKE_CXX_STANDARD": "20", - "CMAKE_CXX_STANDARD_REQUIRED": "ON" - } - }, - { - "name": "flags-linux", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_FLAGS": "-D_FORTIFY_SOURCE=3 -fstack-protector-strong -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", - "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now", - "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now" - } - }, - { - "name": "flags-darwin", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast" - } - }, - { - "name": "flags-windows", - "description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_FLAGS": "/sdl /analyze /analyze:external- /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc", - "CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf" - } - }, - { - "name": "flags-windows-mingw", - "hidden": true, - "cacheVariables": { - "CMAKE_CXX_FLAGS": "-D_FORTIFY_SOURCE=3 -fstack-protector-strong -fcf-protection=full -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast", - "CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed", - "CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed" - } - }, - { - "name": "ci-linux", - "generator": "Unix Makefiles", - "hidden": true, - "inherits": ["flags-linux", "ci-std"], - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } - }, - { - "name": "ci-darwin", - "generator": "Unix Makefiles", - "hidden": true, - "inherits": ["flags-darwin", "ci-std"], - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } - }, - { - "name": "ci-win64", - "inherits": ["flags-windows", "ci-std"], - "generator": "Visual Studio 17 2022", - "architecture": "x64", - "hidden": true - }, - { - "name": "ci-win64-mingw", - "inherits": ["flags-windows-mingw", "ci-std"], - "generator": "Ninja", - "hidden": true - }, - { - "name": "coverage-linux", - "binaryDir": "${sourceDir}/build/coverage", - "inherits": "ci-linux", - "hidden": true, - "cacheVariables": { - "ENABLE_COVERAGE": "ON", - "CMAKE_BUILD_TYPE": "Coverage", - "CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions", - "CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage", - "CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage", - "CMAKE_MAP_IMPORTED_CONFIG_COVERAGE": "Coverage;RelWithDebInfo;Release;Debug;" - } - }, - { - "name": "ci-coverage", - "inherits": ["coverage-linux", "dev-mode", "conan"], - "cacheVariables": { - "COVERAGE_HTML_COMMAND": "" - } - }, - { - "name": "ci-sanitize", - "binaryDir": "${sourceDir}/build/sanitize", - "inherits": ["ci-linux", "dev-mode", "conan"], - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Sanitize", - "CMAKE_CXX_FLAGS_SANITIZE": "-O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common", - "CMAKE_MAP_IMPORTED_CONFIG_SANITIZE": "Sanitize;RelWithDebInfo;Release;Debug;" - } - }, - { - "name": "ci-build", - "binaryDir": "${sourceDir}/build", - "hidden": true - }, - { - "name": "ci-macos", - "inherits": ["ci-build", "ci-darwin", "dev-mode", "conan"] - }, - { - "name": "ci-ubuntu", - "inherits": [ - "ci-build", - "ci-linux", - "clang-tidy", - "conan", - "cppcheck", - "dev-mode" - ] - }, - { - "name": "ci-windows", - "inherits": ["ci-build", "ci-win64", "dev-mode", "conan"] - }, - { - "name": "ci-docker", - "inherits": ["ci-build", "ci-linux", "conan"], - "cacheVariables": { - "USE_GIT_VERSION_TRACKING": "OFF" - } - } - - ] -} diff --git a/wrapper/CODE_OF_CONDUCT.md b/wrapper/CODE_OF_CONDUCT.md deleted file mode 100644 index d1202311..00000000 --- a/wrapper/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,5 +0,0 @@ -# Code of Conduct - -* You will be judged by your contributions first, and your sense of humor - second. -* Nobody owes you anything. diff --git a/wrapper/CONTRIBUTING.md b/wrapper/CONTRIBUTING.md deleted file mode 100644 index 10cccf38..00000000 --- a/wrapper/CONTRIBUTING.md +++ /dev/null @@ -1,19 +0,0 @@ -# Contributing - - - -## Code of Conduct - -Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document. - -## Getting started - -Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md) -document. - -In addition to he above, if you use the presets file as instructed, then you -should NOT check it into source control, just as the CMake documentation -suggests. diff --git a/wrapper/Doxyfile b/wrapper/Doxyfile deleted file mode 100644 index 777bf1a9..00000000 --- a/wrapper/Doxyfile +++ /dev/null @@ -1,2822 +0,0 @@ -# Doxyfile 1.9.8 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). -# -# Note: -# -# Use doxygen to compare the used configuration file with the template -# configuration file: -# doxygen -x [configFile] -# Use doxygen to compare the used configuration file with the template -# configuration file without replacing the environment variables or CMake type -# replacement variables: -# doxygen -x_noenv [configFile] - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "NUTC Client" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = "NUTC Client is a Python/C++ wrapper for user-submitted algorithms. It exposes an API for algos to interact with the exchange(NUTC24) via RabbitMQ." - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 -# sub-directories (in 2 levels) under the output directory of each output format -# and will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to -# control the number of sub-directories. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# Controls the number of sub-directories that will be created when -# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every -# level increment doubles the number of directories, resulting in 4096 -# directories at level 8 which is the default and also the maximum value. The -# sub-directories are organized in 2 levels, the first level always has a fixed -# number of 16 directories. -# Minimum value: 0, maximum value: 8, default value: 8. -# This tag requires that the tag CREATE_SUBDIRS is set to YES. - -CREATE_SUBDIRS_LEVEL = 8 - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, -# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English -# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, -# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with -# English messages), Korean, Korean-en (Korean with English messages), Latvian, -# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, -# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, -# Swedish, Turkish, Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# By default Python docstrings are displayed as preformatted text and doxygen's -# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. -# The default value is: YES. - -PYTHON_DOCSTRING = YES - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:^^" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". Note that you cannot put \n's in the value part of an alias -# to insert newlines (in the resulting output). You can put ^^ in the value part -# of an alias to insert a newline as if a physical newline was in the original -# file. When you need a literal { or } or , in the value part of an alias you -# have to escape them by means of a backslash (\), this can lead to conflicts -# with the commands \{ and \} for these it is advised to use the version @{ and -# @} or use a double escape (\\{ and \\}) - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, -# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files -# as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add -# * to the FILE_PATTERNS. -# -# Note see also the list of default file extension mappings. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 5 - -# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to -# generate identifiers for the Markdown headings. Note: Every identifier is -# unique. -# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a -# sequence number starting at 0 and GITHUB use the lower case version of title -# with any whitespace replaced by '-' and punctuation characters removed. -# The default value is: DOXYGEN. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -MARKDOWN_ID_STYLE = DOXYGEN - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of -# cores available in the system. You can set it explicitly to a value larger -# than 0 to get more control over the balance between CPU load and processing -# speed. At this moment only the input processing can be done using multiple -# threads. Since this is still an experimental feature the default is set to 1, -# which effectively disables parallel processing. Please report any issues you -# encounter. Generating dot graphs in parallel is controlled by the -# DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. - -NUM_PROC_THREADS = 1 - -# If the TIMESTAMP tag is set different from NO then each generated page will -# contain the date or date and time when the page was generated. Setting this to -# NO can help when comparing the output of multiple runs. -# Possible values are: YES, NO, DATETIME and DATE. -# The default value is: NO. - -TIMESTAMP = NO - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If this flag is set to YES, the name of an unnamed parameter in a declaration -# will be determined by the corresponding definition. By default unnamed -# parameters remain unnamed in the output. -# The default value is: YES. - -RESOLVE_UNNAMED_PARAMS = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# will also hide undocumented C++ concepts if enabled. This option has no effect -# if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be -# able to match the capabilities of the underlying filesystem. In case the -# filesystem is case sensitive (i.e. it supports files in the same directory -# whose names only differ in casing), the option must be set to YES to properly -# deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be set to NO to properly deal with -# output files written for symbols that only differ in casing, such as for two -# classes, one named CLASS and the other named Class, and to also support -# references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option -# to NO, whereas on Linux or other Unix flavors it should typically be set to -# YES. -# Possible values are: SYSTEM, NO and YES. -# The default value is: SYSTEM. - -CASE_SENSE_NAMES = SYSTEM - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class -# will show which file needs to be included to use the class. -# The default value is: YES. - -SHOW_HEADERFILE = YES - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. See also section "Changing the -# layout of pages" for information. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as documenting some parameters in -# a documented function twice, or documenting parameters that don't exist or -# using markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete -# function parameter documentation. If set to NO, doxygen will accept that some -# parameters have no documentation without warning. -# The default value is: YES. - -WARN_IF_INCOMPLETE_DOC = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong parameter -# documentation, but not about the absence of documentation. If EXTRACT_ALL is -# set to YES then this flag will automatically be disabled. See also -# WARN_IF_INCOMPLETE_DOC -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about -# undocumented enumeration values. If set to NO, doxygen will accept -# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: NO. - -WARN_IF_UNDOC_ENUM_VAL = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves -# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not -# write the warning messages in between other messages but write them at the end -# of a run, in case a WARN_LOGFILE is defined the warning messages will be -# besides being in the defined file also be shown at the end of a run, unless -# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case -# the behavior will remain as with the setting FAIL_ON_WARNINGS. -# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# See also: WARN_LINE_FORMAT -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# In the $text part of the WARN_FORMAT command it is possible that a reference -# to a more specific place is given. To make it easier to jump to this place -# (outside of doxygen) the user can define a custom "cut" / "paste" string. -# Example: -# WARN_LINE_FORMAT = "'vi $file +$line'" -# See also: WARN_FORMAT -# The default value is: at line $line of file $file. - -WARN_LINE_FORMAT = "at line $line of file $file" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). In case the file specified cannot be opened for writing the -# warning and error messages are written to standard error. When as file - is -# specified the warning and error messages are written to standard output -# (stdout). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = src - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: -# https://www.gnu.org/software/libiconv/) for the list of possible encodings. -# See also: INPUT_FILE_ENCODING -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify -# character encoding on a per file pattern basis. Doxygen will compare the file -# name with each pattern and apply the encoding instead of the default -# INPUT_ENCODING) if there is a match. The character encodings are a list of the -# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding -# "INPUT_ENCODING" for further information on supported encodings. - -INPUT_FILE_ENCODING = - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# Note the list of default checked file patterns might differ from the list of -# default file extension mappings. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, -# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, -# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, -# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be -# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cxxm \ - *.cpp \ - *.cppm \ - *.c++ \ - *.c++m \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.ixx \ - *.l \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f18 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.ice - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# ANamespace::AClass, ANamespace::*Test - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that doxygen will use the data processed and written to standard output -# for further processing, therefore nothing else, like debug statements or used -# commands (so in case of a Windows batch file always use @echo OFF), should be -# written to standard output. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -# The Fortran standard specifies that for fixed formatted Fortran code all -# characters from position 72 are to be considered as comment. A common -# extension is to allow longer lines before the automatic comment starts. The -# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can -# be processed before the automatic comment starts. -# Minimum value: 7, maximum value: 10000, default value: 72. - -FORTRAN_COMMENT_AFTER = 72 - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) -# that should be ignored while generating the index headers. The IGNORE_PREFIX -# tag works for classes, function and member names. The entity will be placed in -# the alphabetical list under the first letter of the entity name that remains -# after removing the prefix. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). -# Note: Since the styling of scrollbars can currently not be overruled in -# Webkit/Chromium, the styling will be left out of the default doxygen.css if -# one or more extra stylesheets have been specified. So if scrollbar -# customization is desired it has to be added explicitly. For an example see the -# documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output -# should be rendered with a dark or light theme. -# Possible values are: LIGHT always generate light mode output, DARK always -# generate dark mode output, AUTO_LIGHT automatically set the mode according to -# the user preference, use light mode if no preference is set (the default), -# AUTO_DARK automatically set the mode according to the user preference, use -# dark mode if no preference is set and TOGGLE allow to user to switch between -# light and dark mode via a button. -# The default value is: AUTO_LIGHT. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE = AUTO_LIGHT - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a color-wheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use gray-scales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be -# dynamically folded and expanded in the generated HTML source code. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_CODE_FOLDING = YES - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: -# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML -# output directory. Running make will produce the docset in that directory and -# running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag determines the URL of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDURL = - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# on Windows. In the beginning of 2021 Microsoft took the original page, with -# a.o. the download links, offline the HTML help workshop was already many years -# in maintenance mode). You can download the HTML help workshop from the web -# archives at Installation executable (see: -# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo -# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the main .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# The SITEMAP_URL tag is used to specify the full URL of the place where the -# generated documentation will be placed on the server by the user during the -# deployment of the documentation. The generated sitemap is called sitemap.xml -# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL -# is specified no sitemap is generated. For information about the sitemap -# protocol see https://www.sitemaps.org -# This tag requires that the tag GENERATE_HTML is set to YES. - -SITEMAP_URL = - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to -# run qhelpgenerator on the generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine tune the look of the index (see "Fine-tuning the output"). As an -# example, the default style sheet generated by doxygen has an example that -# shows how to put an image at the root of the tree instead of the PROJECT_NAME. -# Since the tree basically has the same information as the tab index, you could -# consider setting DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the -# FULL_SIDEBAR option determines if the side bar is limited to only the treeview -# area (value NO) or if it should extend to the full height of the window (value -# YES). Setting this to YES gives a layout similar to -# https://docs.readthedocs.io with more room for contents, but less room for the -# project logo, title, and description. If either GENERATE_TREEVIEW or -# DISABLE_INDEX is set to NO, this option has no effect. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FULL_SIDEBAR = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email -# addresses. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -OBFUSCATE_EMAILS = YES - -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FORMULA_FORMAT = png - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. -# Note that the different versions of MathJax have different requirements with -# regards to the different settings, so it is possible that also other MathJax -# settings have to be changed when switching between the different MathJax -# versions. -# Possible values are: MathJax_2 and MathJax_3. -# The default value is: MathJax_2. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_VERSION = MathJax_2 - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. For more details about the output format see MathJax -# version 2 (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 -# (see: -# http://docs.mathjax.org/en/latest/web/components/output.html). -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility. This is the name for Mathjax version 2, for MathJax version 3 -# this will be translated into chtml), NativeMML (i.e. MathML. Only supported -# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This -# is the name for Mathjax version 3, for MathJax version 2 this will be -# translated into HTML-CSS) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. The default value is: -# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 -# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see -# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# For example for MathJax version 3 (see -# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): -# MATHJAX_EXTENSIONS = ams -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /