From 55497e6ee79cde07f04b3a9ba40d39e9f8de5cf6 Mon Sep 17 00:00:00 2001 From: Mike Kipnis Date: Sun, 2 Nov 2025 19:21:55 -0500 Subject: [PATCH] fix_ws_proxy - initial check-in --- CMakeLists.txt | 2 +- Common/fix_json_converter.h | 127 + .../{oms => fix_ws_proxy}/CMakeLists.txt | 13 +- .../cpp_ws_reactjs/fix_ws_proxy/FIX44.xml | 6594 +++++++++++++++++ .../fix_ws_proxy/fix_application.cpp | 66 + .../fix_ws_proxy/fix_application.h | 48 + .../fix_ws_proxy/fix_ws_proxy.cpp | 70 + .../cpp_ws_reactjs/fix_ws_proxy/fixproxy.ini | 17 + .../cpp_ws_reactjs/fix_ws_proxy/ws_server.cpp | 78 + .../cpp_ws_reactjs/fix_ws_proxy/ws_server.h | 216 + MiscClients/cpp_ws_reactjs/oms/oms.cpp | 6 - .../cpp_ws_reactjs/py_client/py_client.py | 145 + .../webtrader_reactjs_ws/package.json | 43 + .../webtrader_reactjs_ws/public/favicon.ico | Bin 0 -> 3870 bytes .../webtrader_reactjs_ws/public/index.html | 43 + .../webtrader_reactjs_ws/public/logo192.png | Bin 0 -> 5347 bytes .../webtrader_reactjs_ws/public/logo512.png | Bin 0 -> 9664 bytes .../webtrader_reactjs_ws/public/manifest.json | 25 + .../webtrader_reactjs_ws/public/robots.txt | 3 + .../webtrader_reactjs_ws/src/App.css | 102 + .../webtrader_reactjs_ws/src/App.js | 236 + .../webtrader_reactjs_ws/src/App.test.js | 8 + .../src/components/History.js | 119 + .../src/components/Login.js | 101 + .../src/components/PositionsAndMarketData.js | 171 + .../src/components/PriceLevels.js | 68 + .../src/components/SessionStateWrapper.js | 165 + .../src/components/Ticket.js | 200 + .../src/components/helpers.js | 60 + .../webtrader_reactjs_ws/src/index.css | 13 + .../webtrader_reactjs_ws/src/index.js | 17 + .../webtrader_reactjs_ws/src/logo.svg | 1 + .../src/reportWebVitals.js | 13 + .../webtrader_reactjs_ws/src/setupTests.js | 5 + build_with_cmake.sh | 2 +- 35 files changed, 8764 insertions(+), 13 deletions(-) create mode 100644 Common/fix_json_converter.h rename MiscClients/cpp_ws_reactjs/{oms => fix_ws_proxy}/CMakeLists.txt (69%) create mode 100755 MiscClients/cpp_ws_reactjs/fix_ws_proxy/FIX44.xml create mode 100644 MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.cpp create mode 100644 MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.h create mode 100644 MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_ws_proxy.cpp create mode 100644 MiscClients/cpp_ws_reactjs/fix_ws_proxy/fixproxy.ini create mode 100644 MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.cpp create mode 100644 MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.h delete mode 100644 MiscClients/cpp_ws_reactjs/oms/oms.cpp create mode 100644 MiscClients/cpp_ws_reactjs/py_client/py_client.py create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/package.json create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/favicon.ico create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/index.html create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/logo192.png create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/logo512.png create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/manifest.json create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/robots.txt create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.css create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.test.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/History.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Login.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PositionsAndMarketData.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PriceLevels.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/SessionStateWrapper.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Ticket.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/helpers.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.css create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/logo.svg create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/reportWebVitals.js create mode 100644 MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/setupTests.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ad633d..501bad7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ add_subdirectory( FIXGateway/src ) add_subdirectory( DataService/src ) add_subdirectory( MatchingEngine/src ) add_subdirectory( LatencyTest ) -add_subdirectory( MiscClients/cpp_ws_reactjs/oms ) +add_subdirectory( MiscClients/cpp_ws_reactjs/fix_ws_proxy ) install(DIRECTORY ${CMAKE_SOURCE_DIR}/MiscATS/ DESTINATION MiscATS diff --git a/Common/fix_json_converter.h b/Common/fix_json_converter.h new file mode 100644 index 0000000..77b5455 --- /dev/null +++ b/Common/fix_json_converter.h @@ -0,0 +1,127 @@ +// Copyright (C) Mike Kipnis - DistributedATS +#pragma once + +#include +#include +#include +#include +#include + +namespace distributed_ats { +namespace fix_json { + +// Convert a FieldMap (Header, Body, or Trailer) to JSON object (recursive) +inline boost::json::object field_map_to_json(const FIX::FieldMap& map) +{ + boost::json::object json_obj; + + // --- Fields --- + for (FIX::FieldMap::iterator it = const_cast(map).begin(); + it != const_cast(map).end(); ++it) + { + const FIX::FieldBase& field = *it; + int tag = field.getTag(); + json_obj[std::to_string(tag)] = field.getString(); + } + + // --- Groups (repeating groups) --- + const FIX::FieldMap::Groups& group_maps = map.groups(); + for (auto group_it = group_maps.begin(); group_it != group_maps.end(); ++group_it) + { + int group_tag = group_it->first; + const std::vector& group_vector = group_it->second; + + boost::json::array json_group_array; + for (auto* group_map : group_vector) + { + json_group_array.push_back(field_map_to_json(*group_map)); + } + + json_obj[std::to_string(group_tag)] = std::move(json_group_array); + } + + return json_obj; +} + +// Convert entire FIX message to JSON +inline boost::json::object fix_to_json(const FIX::Message& message) +{ + boost::json::object json_root; + + json_root["Header"] = field_map_to_json(message.getHeader()); + json_root["Body"] = field_map_to_json(message); + json_root["Trailer"] = field_map_to_json(message.getTrailer()); + + return json_root; +} + +// Helper: convert JSON object to FieldMap (recursive) +inline void json_to_field_map(const boost::json::object& json_obj, FIX::FieldMap& map) +{ + for (auto& kv : json_obj) + { + const std::string& key = kv.key(); + const boost::json::value& value = kv.value(); + int tag = std::stoi(key); + + if (value.is_string()) + { + map.setField(FIX::StringField(tag, value.as_string().c_str())); + } + else if (value.is_array()) + { + const boost::json::array& arr = value.as_array(); + for (auto& item : arr) + { + if (!item.is_object()) continue; + FIX::Group group(tag, 0); // 0 used as placeholder delimiter + json_to_field_map(item.as_object(), group); + map.addGroup(tag, group); // FIX: addGroup(tag, group) + } + } + else + { + // Convert other JSON types to string + std::ostringstream oss; + oss << value; + map.setField(FIX::StringField(tag, oss.str())); + } + } +} + +// Convert JSON to FIX::Message +inline FIX::Message json_to_fix(const boost::json::object& json_msg) +{ + FIX::Message message; + + if (json_msg.if_contains("Header")) + json_to_field_map(json_msg.at("Header").as_object(), message.getHeader()); + + if (json_msg.if_contains("Body")) + json_to_field_map(json_msg.at("Body").as_object(), message); + + if (json_msg.if_contains("Trailer")) + json_to_field_map(json_msg.at("Trailer").as_object(), message.getTrailer()); + + return message; +} + +// Helper: JSON string → FIX::Message +inline FIX::Message json_to_fix(const std::string& json_str) +{ + boost::json::value parsed = boost::json::parse(json_str); + if (!parsed.is_object()) + throw std::runtime_error("Invalid JSON format for FIX message"); + + return json_to_fix(parsed.as_object()); +} + +// Helper: FIX::Message → JSON string +inline std::string fix_to_json_string(const FIX::Message& message) +{ + boost::json::object json_obj = fix_to_json(message); + return boost::json::serialize(json_obj); +} + +} // namespace fix_json +} // namespace distributed_ats diff --git a/MiscClients/cpp_ws_reactjs/oms/CMakeLists.txt b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/CMakeLists.txt similarity index 69% rename from MiscClients/cpp_ws_reactjs/oms/CMakeLists.txt rename to MiscClients/cpp_ws_reactjs/fix_ws_proxy/CMakeLists.txt index 35c4124..9337830 100644 --- a/MiscClients/cpp_ws_reactjs/oms/CMakeLists.txt +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.12.4) -project(oms) +project(fix_ws_proxy) message(STATUS "Current source dir: ${CMAKE_CURRENT_SOURCE_DIR}") @@ -16,15 +16,18 @@ link_directories(${LOG4CXX_LIBRARY_DIR}) include_directories(${Boost_INCLUDE_DIR}) link_directories(${Boost_LIBRARY_DIRS}) -file(GLOB OMS_SRC *.cpp) -add_executable(oms ${OMS_SRC}) +file(GLOB FIX_WS_SRC *.cpp) +add_executable(fix_ws_proxy ${FIX_WS_SRC}) -find_package(Boost REQUIRED COMPONENTS program_options) +find_package(Boost REQUIRED COMPONENTS program_options thread chrono json) include_directories(${Boost_INCLUDE_DIRS}) -target_link_libraries(oms +target_link_libraries(fix_ws_proxy PRIVATE quickfix log4cxx Boost::program_options + Boost::thread + Boost::json + Boost::chrono ) diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/FIX44.xml b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/FIX44.xml new file mode 100755 index 0000000..030c920 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/FIX44.xml @@ -0,0 +1,6594 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.cpp b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.cpp new file mode 100644 index 0000000..092e688 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.cpp @@ -0,0 +1,66 @@ +// Copyright (C) Mike Kipnis - DistributedATS +#include "fix_application.h" +#include "ws_server.h" + + +void fix_application::toAdmin(FIX::Message& message, const FIX::SessionID& sessionID) +{ + const FIX::Header& header = message.getHeader(); + + const FIX::MsgType& msgType = FIELD_GET_REF( header, MsgType ); + + if ( msgType == FIX::MsgType_Logon ) + { + if (auto session = _ws_session.lock()) { + auto ws_logon = session->get_pending_logon(); + + std::cout << std::endl << "Sending Logon : " << sessionID << "Pending Logon : " << ws_logon << std::endl; + + FIX::Username userName(sessionID.getSenderCompID()); + message.setField(userName); + + FIX::Password password; + ws_logon.getField(password); + + message.setField(password); + + std::cout << "Sending Logon : " << message.toString() << std::endl; + } + } else if ( msgType == FIX::MsgType_Logout ) + { + std::cout << "Logout : " << message << std::endl; + } +} + +void fix_application::fromAdmin(const FIX::Message& message, const FIX::SessionID& sessionId) throw(FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon) +{ + if (auto session = _ws_session.lock()) { + + auto json_obj = distributed_ats::fix_json::fix_to_json(message); + + json_obj["session_qualifier"] = sessionId.getSessionQualifier(); + json_obj["data_type"] = "FIX"; + json_obj["success"] = true; + + auto json_str = boost::json::serialize(json_obj); + + session->send_string(json_str); + } +} + + +void fix_application::fromApp(const FIX::Message& message, const FIX::SessionID& sessionId) throw(FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::UnsupportedMessageType) +{ + if (auto session = _ws_session.lock()) { + + auto json_obj = distributed_ats::fix_json::fix_to_json(message); + + json_obj["session_qualifier"] = sessionId.getSessionQualifier(); + json_obj["data_type"] = "FIX"; + json_obj["success"] = true; + + auto json_str = boost::json::serialize(json_obj); + + session->send_string(json_str); + } +} diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.h b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.h new file mode 100644 index 0000000..56dcf9b --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_application.h @@ -0,0 +1,48 @@ +// Copyright (C) Mike Kipnis - DistributedATS +#pragma once + +#include +#include +#include +#include + +class Session; + +class fix_application : public FIX::Application { + +public: + + fix_application(const std::weak_ptr& ws_session) : _ws_session(ws_session) + { + //std::cout << "Pending Logon : " << _ws_logon << std::endl; + } + + void onCreate(const FIX::SessionID& id) override { + std::cout << "Created: " << id << std::endl; + } + void onLogon(const FIX::SessionID& id) override { + std::cout << "Logon: " << id << std::endl; + } + void onLogout(const FIX::SessionID& id) override { + std::cout << "Logout: " << id << std::endl; + } + void toAdmin(FIX::Message& message, const FIX::SessionID& sessionID) override; + + void fromAdmin(const FIX::Message &, + const FIX::SessionID &) throw(FIX::FieldNotFound, + FIX::IncorrectDataFormat, + FIX::IncorrectTagValue, + FIX::RejectLogon) override; + void fromApp(const FIX::Message &message, + const FIX::SessionID &sessionID) throw(FIX::FieldNotFound, + FIX::IncorrectDataFormat, + FIX::IncorrectTagValue, + FIX::UnsupportedMessageType) override; + + + void toApp(FIX::Message&, const FIX::SessionID&) throw(FIX::DoNotSend) override {}; + +private: + std::weak_ptr _ws_session; +}; + diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_ws_proxy.cpp b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_ws_proxy.cpp new file mode 100644 index 0000000..28f96f5 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fix_ws_proxy.cpp @@ -0,0 +1,70 @@ +// Copyright (C) Mike Kipnis - DistributedATS + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fix_application.h" +#include "ws_server.h" + +using boost::asio::ip::tcp; +namespace asio = boost::asio; +namespace po = boost::program_options; + + +namespace asio = boost::asio; +namespace beast = boost::beast; +namespace http = beast::http; +namespace ws = beast::websocket; +using tcp = asio::ip::tcp; + + +int main(int argc, char** argv) { + try { + // Default values + auto const address = asio::ip::make_address("0.0.0.0"); + unsigned short port = 9002; + std::string fix_client_config; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("port,p", po::value(), "set WebSocket listening port") + ("fix_client_config,f", po::value(), "set FIX client configuration file"); + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + return 0; + } + + if (vm.count("port")) + port = vm["port"].as(); + + if (vm.count("fix_client_config")) + fix_client_config = vm["fix_client_config"].as(); + + std::cout << "Using port: " << port << "\n"; + if (!fix_client_config.empty()) + std::cout << "Using FIX client config file: " << fix_client_config << "\n"; + + + asio::io_context ioc{1}; // number of ws threads + std::make_shared(ioc, tcp::endpoint{address, port})->run(); + + std::cout << "WebSocket echo server listening on port " << port << "\n"; + ioc.run(); + } catch (const std::exception& ex) { + std::cerr << "Fatal: " << ex.what() << "\n"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fixproxy.ini b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fixproxy.ini new file mode 100644 index 0000000..79605fe --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/fixproxy.ini @@ -0,0 +1,17 @@ +[DEFAULT] +BeginString=FIX.4.4 +ConnectionType=initiator +ReconnectInterval=30 +FileStorePath=store +FileLogPath=log +StartTime=00:00:00 +EndTime=00:00:00 +ResetOnLogon=Y +ResetSeqNumFlag=Y +ResetOnDisconnect=Y + +TargetCompID=FIX_GWY_1 +SocketConnectHost=127.0.0.1 +SocketConnectPort=15001 +HeartBtInt=30 +DataDictionary=FIX44.xml diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.cpp b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.cpp new file mode 100644 index 0000000..3e9d37f --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.cpp @@ -0,0 +1,78 @@ +// Copyright (C) Mike Kipnis - DistributedATS + +#include "ws_server.h" +#include "fix_application.h" + +#include +#include +#include + +#include +#include +#include + + +void Session::on_read(beast::error_code ec, std::size_t bytes_transferred) { + + boost::shared_lock lock(rw_mutex_); + + boost::ignore_unused(bytes_transferred); + if (ec == ws::error::closed) return; + if (ec) { + std::cerr << "read error: " << ec.message() << "\n"; + return; + } + + auto message = beast::buffers_to_string(buffer_.data()); + auto fix_message = distributed_ats::fix_json::json_to_fix(message); + auto ws_session_id = fix_message.getSessionID(); + + FIX::SessionID qualified_id( + ws_session_id.getBeginString(), + ws_session_id.getSenderCompID(), + ws_session_id.getTargetCompID(), + fix_session_qualifier_ + ); + + boost::json::object replay; + if ( !FIX::Session::doesSessionExist(qualified_id) ) + { + settings_ = std::make_shared("/Users/mkipnis/Documents/oms/oms/fixproxy.ini"); + settings_->set(qualified_id, FIX::Dictionary()); + + pending_logon_ = fix_message; + + storeFactory_ = std::make_shared(); + logFactory_ = std::make_shared( *settings_ ); + + std::cout << "FIX Message : " << fix_message << std::endl; + + application_ = std::make_shared(shared_from_this()); + socket_initator_ = std::make_shared( *application_, *storeFactory_, *settings_, *logFactory_ ); + + socket_initator_->start(); + replay["token"] = fix_session_qualifier_; + replay["success"] = true; + } else { + + std::cout << "About to send: " << fix_message << std::endl; + replay["token"] = fix_session_qualifier_; + + bool success = FIX::Session::sendToTarget(fix_message, qualified_id); + if (!success) + std::cerr << "Failed to send FIX message to " << qualified_id << std::endl; + + replay["success"] = success; + + } + + replay["data_type"] = "WS"; + std::string replay_str = boost::json::serialize(replay); + + // Send over WebSocket + ws_.text(true); // ensure it's sent as text + ws_.async_write( + boost::asio::buffer(replay_str), + beast::bind_front_handler(&Session::on_write, shared_from_this()) + ); +} diff --git a/MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.h b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.h new file mode 100644 index 0000000..37e5244 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/fix_ws_proxy/ws_server.h @@ -0,0 +1,216 @@ +// Copyright (C) Mike Kipnis - DistributedATS + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace asio = boost::asio; +namespace beast = boost::beast; +namespace http = beast::http; +namespace ws = beast::websocket; +using tcp = asio::ip::tcp; + + +class fix_application; + +// A single WebSocket session (handles one client) +class Session : public std::enable_shared_from_this { +public: + explicit Session(tcp::socket socket) + : ws_(std::move(socket)) {} + + void start() { + // Accept the websocket handshake + ws_.async_accept( + beast::bind_front_handler(&Session::on_accept, shared_from_this())); + } + + ~Session() { + + boost::shared_lock lock(rw_mutex_); + try { + if (socket_initator_) { + socket_initator_->stop(); + socket_initator_.reset(); + } + } catch (std::exception const& e) { + std::cerr << "Exception in Session destructor: " << e.what() << std::endl; + } + } + +private: + ws::stream ws_; + beast::flat_buffer buffer_; + + std::string fix_session_qualifier_; + std::string ws_session_qualifier_; + + + std::shared_ptr settings_; + std::shared_ptr storeFactory_; + std::shared_ptr logFactory_; + + + std::shared_ptr application_; + std::shared_ptr socket_initator_; + + FIX::Message pending_logon_; + + boost::shared_mutex rw_mutex_; + + void on_accept(beast::error_code ec) { + if (ec) { + std::cerr << "accept error: " << ec.message() << "\n"; + return; + } + + auto& tcp_stream = ws_.next_layer(); + auto remote_endpoint = tcp_stream.remote_endpoint(); + auto local_endpoint = tcp_stream.local_endpoint(); + + ws_session_qualifier_ = + local_endpoint.address().to_string() + ":" + std::to_string(local_endpoint.port()) + + " <-> " + + remote_endpoint.address().to_string() + ":" + std::to_string(remote_endpoint.port()); + + boost::uuids::uuid token = boost::uuids::random_generator()(); + fix_session_qualifier_ = boost::uuids::to_string(token); + + do_read(); + } + + void do_read() { + buffer_.consume(buffer_.size()); // ensure buffer empty + ws_.async_read( + buffer_, + beast::bind_front_handler(&Session::on_read, shared_from_this())); + } + + void on_read(beast::error_code ec, std::size_t bytes_transferred); + + void on_write(beast::error_code ec, std::size_t bytes_transferred) { + boost::ignore_unused(bytes_transferred); + if (ec) { + std::cerr << "write error: " << ec.message() << "\n"; + return; + } + // Clear the buffer and read another message + buffer_.consume(buffer_.size()); + do_read(); + } + + void on_close() { + + boost::shared_lock lock(rw_mutex_); + + std::cout << "[WS] Connection closed: " << ws_session_qualifier_ << std::endl; + + // Gracefully stop the FIX initiator if running + if (socket_initator_) { + try { + socket_initator_->stop(); + socket_initator_.reset(); + } catch (std::exception const& e) { + std::cerr << "Error stopping FIX initiator: " << e.what() << std::endl; + } + } + } + +public: + + const FIX::Message& get_pending_logon() + { + return pending_logon_; + } + + void send_string(const std::string& message) { + + boost::unique_lock lock(rw_mutex_); + + asio::dispatch(ws_.get_executor(), [self = shared_from_this(), message]() { + try { + self->ws_.text(true); + self->ws_.write(boost::asio::buffer(message)); // synchronous write + } catch (const std::exception& e) { + std::cerr << "sync write error: " << e.what() << std::endl; + } + }); + + /* + auto self = shared_from_this(); // keep session alive during async write + ws_.text(true); // send as text + ws_.async_write( + boost::asio::buffer(message), + [self](beast::error_code ec, std::size_t bytes_transferred){ + boost::ignore_unused(bytes_transferred); + if (ec) { + std::cerr << "write error: " << ec.message() << "\n"; + } + } + );*/ + } +}; + + +// Accepts incoming connections and launches sessions +class Listener : public std::enable_shared_from_this { +public: + Listener(asio::io_context& ioc, tcp::endpoint endpoint) + : ioc_(ioc), acceptor_(ioc) { + beast::error_code ec; + + acceptor_.open(endpoint.protocol(), ec); + if (ec) throw beast::system_error(ec); + + acceptor_.set_option(asio::socket_base::reuse_address(true), ec); + if (ec) throw beast::system_error(ec); + + acceptor_.bind(endpoint, ec); + if (ec) throw beast::system_error(ec); + + acceptor_.listen(asio::socket_base::max_listen_connections, ec); + if (ec) throw beast::system_error(ec); + } + + void run() { + do_accept(); + } + +private: + asio::io_context& ioc_; + tcp::acceptor acceptor_; + + void do_accept() { + acceptor_.async_accept( + asio::make_strand(ioc_), + beast::bind_front_handler(&Listener::on_accept, shared_from_this())); + } + + void on_accept(beast::error_code ec, tcp::socket socket) { + if (ec) { + std::cerr << "accept failed: " << ec.message() << "\n"; + } else { + // Create and run session + std::make_shared(std::move(socket))->start(); + } + do_accept(); + } +}; + diff --git a/MiscClients/cpp_ws_reactjs/oms/oms.cpp b/MiscClients/cpp_ws_reactjs/oms/oms.cpp deleted file mode 100644 index 3dc2192..0000000 --- a/MiscClients/cpp_ws_reactjs/oms/oms.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main( int argc, char* argv[] ) -{ - return 0; -}; diff --git a/MiscClients/cpp_ws_reactjs/py_client/py_client.py b/MiscClients/cpp_ws_reactjs/py_client/py_client.py new file mode 100644 index 0000000..34376b3 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/py_client/py_client.py @@ -0,0 +1,145 @@ +# Copyright (C) Mike Kipnis - DistributedATS +import asyncio +import json +import argparse +from datetime import datetime, timezone + +import websockets + + +def compose_header(sender_comp_id: str, target_comp_id: str, msg_type: str): + return { + 'Header': { + '8': "FIX.4.4", + '35': msg_type, + '49': sender_comp_id, + '56': target_comp_id, + } + } + + +def compose_logon(sender_comp_id: str, target_comp_id: str): + logon = compose_header(sender_comp_id, target_comp_id, 'A') + logon['Body'] = {'553': sender_comp_id, '554': "TEST"} + return logon + + +def compose_security_list_request(sender_comp_id: str, target_comp_id: str): + req = compose_header(sender_comp_id, target_comp_id, 'x') + req['Body'] = {'320': 'RequestInstrList1', '559': 4} + return req + + +def compose_mass_order_status_request(sender_comp_id: str, target_comp_id: str): + req = compose_header(sender_comp_id, target_comp_id, 'AF') + req['Body'] = {'584': 'RequestMassOrdStatus1', '559': 7} + return req + + +def compose_market_data_request(sender_comp_id: str, target_comp_id: str, instruments: list): + req = compose_header(sender_comp_id, target_comp_id, 'V') + req['Body'] = { + '262': 'MarketDataSnapshot_1', + '263': 1, + '264': 0, + '146': [], + '267': [{"269": 0}, {"269": 1}], + } + + for instr in instruments: + req['Body']['146'].append({k: instr[k] for k in ["55", "207"] if k in instr}) + + return req + + +def compose_order_from_open_price(sender_comp_id: str, target_comp_id: str, snapshot: dict): + order = compose_header(sender_comp_id, target_comp_id, 'D') + order['Body'] = { + '11': f"{snapshot['Body']['55']}_1", + '55': snapshot['Body']['55'], + '207': snapshot['Body']['207'], + '40': 2, # Limit + '59': 0, # Time in force + '60': datetime.now(timezone.utc).strftime("%Y%m%d-%H:%M:%S.%f")[:-3], + } + + for entry in snapshot['Body'].get('268', []): + if entry.get('269') == '4': # Open price + order['Body'].update({'54': 1, '44': entry['270'], '38': 1000}) + break + + return order + + +def pretty_print(description: str, message: dict): + print(f"\n{description}:\n{json.dumps(message, indent=4)}") + + +async def run_fix_session(sender_comp_id: str, target_comp_id: str, ws_url: str): + async with websockets.connect(ws_url) as ws: + print(f"✅ Connected to {ws_url}") + + # Send logon + logon = compose_logon(sender_comp_id, target_comp_id) + await ws.send(json.dumps(logon)) + pretty_print("➡️ Sent Logon", logon) + + session_msg = await ws.recv() + pretty_print("⬅️ Session Qualifier", json.loads(session_msg)) + + try: + async for raw_msg in ws: + msg = json.loads(raw_msg) + + if msg.get("data_type") == "WS": + continue + + msg_type = msg.get('Header', {}).get('35') + + pretty_print("⬅️ Incoming Message", msg) + + if msg_type == 'A': # Logon Ack + req1 = compose_security_list_request(sender_comp_id, target_comp_id) + req2 = compose_mass_order_status_request(sender_comp_id, target_comp_id) + await ws.send(json.dumps(req1)) + await ws.send(json.dumps(req2)) + pretty_print("➡️ Sent Security List Request", req1) + pretty_print("➡️ Sent Mass Order Request", req2) + + elif msg_type == 'y': # Security List + instruments = msg['Body']['146'] + md_req = compose_market_data_request(sender_comp_id, target_comp_id, instruments) + await ws.send(json.dumps(md_req)) + pretty_print("➡️ Sent Market Data Request", md_req) + + elif msg_type == 'W': # Market Data Snapshot + pretty_print("📈 Market Data Snapshot", msg) + order = compose_order_from_open_price(sender_comp_id, target_comp_id, msg) + await ws.send(json.dumps(order)) + pretty_print("➡️ Sent Order", order) + + elif msg_type == 'X': + pretty_print("📊 Market Data Update", msg) + + elif msg_type == '8': + pretty_print("🧾 Execution Report", msg) + + except websockets.ConnectionClosed: + print("❌ Connection closed.") + except Exception as e: + print(f"⚠️ Error: {e}") + + +def main(): + parser = argparse.ArgumentParser(description="FIX over WebSocket client") + parser.add_argument("--sender", required=True, help="SenderCompID (e.g., TRADER_1)") + parser.add_argument("--target", required=True, help="TargetCompID (e.g., FIX_GWY_1)") + parser.add_argument("--url", required=True, help="WebSocket URL (e.g., ws://localhost:9002)") + + args = parser.parse_args() + + asyncio.run(run_fix_session(args.sender, args.target, args.url)) + + +if __name__ == "__main__": + main() diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/package.json b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/package.json new file mode 100644 index 0000000..c05b8ad --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/package.json @@ -0,0 +1,43 @@ +{ + "name": "web-trader", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.2.0", + "@testing-library/user-event": "^13.5.0", + "ag-grid-community": "^27.3.0", + "ag-grid-react": "^27.3.0", + "bootstrap": "^5.2.2", + "rc-input-number": "^7.3.9", + "react": "^18.1.0", + "react-bootstrap": "^2.6.0", + "react-dom": "^18.1.0", + "react-scripts": "5.0.1", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/favicon.ico b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/index.html b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/index.html new file mode 100644 index 0000000..7478acb --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + DistributedATS - WebTrader + + + +
+ + + diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/logo192.png b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/manifest.json b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/robots.txt b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.css b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.css new file mode 100644 index 0000000..6441f6f --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.css @@ -0,0 +1,102 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.contaner +{ + margin: 0; + font-family: Inter, sans-serif; + height: 100vh; + background-color: #282D35; + color: white; + max-width: 100%; + width: 80%; +} + +.App-Row { + margin: auto; + width: 100%; + height: 30%; + display: inline-block; + vertical-align: top; +} + +.App-Column +{ + vertical-align: top; + width: 50%; + display: inline-block; + margin: auto; +} + +* { + box-sizing: border-box; +} + +.body { + margin: 10; + font-family: Inter, sans-serif; + height: 100vh; + background-color: #282D35; +} + +nav { + display: flex; + align-items: center; + background-color: #21222A; + height: 70px; + padding: 10px 10px; +} + + +.nav--logo_text, .nav--title { + margin: 20; +} + +.nav--logo_text { + margin-right: auto; + /* color: #61DAFB;*/ + color : white; + font-weight: 700; + font-size: 22px; +} + +.nav--title { + color: #DEEBF8; + font-weight: 600; +} diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.js new file mode 100644 index 0000000..e27334b --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.js @@ -0,0 +1,236 @@ + +// DistributedATS - Mike Kipnis (c) 2022 +import logo from './logo.svg'; +import './App.css'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import Login from './components/Login'; +import PositionsAndMarketData from './components/PositionsAndMarketData'; +import Ticket from './components/Ticket'; +import History from './components/History'; +import PriceLevels from './components/PriceLevels'; +import SessionStateWrapper from './components/SessionStateWrapper'; +import React from 'react'; +import { useEffect, useState, useMemo } from 'react'; +import { Container, Row, Col } from 'react-bootstrap/'; + +import helpers from './components/helpers'; + +import WebSocketDemo from './components/WebSocketDemo'; + + +const { forwardRef, useRef, useImperativeHandle } = React; + +function App() +{ + const ticketRef = React.useRef(); + const histRef = React.useRef(); + const marketDataAndPositionsRef = React.useRef(); + + const url = window.location.href; + //const url = 'http://localhost:8080'; + + const last_sequence_number = useRef(0); // sequence number between front-end and rest controller + const last_session_state = useRef(null); + const current_ticket = useRef(null); + + + const [sessionToken, setSessionToken] = useState(null); + const [sessionState, setSessionState] = useState(null); + const [ticketState, setTicketState] = useState({instrumentName:"N/A",price:0,quantity:0,username:"",token:""}); + const [loginState, setLoginState] = useState({sessionStateCode:0,text:"Please login"}); + const [blotterData, setBlotterData] = useState([]); + const [orderCancelData, setOrderCancelData] = useState({}); + const [histData, setHistData] = useState([]); + const [priceLevels, setPriceLevels] = useState([]); + + + const Logon_callback = (logon_value) => + { + const requestOptionsResults = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logon_value) }; + fetch(url + '/Login', requestOptionsResults) .then(res => res.json()) + .then(result => setSessionToken(result)) + .catch(err => { + + var invalid_state = {}; + invalid_state["text"] = 'Unable to connect to the server'; + setLoginState(invalid_state); + + }); + } + + const Populate_ticket = ( ticket_data ) => + { + ticket_data.url = url; + + if ( last_session_state.current !== null ) + { + ticket_data.token = last_session_state.current.token; + ticket_data.username = last_session_state.current.username; + + current_ticket.current = ticket_data; + } + + setTicketState( ticket_data ); + + if ( ticket_data.instrumentData != null ) + { + var price_levels = helpers.get_price_levels(ticket_data.instrumentData); + setPriceLevels(price_levels); + } + } + + const Submit_cancel = (cancel_order) => + { + var cancel_out = {}; + + cancel_out["token"] = last_session_state.current.token; + cancel_out["username"] = last_session_state.current.username; + cancel_out["side"] = cancel_order.side; + cancel_out["orderKey"] = cancel_order.orderKey; + cancel_out["securityExchange"] = cancel_order.securityExchange; + cancel_out["symbol"] = cancel_order.symbol; + + const requestOptionsResults = { method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(cancel_out) }; + fetch(url + '/CancelOrder', requestOptionsResults) .then(res => res.json()) + .then(result => setOrderCancelData(result)) + .catch(err => {console.log(err)}); + } + + useEffect(() => { + console.log("Hist Data : " + histData ); + },[histData]); + + useEffect(() => { + console.log("Order Cancel Data : " + orderCancelData );; + },[orderCancelData]); + + useEffect(() => { + + if ( sessionToken !== null ) + { + console.log("sessionToken : " + sessionToken.token); + + var session_state_request = {}; + session_state_request["token"] = sessionToken.Token; + session_state_request["stateMask"] = 0; + session_state_request["maxOrderSequenceNumber"] = 0; + session_state_request["marketDataSequenceNumber"] = 0; + + ticketState.username = sessionToken.username; + ticketState.token = sessionToken.Token;; + + var instrument = {}; + + var instrument_array = []; + session_state_request["activeSecurityList"] = instrument_array; + + const requestOptionsResults = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(session_state_request) }; + fetch(url + '/SessionState', requestOptionsResults) .then(res => res.json()) + .then(result => setSessionState(result)) + .catch(err => { + var invalid_state = {}; + invalid_state["text"] = 'Disconnected from the server'; + setLoginState(invalid_state); + setSessionState(null); + }); + } + + }, [sessionToken]); + + useEffect(() => { + + if ( sessionState !== null ) + { + last_session_state.current = sessionState; + + var session_state_request = {}; + session_state_request["token"] = sessionToken.Token; + + + const session_state_wrapper = new SessionStateWrapper(sessionState, session_state_request); + + setLoginState(session_state_wrapper.get_logon_state()); + + if ( sessionState.sessionState == 1 ) // This session is active + { + var blotter_data = session_state_wrapper.get_market_data_and_positions(); + + setBlotterData(blotter_data); + + marketDataAndPositionsRef.current.update_data(); + + setHistData(session_state_wrapper.get_hist_data(histData, last_sequence_number)); + + if ( current_ticket.current != null ) + { + + var instrument_data = blotter_data[current_ticket.current.instrumentName]; + if ( instrument_data != null ) + { + var price_levels = helpers.get_price_levels(instrument_data); + setPriceLevels(price_levels); + } + } + + } + + if ( sessionState.sessionState != 3 ) + { + const intervalId = setInterval(() => { + const requestOptionsResults = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(session_state_request) }; + fetch(url + '/SessionState', requestOptionsResults) .then(res => res.json()) + .then(result => setSessionState(result)) + .catch(err => { + var invalid_state = {}; + invalid_state["text"] = 'Disconnected from the server'; + setLoginState(invalid_state); + setSessionState(null); + }); + }, 1000) + + return () => { + clearInterval(intervalId); + } + } else { + + } + } else { + console.log("Session State is Null"); + } + + }, [sessionState]); + + return ( ); + + /* +
+
+ + +
+ + +
+ +
+
+ Click on the instrument to trade. +
+ + + + +
+
+ +
+
+
+
+
*/ +} + +export default App; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.test.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.test.js new file mode 100644 index 0000000..1f03afe --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/History.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/History.js new file mode 100644 index 0000000..e49dafe --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/History.js @@ -0,0 +1,119 @@ +// DistributedATS - Mike Kipnis (c) 2022 +import { useCallback, useMemo, useEffect, useState } from 'react'; +import React from 'react'; +import {AgGridColumn, AgGridReact} from 'ag-grid-react'; +import helpers from './helpers'; + +import 'ag-grid-community/dist/styles/ag-grid.css'; +import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css'; + +const { forwardRef, useRef, useImperativeHandle } = React; + +const History = React.forwardRef ((props, ref) => { + + + const HistMessageRenderer = (value) => { + + const invokeCancelMethod = () => { + props.orderCancelCallback(value); + }; + + return ( + + + + ); +}; + + const [histData, setHistData] = useState({}); + + const gridRef = useRef(); + + useImperativeHandle(ref, () => ({ + + update_history() + { + gridRef.current.api.refreshCells(); + } + + })); + + function price_formatter(params) + { + if (params.value == 0 ) + { + return ""; + } else { + return helpers.get_display_price(params.value, params.data.instrument.tickSize); + } + + }; + +const [columnDefs, setColumnDefs] = useState([ + { headerName: 'Exec.Report Timestamp', field: 'lastUpdateTime', sortable: true,flex: 2, filter: 'agTextColumnFilter', }, + { headerName: 'Last.ExecReportID', field: 'lastExecutionReportId', sortable: true, flex: 2, filter: 'agTextColumnFilter', }, + { headerName: 'OrderStatus', field: 'status', sortable: true, flex: 2 }, + { headerName: 'SecurityExchange', field: 'securityExchange', sortable: true, flex: 2 }, + { headerName: 'Symbol', field: 'symbol', sortable: true, flex: 2 }, + { headerName: 'OrderKey', field: 'orderKey', sortable: true, flex: 2 }, + { headerName: 'Price', field: 'price', sortable: true, flex: 2, valueFormatter:price_formatter }, + { headerName: 'StopPx', field: 'stop_price', sortable: true, flex: 2, valueFormatter:price_formatter }, + { headerName: 'Quantity', field: 'quantity', sortable: true, flex: 2 }, + { headerName: 'Side', field: 'side', sortable: true, flex: 2 }, + { headerName: 'FilledAvgPrice', field: 'filled_avg_price', sortable: true, flex: 2, valueFormatter:price_formatter }, + { headerName: 'FilledQty', field: 'filled_quantity', sortable: true, flex: 2 }, + { headerName: 'All or None', field: 'allOrNone', sortable: true, flex: 2 }, + { headerName: 'Condition', field: 'orderCondition', sortable: true, flex: 2 }, + { headerName: 'OrdType', field: 'orderType', sortable: true, flex: 2 }, + + + + { + headerName: 'Actions', + field: 'value', + cellRenderer: params => { + if ( params.data.status == "New" || params.data.status == "Partially Filled") + return HistMessageRenderer(params.data); + else + return null; + }, + colId: 'params', + editable: false, + minWidth: 150, + }, + ]); + + const gridOptions = { + + rowSelection: 'single', + + onRowDataChanged: event => { + var defaultSortModel = [ + { colId: 'lastUpdateTime', sort: 'desc', sortIndex: 0 } + ]; + gridRef.current.columnApi.applyColumnState({ state: defaultSortModel }); + }, + + } + + + const onBtnExport = useCallback(() => { + + props.dataExportCallback(gridRef); + }, []); + + return ( +
+
+ + +
+
+ ); +}); + +export default History; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Login.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Login.js new file mode 100644 index 0000000..98fa1fc --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Login.js @@ -0,0 +1,101 @@ +// DistributedATS - Mike Kipnis (c) 2022 + +import React from 'react'; +import { useEffect, useState } from 'react'; + +import { Container, Row, Col, Button, Form } from 'react-bootstrap/'; + + +const { forwardRef, useRef, useImperativeHandle } = React; + +const Login = React.forwardRef ((props, ref) => { + + + const [name, setName] = useState(''); + const [password, setPassword] = useState(''); + + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(null); + const [sessionToken, setSessionToken] = useState(null); + const [sessionState, setSessionState] = useState(null); + + + const handleName = (e) => { + setName(e.target.value); + setSubmitted(false); + }; + + const handlePassword = (e) => { + setPassword(e.target.value); + setSubmitted(false); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + if (name === '' || password === '') { + setError(true); + } else { + + var logon_value = {}; + logon_value["username"] = name; + logon_value["password"] = password; + + props.logonCallback(logon_value); + + setSubmitted(true); + setError(false); + } + }; + const successMessage = () => { + + return ( +
+ User {name} successfully registered!! + +
+ ); + + }; + + // Showing error message if error is true + const infoMessage = () => { + return ( +
+ {props.loginState.text} +
+ ); + }; + + return ( + + +

DistributedATS - WebTrader

+
{infoMessage()}
+ + +
+
+ + + + + + + +
+
+ +
+ ); +}); + +export default Login; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PositionsAndMarketData.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PositionsAndMarketData.js new file mode 100644 index 0000000..cc0d74d --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PositionsAndMarketData.js @@ -0,0 +1,171 @@ +// DistributedATS - Mike Kipnis (c) 2022 + +import { useCallback, useMemo, useEffect, useState } from 'react'; +import React from 'react'; +import {AgGridColumn, AgGridReact} from 'ag-grid-react'; + +import 'ag-grid-community/dist/styles/ag-grid.css'; +import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css'; + +import helpers from './helpers'; + +const { forwardRef, useRef, useImperativeHandle } = React; + +const Blotter = React.forwardRef ((props, ref) => { + + const [ticketData, setTicketData] = useState({}); + const [blotterData, setBlotterData] = useState([]); + + const gridRef = useRef(); + + function price_formatter(params) + { + if (params.value == 0 ) + { + return ""; + } else { + + return helpers.get_display_price(params.value, params.data.tickSize); + } + + }; + + function size_formatter(params) + { + if (params.value == 0 ) + { + return ""; + } + }; + + + useImperativeHandle(ref, () => ({ + update_data() { + + gridRef.current.api.forEachNode(node => + { + if (node.data == null ) + return; + + var instrument_name = node.data.instrumentName; + var last_market_data_sequence_number = node.data.marketDataSequenceNumber; + + var instrument_update = props.blotterData[instrument_name]; + + if ( instrument_update!== undefined && instrument_update!== null ) + { + if ( instrument_update.marketDataSequenceNumber > last_market_data_sequence_number ) + { + node.setData(instrument_update); + } + } + + + } + ); + + if (gridRef.current.api.getDisplayedRowCount() === 0 ) + { + setBlotterData(Object.values(props.blotterData)); + } + //gridRef.current.api.refreshCells(); + } + })); + +const [columnDefs, setColumnDefs] = useState([ + { headerName: 'symbol', field: 'symbol', filter: 'agTextColumnFilter', cellStyle: {'textAlign': 'left'}, sortable: true, }, + { headerName: 'Position', field: 'position', flex: 2, filter: 'agTextColumnFilter', }, + { headerName: 'VWAP', field: 'vwap', sortable: true, flex: 2,valueFormatter:price_formatter }, + { headerName: 'PNL', field: 'pnl', sortable: true, flex: 2,valueFormatter:price_formatter }, + { headerName: 'BidPrice', field: 'bid_price', sortable: true, flex: 2, valueFormatter:price_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'AskPrice', field: 'ask_price', sortable: true, flex: 2, valueFormatter:price_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'BidSize', field: 'bid_size', sortable: true, flex: 2, valueFormatter:size_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'AskSize', field: 'ask_size', sortable: true, flex: 2, valueFormatter:size_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'Volume', field: 'volume', sortable: true, flex: 2, valueFormatter:size_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'OpenPrice', field: 'openPrice', sortable: true, flex: 2 , valueFormatter:price_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'LastPrice', field: 'lastTradedPrice', sortable: true, flex: 2 , valueFormatter:price_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'PriceChange', field: 'priceChange', sortable: true, flex: 2 , valueFormatter:price_formatter, cellStyle: {'textAlign': 'right'}}, + { headerName: 'MaturityDate', field: 'maturityDate', sortable: true, flex: 2 , hide:true }, + { headerName: 'TickSize', field: 'tickSize', sortable: true, flex: 2, hide:true }, + + + ]); + + const gridOptions = { + rowSelection: 'single', + onRowClicked: event => { + + props.ticketState.instrumentName = event.data.instrumentName; + + if ( event.data.lastTradedPrice != 0 ) + props.ticketState.price = event.data.lastTradedPrice; + else + props.ticketState.price = event.data.openPrice; + + props.ticketState.price = props.ticketState.price / event.data.tickSize; + + props.ticketState.quantity = 100; + props.ticketState.securityExchange = event.data.securityExchange; + props.ticketState.symbol = event.data.symbol; + + /*let price_levels = []; + + for (let i = 0; i < 5; i++) + { + var level = {"bidPrice":0,"askPrice":0,"bidSize":0,"askSize":0}; + + if (event.data.bidSide[i]!=null) + { + level["bidPrice"] = event.data.bidSide[i].price; + level["bidSize"] = event.data.bidSide[i].size; + } + + if (event.data.askSide[i]!=null) + { + level["askPrice"] = event.data.askSide[i].price; + level["askSize"] = event.data.askSide[i].size; + } + + price_levels.push(level); + }*/ + + //props.ticketState.priceLevels = price_levels; + props.ticketState.instrumentData = event.data; + props.ticketState.tickSize = event.data.tickSize; + + props.marketDataCallback(props.ticketState); + }, + + onColumnResized: event => {}, + + onRowDataChanged: event => { + var defaultSortModel = [ + { colId: 'maturityDate', sort: 'asc', sortIndex: 0 } + ]; + gridRef.current.columnApi.applyColumnState({ state: defaultSortModel }); + }, + + getRowHeight: (params) => 25 + } + + useEffect(() => { + + props.marketDataCallback(ticketData); + +}, [ticketData]); + + const onBtnExport = useCallback(() => { + + props.dataExportCallback(gridRef); + }, []); + + return ( +
+ + +
+ ); +}); + +export default Blotter; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PriceLevels.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PriceLevels.js new file mode 100644 index 0000000..f7be31e --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/PriceLevels.js @@ -0,0 +1,68 @@ +// DistributedATS - Mike Kipnis (c) 2022 +import { useCallback, useMemo, useEffect, useState } from 'react'; +import React from 'react'; +import {AgGridColumn, AgGridReact} from 'ag-grid-react'; + +import 'ag-grid-community/dist/styles/ag-grid.css'; +import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css'; + +import helpers from './helpers'; + +const { forwardRef, useRef, useImperativeHandle } = React; + +const PriceLevels = React.forwardRef ((props, ref) => { + + + const [priceLevels, setPriceLevels] = useState({}); + + const gridRef = useRef(); + + function price_formatter(params) + { + if (params.value == 0 ) + { + return ""; + } else { + + return helpers.get_display_price(params.value, props.ticketState.tickSize); + } + + }; + + function size_formatter(params) + { + if (params.value == 0 ) + { + return ""; + } + }; + + + + useImperativeHandle(ref, () => ({ + + update_history() + { + gridRef.current.api.refreshCells(); + } + + })); + +const [columnDefs, setColumnDefs] = useState([ + { headerName: 'BidPrice', field: 'bidPrice', sortable: false,flex: 2, valueFormatter:price_formatter }, + { headerName: 'AskPrice', field: 'askPrice', sortable: false, flex: 2, valueFormatter:price_formatter}, + { headerName: 'BidSize', field: 'bidSize', sortable: true, flex: 2, valueFormatter:size_formatter }, + { headerName: 'AskSize', field: 'askSize', sortable: true, flex: 2, valueFormatter:size_formatter }, + ]); + + + return ( +
+ + +
+ ); +}); + +export default PriceLevels; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/SessionStateWrapper.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/SessionStateWrapper.js new file mode 100644 index 0000000..f4b32d8 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/SessionStateWrapper.js @@ -0,0 +1,165 @@ +// DistributedATS - Mike Kipnis (c) 2022 + +export const STATE_UNABLE_TO_CREATE_SESSION = -1; +export const STATE_PENDING_LOGON = 0; +export const STATE_SUCCESSFUL_LOGIN = 1; +export const STATE_LOGGED_OUT = 3; + + +export const LOGON_STATE_BIT = 0; +export const SECURITY_LIST_BIT = 1; +export const MARKET_DATA_BIT = 2; +export const ORDERS_DATA_BIT = 4; + +class SessionStateWrapper +{ + constructor(session_state, session_state_request) { + + console.log(session_state); + + this.session_state = session_state; + this.session_state_request = session_state_request; + this.login_state = {}; + + if ( this.session_state.sessionState == STATE_PENDING_LOGON ) + { + this.process_pending_login_state(); + } else if ( this.session_state.sessionState == STATE_LOGGED_OUT ) + { + this.process_logout_state(); + } else if ( this.session_state.sessionState == STATE_SUCCESSFUL_LOGIN ) + { + this.process_state_successful_logon(); + } + } + + process_pending_login_state() + { + this.stateMask = 0; + this.stateMask |= 1 << LOGON_STATE_BIT; + this.session_state_request["stateMask"] = this.stateMask; + var instrument_array = []; + this.session_state_request["activeSecurityList"] = instrument_array; + this.login_state = {text:"Pending Login ... "}; + } + + process_state_successful_logon() + { + this.login_state = {text:"Ready to trade"}; + + if ( this.session_state.activeSecurityList == null || this.session_state.activeSecurityList.length == 0 ) + { + this.stateMask = 0; + this.stateMask |= 1 << SECURITY_LIST_BIT; + this.session_state_request["stateMask"] = this.stateMask; + } else { + this.stateMask = 0; + this.stateMask |= 1 << MARKET_DATA_BIT; + this.stateMask |= 1 << ORDERS_DATA_BIT; + this.session_state_request["stateMask"] = this.stateMask; + this.session_state_request["activeSecurityList"] = this.session_state.activeSecurityList; + + + this.session_state_request.marketDataSequenceNumber = this.session_state.marketDataSequenceNumber; + } + } + + get_hist_data(histData, last_sequence_number) + { + for (const [index, order] of Object.entries(this.session_state.orders)) + { + last_sequence_number.current = order.sequenceNumber; + + order["orderKey"] = order.orderKey.orderKey; + order["securityExchange"] = order.instrument.securityExchange; + order["symbol"] = order.instrument.symbol; + + histData[order["orderKey"]] = order; + } + + this.session_state_request.maxOrderSequenceNumber = last_sequence_number.current; + + return histData; + } + + process_logout_state() + { + this.login_state = {text:"Disconnected : " + this.session_state.sessionStateText} + } + + get_logon_state() + { + return this.login_state; + } + + get_session_state_request() + { + return this.session_state_request; + } + + get_market_data_and_positions() + { + var active_instruments = {}; + + if ( this.session_state.activeSecurityList == null || this.session_state.activeSecurityList.length == 0 ) + return active_instruments; + + for (const [index, active_instrument] of Object.entries(this.session_state.activeSecurityList)) + { + var market_data_entry = this.session_state.instrumentMarketDataSnapshot[active_instrument.instrumentName]; + + if ( market_data_entry !== undefined ) + { + market_data_entry["instrumentName"] = active_instrument.instrumentName; + market_data_entry["securityExchange"] = active_instrument.securityExchange; + market_data_entry["symbol"] = active_instrument.symbol; + market_data_entry["maturityDate"] = active_instrument.maturityDate; + market_data_entry["tickSize"] = active_instrument.tickSize; + + if ( Object.values(market_data_entry.bidSide).length > 0 ) + { + market_data_entry["bid_price"] = market_data_entry.bidSide[0].price; + market_data_entry["bid_size"] = market_data_entry.bidSide[0].size; + } + + if ( Object.values(market_data_entry.askSide).length > 0 ) + { + market_data_entry["ask_price"] = market_data_entry.askSide[0].price; + market_data_entry["ask_size"] = market_data_entry.askSide[0].size; + } + + market_data_entry["volume"] = market_data_entry.volume; + market_data_entry["lastTradedPrice"] = market_data_entry.lastTradedPrice; + market_data_entry["openPrice"] = market_data_entry.openPrice; + if ( market_data_entry.lastTradedPrice != 0 && market_data_entry.openPrice!=0 ) + market_data_entry["priceChange"] = market_data_entry.lastTradedPrice-market_data_entry.openPrice; + else + market_data_entry["priceChange"] = 0; + + if ( this.session_state.positionsMap[active_instrument.instrumentName] !== undefined ) + { + var position_data = this.session_state.positionsMap[active_instrument.instrumentName]; + market_data_entry["position"] = position_data.position; + market_data_entry["vwap"] = position_data.vwap; + + if ( position_data.position != 0 ) + { + market_data_entry["pnl"] = ( market_data_entry["lastTradedPrice"] - market_data_entry["vwap"] ) * + market_data_entry["position"]; + } else { + market_data_entry["pnl"] = position_data.sell_amt * position_data.sell_avg_price - + position_data.buy_amt * position_data.buy_avg_price; + } + } + } + + active_instruments[active_instrument.instrumentName] = market_data_entry; + + } + + return active_instruments; + } + +} + +export default SessionStateWrapper; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Ticket.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Ticket.js new file mode 100644 index 0000000..0915f81 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/Ticket.js @@ -0,0 +1,200 @@ +// DistributedATS - Mike Kipnis (c) 2022 +import React from 'react'; +import { useEffect, useState } from 'react'; +import { Container, Row, Col, Button, Form } from 'react-bootstrap/'; +import PriceLevels from './PriceLevels'; + + +const { forwardRef, useRef, useImperativeHandle } = React; + + +const Ticket = React.forwardRef ((props, ref) => { + + const [qty, setQty] = useState(1); + const [orderType, setOrderType] = useState('Limit'); + const [allOrNone, setAllOrNone] = useState(false); + const [orderCondition, setOrderCondition] = useState('Day'); + const [size, setSize] = useState(null); + const [orderSubmitResults, setOrderSubmitResults] = useState({}); + const [cancelAllResults, setCancelAllResults] = useState({}); + const [isTicketing, setIsTicketing] = useState(false); + + const [ticketPrice, setTicketPrice] = useState(0); + const [ticketStopPrice, setTicketStopPrice] = useState(0); + const [ticketSize, setTicketSize] = useState(0); + + const priceLevelsRef = React.useRef(); + + function submit_buy( e ) + { + e.preventDefault(); + props.ticketState.side = "BUY"; + Submit_order(props.ticketState); + } + + function submit_sell( e ) + { + e.preventDefault(); + props.ticketState.side = "SELL"; + Submit_order(props.ticketState); + } + + const Submit_order = ( trade ) => + { + var ticket = {}; + + setIsTicketing(true); + + ticket["symbol"] = trade.symbol; + ticket["securityExchange"] = trade.securityExchange; + ticket["quantity"] = ticketSize; + ticket["price_in_ticks"] = Math.round(ticketPrice * props.ticketState.tickSize); + ticket["stop_price_in_ticks"] = Math.round(ticketStopPrice * props.ticketState.tickSize); + ticket["side"] = trade.side; + ticket["order_type"] = orderType; + ticket["order_condition"] = orderCondition; + ticket["all_or_none"] = allOrNone; + ticket["username"] = trade.username; + ticket["token"] = trade.token; + + const requestOptionsResults = { method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(ticket) }; + fetch(trade.url + '/SubmitOrder', requestOptionsResults) + .then(res => res.json()) + .then(result => setOrderSubmitResults(result)); + } + + + const handleCancellAll = (e) => { + e.preventDefault(); + + setIsTicketing(true); + + var cancel_all = {}; + + cancel_all["username"] = props.ticketState.username; + cancel_all["token"] = props.ticketState.token; + + const requestOptionsResults = { method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(cancel_all) }; + fetch(props.ticketState.url + '/CancelAll', requestOptionsResults) + .then(res => res.json()) + .then(result => setCancelAllResults(result)); + }; + + useEffect(() => + { + setTicketPrice(props.ticketState.price); + setTicketStopPrice(props.ticketState.price); + setTicketSize(props.ticketState.quantity); + }, [props.ticketState.price]); + + useEffect(() => { + setIsTicketing(false); + console.log("Order Submit Results : " + orderSubmitResults ); + },[orderSubmitResults]); + + useEffect(() => { + setIsTicketing(false); + console.log("Cancel All Results : " + cancelAllResults );; + },[cancelAllResults]); + + return ( + + + +
Trade : {props.ticketState.instrumentName}
+ +
+ + + + +
+
+
+ + + + + + +
+ { + setTicketPrice(value.target.value); + } }/> +
+ + + { setTicketSize(value.target.value); } }/> + +
+ + + + + + + + + + + + + + + + + + +
+ { setTicketStopPrice(value.target.value); } }/> +
+ + + setAllOrNone(event.currentTarget.checked)}/> + +
+ + + + + + + + + + + +
+
+
+
+
+ ); +}); + +export default Ticket; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/helpers.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/helpers.js new file mode 100644 index 0000000..1c992c6 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/components/helpers.js @@ -0,0 +1,60 @@ +const helpers = { + + dec_to_tic: function(dec_price) + { + var is_negative = ( dec_price < 0 ? true : false); + + dec_price = Math.abs(dec_price); + + var d_handle = dec_price|0; + var fraction_1 = dec_price - d_handle; + var f_handle = fraction_1*32|0; + var fraction_2 = (fraction_1-f_handle/32)*256|0; + + var price_str = d_handle.toString() + "-" + f_handle.toString().padStart(2,'0') + fraction_2.toString(); + + + return (is_negative == true ? "(" + price_str + ")" : price_str ); + }, + + get_display_price: function(price, tick_size) + { + if (tick_size == 256 ) + { + return helpers.dec_to_tic(price/tick_size); + } else { + if ( price != null ) + { + return price/tick_size; + } + } + }, + + get_price_levels: function(instrument_data) + { + let price_levels = []; + + for (let i = 0; i < 5; i++) + { + var level = {"bidPrice":0,"askPrice":0,"bidSize":0,"askSize":0}; + + if (instrument_data.bidSide[i]!=null) + { + level["bidPrice"] = instrument_data.bidSide[i].price; + level["bidSize"] = instrument_data.bidSide[i].size; + } + + if (instrument_data.askSide[i]!=null) + { + level["askPrice"] = instrument_data.askSide[i].price; + level["askSize"] = instrument_data.askSide[i].size; + } + + price_levels.push(level); + } + + return price_levels; + } +} + +export default helpers; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.css b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.js new file mode 100644 index 0000000..d563c0f --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/logo.svg b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/reportWebVitals.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/setupTests.js b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/MiscClients/cpp_ws_reactjs/webtrader_reactjs_ws/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/build_with_cmake.sh b/build_with_cmake.sh index 724ff5f..5ee015d 100755 --- a/build_with_cmake.sh +++ b/build_with_cmake.sh @@ -6,7 +6,7 @@ set -ex # Print each command and exit on error OS="$(uname)" if [[ "$OS" == "Darwin" ]]; then LIB_PATH_VAR="DYLD_LIBRARY_PATH" -# CMAKE_FLAGS="-G Xcode" + #CMAKE_FLAGS="-G Xcode" else LIB_PATH_VAR="LD_LIBRARY_PATH" fi