diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index a35d127c..ca03e19f 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -3,17 +3,55 @@ version: '3' services: - sandbox: + sandbox1: image: nutc-exchange build: context: ../.. dockerfile: exchange/docker/sandbox/Dockerfile - args: - firebase_emulator: "false" restart: unless-stopped environment: - NUTC_EXPOSE_METRICS=1 - command: ["--sandbox"] + - NUTC_ALGO_ENDPOINT=http://webserver:16124/algorithms/HFT + - NUTC_ID=0 + depends_on: + - webserver + # sandbox2: + # image: nutc-exchange + # build: + # context: ../.. + # dockerfile: exchange/docker/sandbox/Dockerfile + # restart: unless-stopped + # environment: + # - NUTC_EXPOSE_METRICS=1 + # - NUTC_ALGO_ENDPOINT=http://webserver:16124/algorithms/HFT + # - NUTC_ID=1 + # depends_on: + # - webserver + # sandbox3: + # image: nutc-exchange + # build: + # context: ../.. + # dockerfile: exchange/docker/sandbox/Dockerfile + # restart: unless-stopped + # environment: + # - NUTC_EXPOSE_METRICS=1 + # - NUTC_ALGO_ENDPOINT=http://webserver:16124/algorithms/HFT + # - NUTC_ID=2 + # depends_on: + # - webserver + sandbox4: + image: nutc-exchange + build: + context: ../.. + dockerfile: exchange/docker/sandbox/Dockerfile + restart: unless-stopped + environment: + - NUTC_EXPOSE_METRICS=1 + - NUTC_ALGO_ENDPOINT=http://webserver:16124/algorithms/HFT + - NUTC_ID=3 + depends_on: + - webserver + # command: ["--sandbox"] #port: 16124 webserver: @@ -23,15 +61,6 @@ services: dockerfile: webserver/Dockerfile restart: unless-stopped - linter: - image: nutc-linter - restart: unless-stopped - build: - context: ../.. - dockerfile: exchange/docker/linter/Dockerfile - args: - firebase_emulator: "false" - # Exposed on port 9000 prometheus: image: prom/prometheus @@ -42,16 +71,6 @@ services: - '--storage.tsdb.retention.time=12h' restart: unless-stopped - reverse-proxy: - image: nginx:latest - ports: - - "26389:80" - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf - restart: unless-stopped - depends_on: - - webserver - - grafana grafana: image: grafana/grafana @@ -68,63 +87,3 @@ services: - ../../exchange/docker/dev/grafana_data:/var/lib/grafana # - /var/lib/grafana/grafana.db # - /var/lib/grafana/alerting - - web: - image: node:latest - restart: unless-stopped - working_dir: /app - environment: - - PORT=3001 - volumes: - - ../../web:/app - command: ["npm", "run", "dev",] - ports: - - "3001:3001" - depends_on: - - postgres - - localstack: - image: localstack/localstack - restart: unless-stopped - ports: - - "4566:4566" - - "4571:4571" - - "8080:8080" - environment: - - SERVICES=s3 - - DEFAULT_REGION=us-east-1 - - DATA_DIR=/var/lib/localstack/data - - AWS_ACCESS_KEY_ID=test - - AWS_SECRET_ACCESS_KEY=test - - S3_FORCE_PATH_STYLE=true - volumes: - - localstack:/var/lib/localstack - - awscli: - image: amazon/aws-cli - environment: - AWS_ACCESS_KEY_ID: test - AWS_SECRET_ACCESS_KEY: test - AWS_DEFAULT_REGION: us-east-1 - depends_on: - - localstack - volumes: - - ../../web/dev/local-dev-setup.sh:/home/init.sh - - ../../web/dev/cors.json:/home/cors.json - entrypoint: /home/init.sh - - postgres: - image: postgres:13 - restart: always - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: nutc - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - -volumes: - postgres_data: - localstack: diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 4fb126f2..4105ce0b 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -11,17 +11,30 @@ services: args: firebase_emulator: "false" restart: unless-stopped + ports: + - "18080:18080" environment: - NUTC_EXPOSE_METRICS=1 + - NUTC_ID=0 command: ["--sandbox"] + extra_hosts: + - "host.docker.internal:host-gateway" #port: 16124 webserver: image: nutc-webserver + ports: + - "16124:16124" build: context: ../.. dockerfile: webserver/Dockerfile restart: unless-stopped + extra_hosts: + - "host.docker.internal:host-gateway" + links: + - sandbox + depends_on: + - sandbox linter: image: nutc-linter @@ -31,12 +44,18 @@ services: dockerfile: exchange/docker/linter/Dockerfile args: firebase_emulator: "false" + extra_hosts: + - "host.docker.internal:host-gateway" # Exposed on port 9000 prometheus: image: prom/prometheus volumes: - ../../exchange/docker/sandbox/prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "9090:9090" + - "9000:9000" + - "9091:9091" command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=12h' @@ -58,7 +77,7 @@ services: restart: unless-stopped user: "${UID}:${GID}" ports: - - "3000:3000" + - "3001:3000" environment: - GF_SECURITY_ALLOW_EMBEDDING=true - GF_AUTH_ANONYMOUS_ENABLED=true diff --git a/exchange/CMakeLists.txt b/exchange/CMakeLists.txt index 5fac60f9..ae28ece4 100644 --- a/exchange/CMakeLists.txt +++ b/exchange/CMakeLists.txt @@ -306,7 +306,6 @@ target_link_libraries(LINTER_spawner_exe PRIVATE Python3::Python) # ---- COMMON ---------- add_library(COMMON_lib OBJECT - src/common/firebase/firebase.cpp src/common/file_operations/file_operations.cpp src/common/util.cpp src/common/types/decimal.cpp diff --git a/exchange/benchmark/src/orderbook.cpp b/exchange/benchmark/src/orderbook.cpp index aa9d6235..59bb47db 100644 --- a/exchange/benchmark/src/orderbook.cpp +++ b/exchange/benchmark/src/orderbook.cpp @@ -1,3 +1,4 @@ +#include "common/util.hpp" #include "exchange/orders/orderbook/limit_orderbook.hpp" #include "helpers/benchmark_trader.hpp" diff --git a/exchange/docker/dev/grafana_data/grafana.db b/exchange/docker/dev/grafana_data/grafana.db index 8ae6c220..e591490a 100644 Binary files a/exchange/docker/dev/grafana_data/grafana.db and b/exchange/docker/dev/grafana_data/grafana.db differ diff --git a/exchange/docker/sandbox/prometheus.yml b/exchange/docker/sandbox/prometheus.yml index 360f4241..67367804 100644 --- a/exchange/docker/sandbox/prometheus.yml +++ b/exchange/docker/sandbox/prometheus.yml @@ -5,3 +5,5 @@ scrape_configs: - job_name: 'exchange-sandbox' static_configs: - targets: ['sandbox:4152'] + # - targets: ['sandbox1:4152', 'sandbox2:4152', 'sandbox3:4152', 'sandbox4:4152'] + diff --git a/exchange/src/common/compilation/compile_cpp.cpp b/exchange/src/common/compilation/compile_cpp.cpp index c7bd68c8..388490de 100644 --- a/exchange/src/common/compilation/compile_cpp.cpp +++ b/exchange/src/common/compilation/compile_cpp.cpp @@ -1,5 +1,6 @@ #include "compile_cpp.hpp" +#include "common/file_operations/file_operations.hpp" #include "common/util.hpp" #include @@ -43,8 +44,8 @@ compile_cpp(const std::filesystem::path& filepath) if (result) { throw std::runtime_error(fmt::format( - "Compilation of {} failed. Compiler output below:\n {}", filepath.string(), - result.value() + "Compilation of {} failed. Compiler output below:\n {}\n Code: {}", + filepath.string(), result.value(), read_file_content(filepath.string()) )); } return binary_output; diff --git a/exchange/src/common/firebase/firebase.cpp b/exchange/src/common/firebase/firebase.cpp deleted file mode 100644 index 63d268e2..00000000 --- a/exchange/src/common/firebase/firebase.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "firebase.hpp" - -#include "common/util.hpp" - -#include - -namespace nutc::common { - -glz::json_t -get_user_info(const std::string& uid) -{ - auto params = fmt::format("users/{}.json", uid); - auto url = common::get_firebase_endpoint(params); - return firebase_request("GET", url); -} - -namespace { -size_t -write_callback(void* contents, size_t size, size_t nmemb, void* userp) -{ - auto* str = static_cast(userp); - auto* data = static_cast(contents); - - str->append(data, size * nmemb); - return size * nmemb; -} -} // namespace - -std::string -storage_request(const std::string& firebase_url) -{ - CURL* curl{}; - CURLcode res{}; - std::string read_buffer{}; - - curl = curl_easy_init(); - if (curl == nullptr) - throw std::runtime_error("curl_easy_init() failed"); - - curl_easy_setopt(curl, CURLOPT_URL, firebase_url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer); - - res = curl_easy_perform(curl); - - if (res != CURLE_OK) { - throw std::runtime_error( - fmt::format("curl_easy_perform() failed: {}", curl_easy_strerror(res)) - ); - } - - curl_easy_cleanup(curl); - - return read_buffer; -} - -std::optional -get_algo(const std::string& uid, const std::string& algo_id) -{ - glz::json_t user_info = get_user_info(uid); - if (!user_info.contains("algos")) - return std::nullopt; - - glz::json_t algos = user_info["algos"]; - if (!algos.contains(algo_id)) - return std::nullopt; - - glz::json_t algo_info = algos[algo_id]; - if (!algo_info.contains("downloadURL")) - return std::nullopt; - - std::string download_url = algo_info["downloadURL"].get(); - std::string algo_file = storage_request(download_url); - return algo_file; -} - -glz::json_t -firebase_request( - const std::string& method, const std::string& url, const std::string& data -) -{ - CURL* curl{}; - CURLcode res{}; - std::string read_buffer{}; - - curl = curl_easy_init(); - if (curl == nullptr) - throw std::runtime_error("curl_easy_init() failed"); - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &read_buffer); - - if (method == "POST") { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - } - else if (method == "PUT") { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - } - else if (method == "DELETE") { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - } - - res = curl_easy_perform(curl); - if (res != CURLE_OK) { - throw std::runtime_error( - fmt::format("curl_easy_perform() failed: {}", curl_easy_strerror(res)) - ); - } - - curl_easy_cleanup(curl); - - glz::json_t json{}; - auto error = glz::read_json(json, read_buffer); - if (error) { - std::string descriptive_error = glz::format_error(error, read_buffer); - throw std::runtime_error( - fmt::format("glz::read_json() failed: {}", descriptive_error) - ); - } - return json; -} -} // namespace nutc::common diff --git a/exchange/src/common/firebase/firebase.hpp b/exchange/src/common/firebase/firebase.hpp deleted file mode 100644 index 19c7f573..00000000 --- a/exchange/src/common/firebase/firebase.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -#include -#include - -namespace nutc::common { - -// database request - change name -glz::json_t firebase_request( - const std::string& method, const std::string& url, const std::string& data = "" -); - -glz::json_t get_user_info(const std::string& uid); - -std::optional get_algo(const std::string& uid, const std::string& algo_id); - -} // namespace nutc::common diff --git a/exchange/src/common/messages_wrapper_to_exchange.hpp b/exchange/src/common/messages_wrapper_to_exchange.hpp index 5e074d58..110e607b 100644 --- a/exchange/src/common/messages_wrapper_to_exchange.hpp +++ b/exchange/src/common/messages_wrapper_to_exchange.hpp @@ -15,10 +15,20 @@ struct init_message { std::string_view name = "init_message"; }; +struct log_message { + std::string message; +}; + struct cancel_order { Ticker ticker; order_id_t order_id; - std::uint64_t timestamp = get_time(); + std::uint64_t timestamp; + + cancel_order(Ticker ticker, order_id_t order_id, std::uint64_t timestamp) : + ticker(ticker), order_id(order_id), timestamp(timestamp) + {} + + cancel_order() = default; bool operator==(const cancel_order& other) const @@ -31,12 +41,13 @@ struct market_order { Ticker ticker; Side side; decimal_quantity quantity; - std::uint64_t timestamp = get_time(); + std::uint64_t timestamp; constexpr market_order() = default; - market_order(Ticker ticker, Side side, decimal_quantity quantity) : - ticker(ticker), side(side), quantity(quantity) + market_order( + Ticker ticker, Side side, decimal_quantity quantity, std::uint64_t timestamp + ) : ticker(ticker), side(side), quantity(quantity), timestamp(timestamp) {} bool @@ -50,7 +61,7 @@ struct market_order { struct limit_order : market_order { decimal_price price; bool ioc{false}; - order_id_t order_id = generate_order_id(); + order_id_t order_id; // TODO: fix tests and remove bool @@ -61,16 +72,12 @@ struct limit_order : market_order { && ioc == other.ioc; } - limit_order( - std::string_view ticker, Side side, decimal_quantity quantity, - decimal_price price, bool ioc = false - ) : market_order{force_to_ticker(ticker), side, quantity}, price{price}, ioc{ioc} - {} - limit_order( Ticker ticker, Side side, decimal_quantity quantity, decimal_price price, - bool ioc = false - ) : market_order{ticker, side, quantity}, price{price}, ioc{ioc} + bool ioc, std::uint64_t timestamp, order_id_t order_id + ) : + market_order{ticker, side, quantity, timestamp}, price{price}, ioc{ioc}, + order_id{order_id} {} limit_order() = default; @@ -85,7 +92,8 @@ using IncomingMessageVariant = template <> struct glz::meta { using t = nutc::common::cancel_order; - static constexpr auto value = object("cancel", &t::ticker, &t::order_id); + static constexpr auto value = + object("cancel", &t::ticker, &t::order_id, &t::timestamp); }; /// \cond @@ -93,7 +101,7 @@ template <> struct glz::meta { using t = nutc::common::limit_order; static constexpr auto value = object( - "limit", &t::timestamp, &t::ticker, &t::side, &t::quantity, &t::price, &t::ioc, + "limit", &t::ticker, &t::side, &t::quantity, &t::timestamp, &t::price, &t::ioc, &t::order_id ); }; @@ -103,7 +111,7 @@ template <> struct glz::meta { using t = nutc::common::market_order; static constexpr auto value = - object("market", &t::timestamp, &t::ticker, &t::side, &t::quantity); + object("market", &t::ticker, &t::side, &t::quantity, &t::timestamp); }; /// \cond @@ -112,3 +120,9 @@ struct glz::meta { using t = nutc::common::init_message; static constexpr auto value = object(&t::name); }; + +template <> +struct glz::meta { + using t = nutc::common::log_message; + static constexpr auto value = object("log_message", &t::message); +}; diff --git a/exchange/src/wrapper/util/resource_limits.hpp b/exchange/src/common/resource_limits.hpp similarity index 78% rename from exchange/src/wrapper/util/resource_limits.hpp rename to exchange/src/common/resource_limits.hpp index da2c2078..7f64a5d4 100644 --- a/exchange/src/wrapper/util/resource_limits.hpp +++ b/exchange/src/common/resource_limits.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #ifdef __linux__ @@ -26,6 +27,19 @@ set_memory_limit(std::size_t limit_in_mb) } return true; } + +inline bool +set_cpu_affinity(int cpu) +{ + cpu_set_t mask; + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { + return false; + } + return true; +} } // namespace nutc::wrapper #else namespace nutc::wrapper { diff --git a/exchange/src/common/types/algorithm/remote_algorithm.hpp b/exchange/src/common/types/algorithm/remote_algorithm.hpp index bc37d0bf..3ec15527 100644 --- a/exchange/src/common/types/algorithm/remote_algorithm.hpp +++ b/exchange/src/common/types/algorithm/remote_algorithm.hpp @@ -13,11 +13,15 @@ namespace nutc::common { class RemoteAlgorithm : public BaseAlgorithm { std::string id_; std::string algo_data_; + std::string display_name_; public: - RemoteAlgorithm(AlgoLanguage language, std::string algo_id, std::string algo_data) : + RemoteAlgorithm( + AlgoLanguage language, std::string algo_id, std::string algo_data, + std::string display_name + ) : BaseAlgorithm{language}, id_{std::move(algo_id)}, - algo_data_{std::move(algo_data)} + algo_data_{std::move(algo_data)}, display_name_{std::move(display_name)} {} std::string diff --git a/exchange/src/common/util.cpp b/exchange/src/common/util.cpp index 3b8b93d7..fa580f85 100644 --- a/exchange/src/common/util.cpp +++ b/exchange/src/common/util.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include namespace nutc::common { namespace bi = boost::archive::iterators; @@ -52,9 +52,11 @@ find_project_file(const std::string& file_name) order_id_t generate_order_id() { - static std::mt19937_64 gen{std::random_device{}()}; - static std::uniform_int_distribution dis; - return dis(gen); + // static constexpr auto MAX_PID_BITS = 22; + static auto pid = static_cast(getpid()); + // static std::atomic start_order_id{(pid << 46)}; + static std::int64_t start_order_id{(pid<<46)}; + return ++start_order_id; } uint64_t diff --git a/exchange/src/exchange/algos/normal_mode/normal_mode.cpp b/exchange/src/exchange/algos/normal_mode/normal_mode.cpp index 84700d2c..4adaddcf 100644 --- a/exchange/src/exchange/algos/normal_mode/normal_mode.cpp +++ b/exchange/src/exchange/algos/normal_mode/normal_mode.cpp @@ -17,49 +17,45 @@ NormalModeAlgoInitializer::initialize_trader_container( TraderContainer& traders, common::decimal_price start_capital ) const { - constexpr const std::array REQUIRED_DB_FIELDS = { - "latestAlgoId", "firstName", "lastName", "algos" - }; - glz::json_t::object_t firebase_users = get_remote_traders(); - for (const auto& user_it : firebase_users) { - const auto& user_id = user_it.first; - const auto& user = user_it.second; - - bool contains_all_fields = - std::ranges::all_of(REQUIRED_DB_FIELDS, [&user](const char* field) { - return user.contains(field); - }); - if (!contains_all_fields) { - continue; + static int id = 0; + auto firebase_users = get_remote_traders(); + for (const auto& user : firebase_users) { + if (!user.contains("language") || !user.contains("code") + || !user.contains("name")) { + throw std::runtime_error("Not contain field"); } - - std::string full_name = fmt::format( - "{} {}", user["firstName"].get(), - user["lastName"].get() - ); - std::string algo_id = user["latestAlgoId"].get(); - + common::AlgoLanguage language = user["language"].get() == "Python" + ? common::AlgoLanguage::python + : common::AlgoLanguage::cpp; + std::string code = user["code"].get(); + std::string name = user["name"].get(); try { - // TODO: add back - // traders.add_trader(common::RemoteAlgorithm{}, start_capital); - log_i(main, "Created user"); + traders.add_trader( + common::RemoteAlgorithm(language, std::to_string(id++), code, name), + start_capital, name + ); + log_i(main, "Created user {}", name); } catch (const std::runtime_error& err) { - log_w(main, "Failed to create user {}", user_id); + log_w(main, "Failed to create user {}: {}", name, err.what()); } } + log_i(main, "Done creating users, sending start time"); int64_t start_time = get_start_time(WAIT_SECS); std::for_each(traders.begin(), traders.end(), [start_time](auto& trader) { send_start_time(trader, start_time); }); + log_i(main, "Starting exchange"); + std::this_thread::sleep_for(std::chrono::seconds(WAIT_SECS)); } -glz::json_t::object_t +glz::json_t::array_t NormalModeAlgoInitializer::get_remote_traders() { - const std::string endpoint = common::get_firebase_endpoint("users.json"); + const auto* endpoint_c = std::getenv("NUTC_ALGO_ENDPOINT"); + const std::string endpoint{endpoint_c}; glz::json_t res = request_to_json("GET", endpoint); - return res.get(); + return res.get(); } } // namespace nutc::exchange diff --git a/exchange/src/exchange/algos/normal_mode/normal_mode.hpp b/exchange/src/exchange/algos/normal_mode/normal_mode.hpp index 6b43162f..14ed5012 100644 --- a/exchange/src/exchange/algos/normal_mode/normal_mode.hpp +++ b/exchange/src/exchange/algos/normal_mode/normal_mode.hpp @@ -21,6 +21,6 @@ class NormalModeAlgoInitializer : public AlgoInitializer { {} private: - static glz::json_t::object_t get_remote_traders(); + static glz::json_t::array_t get_remote_traders(); }; } // namespace nutc::exchange diff --git a/exchange/src/exchange/config/static/config.hpp b/exchange/src/exchange/config/static/config.hpp index 6b0c9d75..ecbdf5b9 100644 --- a/exchange/src/exchange/config/static/config.hpp +++ b/exchange/src/exchange/config/static/config.hpp @@ -4,7 +4,7 @@ #define DEBUG_NUM_USERS 2 // How many outgoing messages for one wrapper before we start dropping -#define MAX_OUTGOING_MQ_SIZE 1000 +#define MAX_OUTGOING_MQ_SIZE 10000 // Limit to 16kb #define MAX_PIPE_MSG_SIZE 16000 diff --git a/exchange/src/exchange/main.cpp b/exchange/src/exchange/main.cpp index 815126cf..c4e170fb 100644 --- a/exchange/src/exchange/main.cpp +++ b/exchange/src/exchange/main.cpp @@ -1,5 +1,6 @@ #include "algos/algo_manager.hpp" #include "common/logging/logging.hpp" +#include "common/resource_limits.hpp" #include "common/util.hpp" #include "exchange/algos/algo_manager.hpp" #include "exchange/config/dynamic/argparse.hpp" @@ -11,6 +12,7 @@ #include "exchange/traders/trader_container.hpp" #include +#include #include @@ -28,7 +30,7 @@ create_cycle(TraderContainer& traders, const auto& mode) switch (mode) { case Mode::normal: - return std::make_unique( + return std::make_unique( tickers, traders, order_fee, max_order_volume ); case Mode::sandbox: @@ -59,6 +61,17 @@ main_event_loop(std::unique_ptr cycle) int main(int argc, const char** argv) { + std::cout << "made it here1\n"; + const int nutc_id = [] { + const char* id = std::getenv("NUTC_ID"); + if (id == nullptr) { + return 0; + } + + return std::stoi(id); + }(); + std::cout << "made it her2\n"; + nutc::wrapper::set_cpu_affinity(nutc_id / 2); nutc::logging::init("exchange.log", quill::LogLevel::Info); std::signal(SIGINT, [](auto) { std::exit(0); }); std::signal(SIGPIPE, SIG_IGN); @@ -66,6 +79,7 @@ main(int argc, const char** argv) auto mode = process_arguments(argc, argv); TraderContainer traders{}; AlgoInitializer::get_algo_initializer(mode)->initialize_algo_management(traders); + std::cout << "made it her3\n"; main_event_loop(create_cycle(traders, mode)); diff --git a/exchange/src/exchange/matching/engine.cpp b/exchange/src/exchange/matching/engine.cpp index a7ac9a12..e371fa47 100644 --- a/exchange/src/exchange/matching/engine.cpp +++ b/exchange/src/exchange/matching/engine.cpp @@ -19,7 +19,9 @@ match_orders_( { auto match_result = attempt_match_(orders, order_fee); if (match_result.has_value()) [[likely]] { - orders.handle_match(*match_result, order_fee, orderbook); + auto [buyer_capital, seller_capital] = orders.handle_match(*match_result, order_fee, orderbook); + match_result->buyer_capital = buyer_capital; + match_result->seller_capital = seller_capital; return match_result.value(); } if (match_result.error() == MatchFailure::seller_failure) { diff --git a/exchange/src/exchange/matching/order_pair.hpp b/exchange/src/exchange/matching/order_pair.hpp index 7cae34f0..459093b5 100644 --- a/exchange/src/exchange/matching/order_pair.hpp +++ b/exchange/src/exchange/matching/order_pair.hpp @@ -129,23 +129,28 @@ class OrderPair { return std::min(buyer.quantity, seller.quantity); } - void + std::pair handle_match( const common::match& match, common::decimal_price order_fee, CompositeOrderBook& orderbook ) { - get_underlying_order().trader->get_portfolio().notify_match( + GenericTrader* buy_trader = get_underlying_order().trader; + GenericTrader* sell_trader = get_underlying_order().trader; + + buy_trader->get_portfolio().notify_match( {match.position.ticker, common::Side::buy, match.position.quantity, match.position.price * (common::decimal_price{1.0} + order_fee)} ); - get_underlying_order().trader->get_portfolio().notify_match( + sell_trader->get_portfolio().notify_match( {match.position.ticker, common::Side::sell, match.position.quantity, match.position.price * (common::decimal_price{1.0} - order_fee)} ); change_order_quantity(seller, -match.position.quantity, orderbook); change_order_quantity(buyer, -match.position.quantity, orderbook); + + return {buy_trader->get_portfolio().get_capital(), sell_trader->get_portfolio().get_capital()}; } private: diff --git a/exchange/src/exchange/matching_cycle/base/base_cycle.cpp b/exchange/src/exchange/matching_cycle/base/base_cycle.cpp index eae0a061..af703b7c 100644 --- a/exchange/src/exchange/matching_cycle/base/base_cycle.cpp +++ b/exchange/src/exchange/matching_cycle/base/base_cycle.cpp @@ -34,8 +34,11 @@ BaseMatchingCycle::collect_orders(uint64_t) -> std::vector else if constexpr (std::is_same_v) { return order; } - else { - return tagged_order{trader, order}; + else if constexpr (std::is_same_v) { + return tagged_limit_order{trader, order}; + } + else if constexpr (std::is_same_v) { + return tagged_market_order{trader, order}; } }; @@ -81,6 +84,17 @@ BaseMatchingCycle::match_orders_(std::vector orders) return matches; } +void +yield_for_3_ms() +{ + auto start = std::chrono::steady_clock::now(); + auto end = start + std::chrono::milliseconds(3); + + while (std::chrono::steady_clock::now() < end) { + sched_yield(); + } +} + void BaseMatchingCycle::handle_matches_(std::vector matches) { @@ -104,6 +118,7 @@ BaseMatchingCycle::handle_matches_(std::vector matches) traders_.begin(), traders_.end(), [&message = *update](GenericTrader& trader) { trader.send_message(message); } ); + yield_for_3_ms(); } } // namespace nutc::exchange diff --git a/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp b/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp index d9cea5ca..97c4f258 100644 --- a/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp +++ b/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp @@ -28,7 +28,35 @@ class SandboxMatchingCycle : public DevMatchingCycle { get_traders().add_trader(trader); }); + dispatch_logs_(); + DevMatchingCycle::before_cycle_(tick); } + + void + dispatch_logs_() + { + auto& traders = get_traders(); + + std::for_each(traders.begin(), traders.end(), [this](GenericTrader& trader) { + auto* algo_trader = dynamic_cast(&trader); + if (!algo_trader + || !http_server_.is_receiving_logs(algo_trader->get_id())) { + return; + } + + auto logs = algo_trader->read_logs(); + if (logs.empty()) { + return; + } + + // all this log stuff is pretty bad + // one(of many) issue is that we are constantly acquiring and unacquiring a + // lock here... + for (const auto& log : logs) { + http_server_.send_log(algo_trader->get_id(), log); + } + }); + } }; } // namespace nutc::exchange diff --git a/exchange/src/exchange/metrics/on_tick_metrics.cpp b/exchange/src/exchange/metrics/on_tick_metrics.cpp index b80d43f8..e69d3950 100644 --- a/exchange/src/exchange/metrics/on_tick_metrics.cpp +++ b/exchange/src/exchange/metrics/on_tick_metrics.cpp @@ -6,6 +6,7 @@ #include "exchange/traders/portfolio/trader_portfolio.hpp" #include "exchange/traders/trader_container.hpp" #include "prometheus.hpp" +#include #include @@ -137,7 +138,7 @@ TickerMetricsPusher::report_trader_stats(const TickerContainer& tickers) .Add({ {"ticker", common::to_string(ticker)}, {"trader_type", trader.get_type() }, - {"id", trader.get_id() }, + {"name", trader.get_display_name()}, }) .Set(amount_held); } @@ -165,14 +166,14 @@ TickerMetricsPusher::report_trader_stats(const TickerContainer& tickers) per_trader_pnl_gauge .Add({ - {"id", trader.get_id() }, - {"trader_type", trader.get_type()}, + {"trader_type", trader.get_type() }, + {"name", trader.get_display_name()}, }) .Set(pnl); per_trader_capital_gauge .Add({ - {"id", trader.get_id() }, - {"trader_type", trader.get_type()}, + {"trader_type", trader.get_type() }, + {"name", trader.get_display_name()}, }) .Set(capital); }; diff --git a/exchange/src/exchange/orders/orderbook/order_id_tracker.cpp b/exchange/src/exchange/orders/orderbook/order_id_tracker.cpp index dd2d969a..ea128f45 100644 --- a/exchange/src/exchange/orders/orderbook/order_id_tracker.cpp +++ b/exchange/src/exchange/orders/orderbook/order_id_tracker.cpp @@ -16,8 +16,10 @@ OrderIdTracker::remove_order(common::order_id_t order_id) void OrderIdTracker::add_order(LimitOrderBook::stored_limit_order order) { - if (!order->ioc) { - order_map_.emplace(order->order_id, order); - } + if (order->ioc) + return; + if (order_map_.contains(order->order_id)) [[unlikely]] + throw std::runtime_error("Uhhhh"); + order_map_.emplace(order->order_id, order); } } // namespace nutc::exchange diff --git a/exchange/src/exchange/orders/storage/order_storage.hpp b/exchange/src/exchange/orders/storage/order_storage.hpp index 93659411..18a9145b 100644 --- a/exchange/src/exchange/orders/storage/order_storage.hpp +++ b/exchange/src/exchange/orders/storage/order_storage.hpp @@ -1,31 +1,56 @@ #pragma once #include "common/messages_wrapper_to_exchange.hpp" +#include "common/types/decimal.hpp" +#include "common/util.hpp" #include "exchange/traders/trader_types/generic_trader.hpp" #include namespace nutc::exchange { -template -class tagged_order : public BaseOrderT { +// TODO: make generic again +class tagged_limit_order : public common::limit_order { public: GenericTrader* trader; - tagged_order(GenericTrader& order_creator, const auto& order) : - BaseOrderT(order), trader(&order_creator) + tagged_limit_order(GenericTrader& order_creator, const limit_order& order) : + limit_order(order), trader(&order_creator) {} - template - tagged_order(GenericTrader& order_creator, Args&&... args) - requires std::is_constructible_v - : BaseOrderT(args...), trader(&order_creator) + tagged_limit_order( + GenericTrader& order_creator, common::Ticker ticker, common::Side side, + common::decimal_quantity decimal_quantity, common::decimal_price decimal_price, + bool ioc = false + ) : + limit_order( + ticker, side, decimal_quantity, decimal_price, ioc, common::get_time(), + common::generate_order_id() + ), + trader(&order_creator) {} - bool operator==(const tagged_order& other) const = default; + bool operator==(const tagged_limit_order& other) const = default; }; -using tagged_limit_order = tagged_order; -using tagged_market_order = tagged_order; +// TODO: make generic again +class tagged_market_order : public common::market_order { +public: + GenericTrader* trader; + + tagged_market_order(GenericTrader& order_creator, const market_order& order) : + market_order(order), trader(&order_creator) + {} + + tagged_market_order( + GenericTrader& order_creator, common::Ticker ticker, common::Side side, + common::decimal_quantity decimal_quantity + ) : + market_order(ticker, side, decimal_quantity, common::get_time()), + trader(&order_creator) + {} + + bool operator==(const tagged_market_order& other) const = default; +}; using OrderVariant = std::variant; diff --git a/exchange/src/exchange/sandbox_server/crow.cpp b/exchange/src/exchange/sandbox_server/crow.cpp index 9dd1f59f..e6470a2b 100644 --- a/exchange/src/exchange/sandbox_server/crow.cpp +++ b/exchange/src/exchange/sandbox_server/crow.cpp @@ -9,8 +9,11 @@ #include #include +#include #include +#include + namespace nutc::exchange { CrowServer::CrowServer(std::uint16_t port) : @@ -76,6 +79,48 @@ CrowServer::CrowServer(std::uint16_t port) : } } ); + + CROW_WEBSOCKET_ROUTE(app, "/ws") + .onaccept([&](const crow::request& conn, void** data) { + const char* algo_id_param = conn.url_params.get("algo_id"); + if (algo_id_param == nullptr) { + return false; + } + std::string algo_id{algo_id_param}; + + std::lock_guard lock{log_ws_mutex}; + unmapped_algo_ids.push_back(algo_id); + *data = &unmapped_algo_ids.back(); + + return true; + }) + .onclose([&](crow::websocket::connection& conn, const std::string&) { + std::lock_guard lock(log_ws_mutex); + + auto it = algo_id_by_log_connection.find(&conn); + if (it != algo_id_by_log_connection.end()) { + const std::string& algo_id = it->second; + log_connection_by_algo_id.erase(algo_id); + algo_id_by_log_connection.erase(it); + } + }) + .onmessage([&](crow::websocket::connection& conn, const std::string& m, bool) { + void* user_data = conn.userdata(); + auto algo_id = *static_cast(user_data); + + log_i( + sandbox_server, "Received log websocket connection from algo_id {}", + algo_id + ); + + std::lock_guard lock{log_ws_mutex}; + algo_id_by_log_connection[&conn] = algo_id; + log_connection_by_algo_id[algo_id] = &conn; + std::ranges::remove_if(unmapped_algo_ids, [&](const std::string& id) { + return id == algo_id; + }); + }); + server_thread = std::thread([this, port] { app.signal_clear().port(port).run(); }); } @@ -85,10 +130,12 @@ CrowServer::add_pending_trader_( const std::string& algorithm_data, const std::string& logfile_url ) { + std::cout << "Adding trader to the sandbox: \n" << algorithm_data << '\n'; static const auto STARTING_CAPITAL = Config::get().constants().STARTING_CAPITAL; auto trader = std::make_shared( - common::RemoteAlgorithm{language, algo_id, algorithm_data}, STARTING_CAPITAL + common::RemoteAlgorithm{language, algo_id, algorithm_data, "SANDBOX"}, + STARTING_CAPITAL, algo_id ); trader_lock.lock(); @@ -109,6 +156,23 @@ CrowServer::add_pending_trader_( trader->send_message(start_message); } +bool +CrowServer::is_receiving_logs(const std::string& algo_id) +{ + return log_connection_by_algo_id.contains(algo_id); +} + +void +CrowServer::send_log(const std::string& algo_id, const std::string& log_message) +{ + std::lock_guard lock{log_ws_mutex}; + + auto it = log_connection_by_algo_id.find(algo_id); + if (it != log_connection_by_algo_id.end()) { + it->second->send_text(log_message); + } +} + CrowServer::~CrowServer() { io_context_.stop(); diff --git a/exchange/src/exchange/sandbox_server/crow.hpp b/exchange/src/exchange/sandbox_server/crow.hpp index 22f01584..b0520321 100644 --- a/exchange/src/exchange/sandbox_server/crow.hpp +++ b/exchange/src/exchange/sandbox_server/crow.hpp @@ -25,6 +25,13 @@ class CrowServer { mutable std::mutex trader_lock; std::vector> traders_to_add; + std::mutex log_ws_mutex; + std::unordered_map + log_connection_by_algo_id; + std::unordered_map + algo_id_by_log_connection; + std::vector unmapped_algo_ids; + public: std::vector> get_and_clear_pending_traders() @@ -36,6 +43,10 @@ class CrowServer { return pending_traders; } + bool is_receiving_logs(const std::string& algo_id); + + void send_log(const std::string& algo_id, const std::string& log_message); + explicit CrowServer(std::uint16_t port); ~CrowServer(); diff --git a/exchange/src/exchange/traders/trader_types/algo_trader.hpp b/exchange/src/exchange/traders/trader_types/algo_trader.hpp index 30da57b4..44116e9a 100644 --- a/exchange/src/exchange/traders/trader_types/algo_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/algo_trader.hpp @@ -10,15 +10,14 @@ namespace nutc::exchange { class AlgoTrader : public GenericTrader { - const std::string DISPLAY_NAME; std::optional wrapper_handle_; public: explicit AlgoTrader( - const common::algorithm_variant& algo_variant, common::decimal_price capital + const common::algorithm_variant& algo_variant, common::decimal_price capital, + std::string name = "" ) : - GenericTrader(common::get_id(algo_variant), capital), - DISPLAY_NAME(common::get_id(algo_variant)), + GenericTrader(common::get_id(algo_variant), capital, std::move(name)), wrapper_handle_(std::make_optional(algo_variant)) {} @@ -29,12 +28,6 @@ class AlgoTrader : public GenericTrader { return TYPE; } - const std::string& - get_display_name() const override - { - return DISPLAY_NAME; - } - bool can_leverage() const override { @@ -62,6 +55,15 @@ class AlgoTrader : public GenericTrader { return wrapper_handle_->read_shared(); } + + std::vector + read_logs() + { + if (!wrapper_handle_.has_value()) [[unlikely]] + return {}; + + return wrapper_handle_->read_logs(); + } }; } // namespace nutc::exchange diff --git a/exchange/src/exchange/traders/trader_types/bot_trader.hpp b/exchange/src/exchange/traders/trader_types/bot_trader.hpp index 9a4bb264..26093356 100644 --- a/exchange/src/exchange/traders/trader_types/bot_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/bot_trader.hpp @@ -73,7 +73,13 @@ class BotTrader : public GenericTrader { common::decimal_price price, bool ioc ) { - common::limit_order order{TICKER, side, quantity, price, ioc}; + common::limit_order order{TICKER, + side, + quantity, + price, + ioc, + common::get_time(), + common::generate_order_id()}; orders_.emplace_back(order); return order.order_id; } @@ -81,13 +87,16 @@ class BotTrader : public GenericTrader { void cancel_order(common::order_id_t order_id) { - orders_.emplace_back(common::cancel_order{TICKER, order_id}); + orders_.emplace_back(common::cancel_order{TICKER, order_id, common::get_time()} + ); } void add_market_order(common::Side side, common::decimal_quantity quantity) { - orders_.emplace_back(common::market_order{TICKER, side, quantity}); + orders_.emplace_back( + common::market_order{TICKER, side, quantity, common::get_time()} + ); } void diff --git a/exchange/src/exchange/traders/trader_types/generic_trader.hpp b/exchange/src/exchange/traders/trader_types/generic_trader.hpp index d7d8fe5c..494a4731 100644 --- a/exchange/src/exchange/traders/trader_types/generic_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/generic_trader.hpp @@ -14,11 +14,16 @@ namespace nutc::exchange { class GenericTrader { std::string user_id_; + std::string display_name_; TraderPortfolio state_; public: - explicit GenericTrader(std::string user_id, common::decimal_price initial_capital) : - user_id_(std::move(user_id)), state_{initial_capital} + explicit GenericTrader( + std::string user_id, common::decimal_price initial_capital, + std::string display_name = "" + ) : + user_id_(std::move(user_id)), display_name_{std::move(display_name)}, + state_{initial_capital} {} GenericTrader(GenericTrader&&) = default; @@ -39,7 +44,7 @@ class GenericTrader { virtual const std::string& get_display_name() const { - return user_id_; + return display_name_; } const std::string& diff --git a/exchange/src/exchange/wrappers/handle/wrapper_handle.cpp b/exchange/src/exchange/wrappers/handle/wrapper_handle.cpp index 18ef1262..6064d238 100644 --- a/exchange/src/exchange/wrappers/handle/wrapper_handle.cpp +++ b/exchange/src/exchange/wrappers/handle/wrapper_handle.cpp @@ -7,6 +7,8 @@ #include #include +#include + namespace { std::string quote_id(std::string user_id) @@ -78,6 +80,23 @@ WrapperHandle::create_arguments(const common::algorithm_variant& algo_variant) else if (language == common::AlgoLanguage::python) { args.emplace_back("--python_algo"); } + args.emplace_back("--core_num"); + static int id = [] { + const char* id = std::getenv("NUTC_ID"); + if (id == nullptr) { + return 0; + } + + return std::stoi(id); + }(); + if (id <= 1) { + static int num = 32; + args.emplace_back(std::to_string(--num)); + } + else { + static int num = 2; + args.emplace_back(std::to_string(++num)); + } return args; } diff --git a/exchange/src/exchange/wrappers/handle/wrapper_handle.hpp b/exchange/src/exchange/wrappers/handle/wrapper_handle.hpp index c48fa2fd..76f6bb58 100644 --- a/exchange/src/exchange/wrappers/handle/wrapper_handle.hpp +++ b/exchange/src/exchange/wrappers/handle/wrapper_handle.hpp @@ -32,6 +32,12 @@ class WrapperHandle { return reader_.get_shared(); } + std::vector + read_logs() + { + return reader_.get_logs(); + } + void send_message(const std::string& message) { diff --git a/exchange/src/exchange/wrappers/messaging/async_pipe_runner.cpp b/exchange/src/exchange/wrappers/messaging/async_pipe_runner.cpp index 80fa8e6f..0a3240a1 100644 --- a/exchange/src/exchange/wrappers/messaging/async_pipe_runner.cpp +++ b/exchange/src/exchange/wrappers/messaging/async_pipe_runner.cpp @@ -1,5 +1,7 @@ #include "async_pipe_runner.hpp" +#include "common/resource_limits.hpp" + #include #include #include @@ -18,7 +20,10 @@ AsyncPipeRunner::~AsyncPipeRunner() AsyncPipeRunner::AsyncPipeRunner() : ios(std::make_shared()), work_guard(ios->get_executor()), - ios_thread([this]() { ios->run(); }) + ios_thread([this]() { + wrapper::set_cpu_affinity(2); + ios->run(); + }) {} std::shared_ptr diff --git a/exchange/src/exchange/wrappers/messaging/pipe_reader.cpp b/exchange/src/exchange/wrappers/messaging/pipe_reader.cpp index 18aa9042..ad4bc84c 100644 --- a/exchange/src/exchange/wrappers/messaging/pipe_reader.cpp +++ b/exchange/src/exchange/wrappers/messaging/pipe_reader.cpp @@ -32,7 +32,10 @@ PipeReader::PipeReader() : void PipeReader::store_message_(const std::string& message) { - common::IncomingMessageVariant data; + std::variant< + common::init_message, common::cancel_order, common::limit_order, + common::market_order, common::log_message> + data; auto err = glz::read_json(data, message); // TODO: handle better @@ -45,7 +48,19 @@ PipeReader::store_message_(const std::string& message) } std::lock_guard lock{message_lock_}; - shared.push_back(data); + std::visit( + [&](auto&& msg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + shared_logs.push_back(std::move(msg.message)); + } + else { + shared.push_back(msg); + } + }, + data + ); } void diff --git a/exchange/src/exchange/wrappers/messaging/pipe_reader.hpp b/exchange/src/exchange/wrappers/messaging/pipe_reader.hpp index 8d4f87da..19480849 100644 --- a/exchange/src/exchange/wrappers/messaging/pipe_reader.hpp +++ b/exchange/src/exchange/wrappers/messaging/pipe_reader.hpp @@ -13,6 +13,7 @@ namespace ba = boost::asio; class PipeReader { std::mutex message_lock_; std::vector shared; + std::vector shared_logs; std::shared_ptr pipe_context_; bp::async_pipe pipe_in_; @@ -58,6 +59,15 @@ class PipeReader { result.swap(shared); return result; } + + std::vector + get_logs() + { + std::lock_guard lock{message_lock_}; + std::vector result; + result.swap(shared_logs); + return result; + } }; } // namespace nutc::exchange diff --git a/exchange/src/exchange/wrappers/messaging/pipe_writer.cpp b/exchange/src/exchange/wrappers/messaging/pipe_writer.cpp index a0f2fb56..c49322cf 100644 --- a/exchange/src/exchange/wrappers/messaging/pipe_writer.cpp +++ b/exchange/src/exchange/wrappers/messaging/pipe_writer.cpp @@ -3,6 +3,8 @@ #include "async_pipe_runner.hpp" #include "exchange/config/static/config.hpp" +#include + namespace nutc::exchange { PipeWriter::PipeWriter() : @@ -22,6 +24,7 @@ PipeWriter::send_message(const std::string& message) queued_shared_.push_back(message); if (queued_shared_.size() > MAX_OUTGOING_MQ_SIZE) [[unlikely]] { queued_shared_.pop_front(); + // std::cerr << "DROPPED MESSAGE\n"; } // It will enqueue these shared anyway diff --git a/exchange/src/linter/config.h b/exchange/src/linter/config.h index 6757d808..c19ae7eb 100644 --- a/exchange/src/linter/config.h +++ b/exchange/src/linter/config.h @@ -19,11 +19,15 @@ #define LOG_BACKUP_COUNT 5 #ifdef NUTC_LOCAL_DEV -# define S3_URL "http://localhost:4566" -# define WEBSERVER_URL "http://localhost:16124" +// # define S3_URL "http://localhost:4566" +// # define WEBSERVER_URL "http://localhost:16124" +# define S3_URL "https://5ae8160d6aff40cf276784df536e6dfc.r2.cloudflarestorage.com" +# define WEBSERVER_URL "http://webserver:16124" #else -# define S3_URL "https://nutc.s3.us-east-2.amazonaws.com" -# define WEBSERVER_URL "http://localhost:16124" +# define S3_URL "https://5ae8160d6aff40cf276784df536e6dfc.r2.cloudflarestorage.com" +# define WEBSERVER_URL "http://webserver:16124" +// # define S3_URL "https://nutc.s3.us-east-2.amazonaws.com" +// # define WEBSERVER_URL "http://localhost:16124" #endif #define S3_BUCKET "nutc" diff --git a/exchange/src/linter/spawning/spawning.cpp b/exchange/src/linter/spawning/spawning.cpp index 6dd7bb17..ae0882fa 100644 --- a/exchange/src/linter/spawning/spawning.cpp +++ b/exchange/src/linter/spawning/spawning.cpp @@ -96,7 +96,7 @@ spawn_client( res.message += fmt::format( "[linter] FAILED to lint algo\n\nYour code did not execute within " "{} seconds. Check for infinite loops or recursion. If the issue " - "persists, reach out on Piazza.\n", + "persists, reach out on Discord.\n", timeout.count() ); } diff --git a/exchange/src/wrapper/config/argparse.cpp b/exchange/src/wrapper/config/argparse.cpp index 8bd49eaa..9bcd9498 100644 --- a/exchange/src/wrapper/config/argparse.cpp +++ b/exchange/src/wrapper/config/argparse.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace nutc::wrapper { wrapper_args process_arguments(int argc, const char** argv) @@ -41,6 +43,8 @@ process_arguments(int argc, const char** argv) .implicit_value(true) .nargs(0); + program.add_argument("--core_num").nargs(1).required(); + try { program.parse_args(argc, argv); } catch (const std::runtime_error& err) { @@ -50,12 +54,13 @@ process_arguments(int argc, const char** argv) } auto trader_id = program.get("--uid"); + int core_num = std::stoi(program.get("--core_num")); if (program.get("--cpp_algo")) { - return {verbosity, trader_id, common::AlgoLanguage::cpp}; + return {verbosity, trader_id, common::AlgoLanguage::cpp, core_num}; } if (program.get("--python_algo")) { - return {verbosity, trader_id, common::AlgoLanguage::python}; + return {verbosity, trader_id, common::AlgoLanguage::python, core_num}; } throw std::runtime_error("No language provided"); diff --git a/exchange/src/wrapper/config/argparse.hpp b/exchange/src/wrapper/config/argparse.hpp index e46c7e8b..61607798 100644 --- a/exchange/src/wrapper/config/argparse.hpp +++ b/exchange/src/wrapper/config/argparse.hpp @@ -11,6 +11,7 @@ struct wrapper_args { const uint8_t VERBOSITY; const std::string TRADER_ID; const common::AlgoLanguage ALGO_TYPE; + const int CORE_NUM; }; wrapper_args process_arguments(int argc, const char** argv); diff --git a/exchange/src/wrapper/main.cpp b/exchange/src/wrapper/main.cpp index 4867754b..e0057079 100644 --- a/exchange/src/wrapper/main.cpp +++ b/exchange/src/wrapper/main.cpp @@ -3,7 +3,7 @@ #include "wrapper/messaging/exchange_communicator.hpp" #include "wrapper/runtime/cpp/cpp_runtime.hpp" #include "wrapper/runtime/python/python_runtime.hpp" -#include "wrapper/util/resource_limits.hpp" +#include "common/resource_limits.hpp" #include #include @@ -34,7 +34,8 @@ main(int argc, const char** argv) std::signal(SIGINT, catch_sigint); std::signal(SIGTERM, catch_sigterm); - auto [verbosity, trader_id, algo_type] = process_arguments(argc, argv); + auto [verbosity, trader_id, algo_type, core_num] = process_arguments(argc, argv); + nutc::wrapper::set_cpu_affinity(core_num); static constexpr std::uint32_t MAX_LOG_SIZE = 50'000; nutc::logging::init_file_only( @@ -43,7 +44,7 @@ main(int argc, const char** argv) ExchangeCommunicator communicator{trader_id}; - if (!set_memory_limit(1024) || !kill_on_exchange_death()) { + if (!set_memory_limit(2048) || !kill_on_exchange_death()) { log_e(main, "Failed to set memory limit"); communicator.report_startup_complete(); return 1; diff --git a/exchange/src/wrapper/messaging/exchange_communicator.cpp b/exchange/src/wrapper/messaging/exchange_communicator.cpp index f6adc888..943de840 100644 --- a/exchange/src/wrapper/messaging/exchange_communicator.cpp +++ b/exchange/src/wrapper/messaging/exchange_communicator.cpp @@ -1,5 +1,6 @@ #include "exchange_communicator.hpp" +#include "common/logging/logging.hpp" #include "common/messages_exchange_to_wrapper.hpp" #include "common/messages_wrapper_to_exchange.hpp" #include "common/util.hpp" @@ -61,6 +62,16 @@ ExchangeCommunicator::consume_message() return data; } +LogFunction +ExchangeCommunicator::send_log_message() +{ + return [this](const std::string& message) { + log_i(algo_print, "{}", message); + log_message msg{std::move(message)}; + (void)publish_message(msg); + }; +} + LimitOrderFunction ExchangeCommunicator::place_limit_order() { @@ -68,7 +79,8 @@ ExchangeCommunicator::place_limit_order() common::Side side, common::Ticker ticker, double quantity, double price, bool ioc ) -> order_id_t { - limit_order order{ticker, side, quantity, price, ioc}; + limit_order order{ticker, side, quantity, price, + ioc, get_time(), generate_order_id()}; if (!publish_message(order)) return -1; return order.order_id; @@ -79,7 +91,7 @@ MarketOrderFunction ExchangeCommunicator::place_market_order() { return [this](common::Side side, common::Ticker ticker, double quantity) { - market_order order{ticker, side, quantity}; + market_order order{ticker, side, quantity, common::get_time()}; return publish_message(order); }; } @@ -88,7 +100,9 @@ CancelOrderFunction ExchangeCommunicator::cancel_order() { return [this](common::Ticker ticker, order_id_t order_id) -> bool { - return publish_message(common::cancel_order{ticker, order_id}); + return publish_message( + common::cancel_order{ticker, order_id, common::get_time()} + ); }; } diff --git a/exchange/src/wrapper/messaging/exchange_communicator.hpp b/exchange/src/wrapper/messaging/exchange_communicator.hpp index beb5dd8b..76c6e824 100644 --- a/exchange/src/wrapper/messaging/exchange_communicator.hpp +++ b/exchange/src/wrapper/messaging/exchange_communicator.hpp @@ -18,6 +18,7 @@ using MarketOrderFunction = std::function; using CancelOrderFunction = std::function; +using LogFunction = std::function; class ExchangeCommunicator { RateLimiter limiter_{}; @@ -50,6 +51,7 @@ class ExchangeCommunicator { return true; } + LogFunction send_log_message(); LimitOrderFunction place_limit_order(); MarketOrderFunction place_market_order(); CancelOrderFunction cancel_order(); diff --git a/exchange/src/wrapper/messaging/rate_limiter.hpp b/exchange/src/wrapper/messaging/rate_limiter.hpp index cfb77559..f6645ef7 100644 --- a/exchange/src/wrapper/messaging/rate_limiter.hpp +++ b/exchange/src/wrapper/messaging/rate_limiter.hpp @@ -8,7 +8,7 @@ class RateLimiter { std::queue timestamps_; // TODO(stevenewald): make configurable - static constexpr size_t MAX_CALLS = 3'000; + static constexpr size_t MAX_CALLS = 50'000; static constexpr std::chrono::seconds TIME_WINDOW = std::chrono::seconds(1); public: diff --git a/exchange/src/wrapper/runtime/cpp/cpp_runtime.cpp b/exchange/src/wrapper/runtime/cpp/cpp_runtime.cpp index 8e382501..45673562 100644 --- a/exchange/src/wrapper/runtime/cpp/cpp_runtime.cpp +++ b/exchange/src/wrapper/runtime/cpp/cpp_runtime.cpp @@ -71,7 +71,7 @@ CppRuntime::CppRuntime( } strategy_object_ = init_func( communicator_.place_market_order(), communicator_.place_limit_order(), - communicator_.cancel_order(), log_text + communicator_.cancel_order(), communicator_.send_log_message() ); } diff --git a/exchange/src/wrapper/runtime/cpp/cpp_runtime.hpp b/exchange/src/wrapper/runtime/cpp/cpp_runtime.hpp index 3802cf5c..8a93ba4a 100644 --- a/exchange/src/wrapper/runtime/cpp/cpp_runtime.hpp +++ b/exchange/src/wrapper/runtime/cpp/cpp_runtime.hpp @@ -1,10 +1,9 @@ #pragma once +#include "wrapper/messaging/exchange_communicator.hpp" #include "wrapper/runtime/runtime.hpp" namespace nutc::wrapper { -using PrintLn = std::function; - class CppRuntime : public Runtime { public: CppRuntime( @@ -29,7 +28,7 @@ class CppRuntime : public Runtime { using Strategy = void; using InitFunc = Strategy* (*)(MarketOrderFunction, LimitOrderFunction, - CancelOrderFunction, PrintLn); + CancelOrderFunction, LogFunction); using on_trade_update_func = void (*)(Strategy*, Ticker, Side, float, float); using on_orderbook_update_func = void (*)(Strategy*, Ticker, Side, float, float); using on_account_update_func = diff --git a/exchange/src/wrapper/runtime/python/python_runtime.cpp b/exchange/src/wrapper/runtime/python/python_runtime.cpp index 24698172..27b7625e 100644 --- a/exchange/src/wrapper/runtime/python/python_runtime.cpp +++ b/exchange/src/wrapper/runtime/python/python_runtime.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace nutc::wrapper { namespace py = pybind11; @@ -20,7 +22,7 @@ PyRuntime::fire_on_trade_update( ); } catch (const py::error_already_set& err) { log_error(err.what()); - // std::cerr << err.what() << "\n"; + std::cerr << err.what() << "\n"; } } @@ -29,13 +31,17 @@ PyRuntime::fire_on_orderbook_update( Ticker ticker, Side side, decimal_quantity quantity, decimal_price price ) const { + static bool reported = false; try { py::globals()["strategy"].attr("on_orderbook_update")( ticker, side, static_cast(quantity), static_cast(price) ); } catch (const py::error_already_set& err) { log_error(err.what()); - // std::cerr << err.what() << "\n"; + if (reported) + return; + std::cerr << err.what() << "\n"; + reported = true; } } @@ -52,14 +58,14 @@ PyRuntime::fire_on_account_update( ); } catch (const py::error_already_set& err) { log_error(err.what()); - // std::cerr << err.what() << "\n"; + std::cerr << err.what() << "\n"; } } void PyRuntime::create_api_module( LimitOrderFunction publish_limit_order, MarketOrderFunction publish_market_order, - CancelOrderFunction cancel_order + CancelOrderFunction cancel_order, LogFunction send_log_message ) { py::module_ sys = py::module_::import("sys"); @@ -91,7 +97,7 @@ PyRuntime::create_api_module( module.def("publish_market_order", publish_market_order); module.def("publish_limit_order", publish_limit_order); module.def("cancel_order", cancel_order); - module.def("print", log_text); + module.def("print", send_log_message); auto sys_modules = sys.attr("modules").cast(); sys_modules["nutc_api"] = module; diff --git a/exchange/src/wrapper/runtime/python/python_runtime.hpp b/exchange/src/wrapper/runtime/python/python_runtime.hpp index 7d7cb3fc..b6ab9f04 100644 --- a/exchange/src/wrapper/runtime/python/python_runtime.hpp +++ b/exchange/src/wrapper/runtime/python/python_runtime.hpp @@ -19,7 +19,7 @@ class PyRuntime : public Runtime { { create_api_module( communicator_.place_limit_order(), communicator_.place_market_order(), - communicator_.cancel_order() + communicator_.cancel_order(), communicator_.send_log_message() ); run_initialization_code(algo_); } @@ -42,7 +42,8 @@ class PyRuntime : public Runtime { static void create_api_module( LimitOrderFunction publish_limit_order, - MarketOrderFunction publish_market_order, CancelOrderFunction cancel_order + MarketOrderFunction publish_market_order, CancelOrderFunction cancel_order, + LogFunction send_log_message ); static void run_initialization_code(const std::string& py_code); }; diff --git a/exchange/src/wrapper/runtime/runtime.cpp b/exchange/src/wrapper/runtime/runtime.cpp index cd36b083..a338dae3 100644 --- a/exchange/src/wrapper/runtime/runtime.cpp +++ b/exchange/src/wrapper/runtime/runtime.cpp @@ -22,12 +22,12 @@ Runtime::process_message(tick_update&& tick_update) if (m.buyer_id == trader_id_) [[unlikely]] { fire_on_account_update( - p.ticker, p.side, p.price, p.quantity, m.buyer_capital + p.ticker, Side::buy, p.price, p.quantity, m.buyer_capital ); } if (m.seller_id == trader_id_) [[unlikely]] { fire_on_account_update( - p.ticker, p.side, p.price, p.quantity, m.seller_capital + p.ticker, Side::sell, p.price, p.quantity, m.seller_capital ); } }); diff --git a/exchange/src/wrapper/runtime/runtime.hpp b/exchange/src/wrapper/runtime/runtime.hpp index cf1258b3..7792bc8a 100644 --- a/exchange/src/wrapper/runtime/runtime.hpp +++ b/exchange/src/wrapper/runtime/runtime.hpp @@ -54,12 +54,6 @@ class Runtime { std::string trader_id_; ExchangeCommunicator communicator_; - static void - log_text(const std::string& text) - { - log_i(algo_print, "{}", text); - } - static void log_error(const std::string& text) { diff --git a/exchange/test/src/integration/tests/basic.cpp b/exchange/test/src/integration/tests/basic.cpp index a2e7fea5..ed8b9289 100644 --- a/exchange/test/src/integration/tests/basic.cpp +++ b/exchange/test/src/integration/tests/basic.cpp @@ -25,11 +25,11 @@ TEST_P(IntegrationBasicAlgo, ConfirmOrderReceived) start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT - trader2->add_order(limit_order{Ticker::ETH, sell, 100.0, 10.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 10.0)); TestMatchingCycle cycle{traders_}; - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); ASSERT_EQ( double{ trader2->get_portfolio().get_capital() @@ -44,11 +44,11 @@ TEST_P(IntegrationBasicAlgo, ConfirmOrderFeeApplied) start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT - trader2->add_order(limit_order{Ticker::ETH, sell, 100.0, 10.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 10.0)); TestMatchingCycle cycle{traders_, .5}; - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); ASSERT_EQ( double{ trader2->get_portfolio().get_capital() @@ -63,7 +63,7 @@ TEST_P(IntegrationBasicAlgo, RemoveIOCOrder) auto& trader1 = start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT - trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 100.0)); TestMatchingCycle cycle{traders_}; @@ -82,11 +82,11 @@ TEST_P(IntegrationBasicAlgo, MarketOrderBuy) start_wrappers(traders_, GetParam(), "buy_market_order_1000"); auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); - trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 100.0)); TestMatchingCycle cycle{traders_}; - cycle.wait_for_order(limit_order{Ticker::BTC, buy, 1.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::BTC, buy, 1.0, 100.0)); } TEST_P(IntegrationBasicAlgo, MarketOrderSell) @@ -94,12 +94,12 @@ TEST_P(IntegrationBasicAlgo, MarketOrderSell) auto& trader1 = start_wrappers(traders_, GetParam(), "sell_market_order_1000"); auto trader2 = traders_.add_trader(0); trader1.get_portfolio().modify_holdings(Ticker::ETH, 1000.0); - trader2->add_order({Ticker::ETH, buy, 1.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, buy, 1.0, 100.0)); trader2->get_portfolio().modify_capital(1000.0); TestMatchingCycle cycle{traders_}; - cycle.wait_for_order(limit_order{Ticker::BTC, buy, 1.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::BTC, buy, 1.0, 100.0)); } TEST_P(IntegrationBasicAlgo, ManyUpdates) @@ -112,12 +112,14 @@ TEST_P(IntegrationBasicAlgo, ManyUpdates) TestMatchingCycle cycle{traders_}; for (int i = 0; i < 10000; i++) { - trader2->add_order({Ticker::ETH, sell, 1.0, static_cast(i)}); + trader2->add_order( + make_limit_order(Ticker::ETH, sell, 1.0, static_cast(i)) + ); } cycle.on_tick(0); - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 10.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 10.0, 100.0)); } TEST_P(IntegrationBasicAlgo, OrderVolumeLimitsPreventGoingAboveLimit) @@ -127,7 +129,8 @@ TEST_P(IntegrationBasicAlgo, OrderVolumeLimitsPreventGoingAboveLimit) TestMatchingCycle cycle{traders_, 0.0, 10.0}; for (int i = 1; i < 21; i++) { - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 1.0, static_cast(i)} + cycle.wait_for_order( + make_limit_order(Ticker::ETH, buy, 1.0, static_cast(i)) ); cycle.on_tick(0); } @@ -144,11 +147,11 @@ TEST_P(IntegrationBasicAlgo, OnTradeUpdate) TestMatchingCycle cycle{traders_}; - trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 100.0)); - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 10.0, 102.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 10.0, 102.0)); - cycle.wait_for_order(limit_order{Ticker::BTC, buy, 1.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::BTC, buy, 1.0, 100.0)); } // Sanity check that it goes through the orderbook @@ -161,10 +164,10 @@ TEST_P(IntegrationBasicAlgo, MultipleLevelOrder) TestMatchingCycle cycle{traders_}; - trader2->add_order({Ticker::ETH, sell, 55.0, 1.0}); - trader2->add_order({Ticker::ETH, sell, 45.0, 1.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 55.0, 1.0)); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 45.0, 1.0)); - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); ASSERT_EQ( trader1.get_portfolio().get_capital() - trader1.get_portfolio().get_initial_capital(), @@ -178,16 +181,16 @@ TEST_P(IntegrationBasicAlgo, OnAccountUpdateSell) trader1.get_portfolio().modify_holdings(Ticker::ETH, 1000.0); auto trader2 = traders_.add_trader(100000); - trader2->add_order({Ticker::ETH, buy, 102.0, 102.0}); + trader2->add_order(make_limit_order(Ticker::ETH, buy, 102.0, 102.0)); TestMatchingCycle cycle{traders_}; // obupdate triggers one user to place acommon::Side::buy order of 10 ABC at 102 - cycle.wait_for_order(limit_order{Ticker::ETH, sell, 10.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, sell, 10.0, 100.0)); // on_trade_match triggers one user to place acommon::Side::buy order of 1 ABC // at 100 - cycle.wait_for_order(limit_order{Ticker::BTC, buy, 1.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::BTC, buy, 1.0, 100.0)); } TEST_P(IntegrationBasicAlgo, OnAccountUpdateBuy) @@ -196,15 +199,15 @@ TEST_P(IntegrationBasicAlgo, OnAccountUpdateBuy) auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT - trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 100.0)); TestMatchingCycle cycle{traders_}; // obupdate triggers one user to place acommon::Side::buy order of 10 ABC at 102 - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 10.0, 102.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 10.0, 102.0)); // on_trade_match triggers one user to place acommon::Side::buy order of 1 ABC // at 100 - cycle.wait_for_order(limit_order{Ticker::BTC, buy, 1.0, 100.0}); + cycle.wait_for_order(make_limit_order(Ticker::BTC, buy, 1.0, 100.0)); } TEST_P(IntegrationBasicAlgo, AlgoStartDelay) @@ -218,11 +221,11 @@ TEST_P(IntegrationBasicAlgo, AlgoStartDelay) auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT - trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 100.0)); TestMatchingCycle cycle{traders_}; - cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); auto end = std::chrono::high_resolution_clock::now(); const int64_t observed_duration_ms = @@ -239,7 +242,7 @@ TEST_P(IntegrationBasicAlgo, DisableTrader) auto& trader1 = start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT - trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); + trader2->add_order(make_limit_order(Ticker::ETH, sell, 100.0, 100.0)); trader1.disable(); diff --git a/exchange/test/src/integration/tests/cancellation.cpp b/exchange/test/src/integration/tests/cancellation.cpp index cacb5f05..f63b6a34 100644 --- a/exchange/test/src/integration/tests/cancellation.cpp +++ b/exchange/test/src/integration/tests/cancellation.cpp @@ -23,9 +23,10 @@ TEST_P(IntegrationBasicCancellation, CancelMessageHasSameIdAsOrder) start_wrappers(traders_, GetParam(), "cancel_limit_order"); TestMatchingCycle cycle{traders_}; - auto order_id = cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + auto order_id = + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); EXPECT_TRUE(order_id.has_value()); - cycle.wait_for_order(common::cancel_order{common::Ticker::ETH, *order_id}); + cycle.wait_for_order(make_cancel_order(common::Ticker::ETH, *order_id)); } TEST_P(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) @@ -35,10 +36,11 @@ TEST_P(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) trader2->get_portfolio().modify_holdings(Ticker::ETH, 100.0); TestMatchingCycle cycle{traders_}; - auto order_id = cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + auto order_id = + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); EXPECT_TRUE(order_id.has_value()); - cycle.wait_for_order(common::cancel_order{common::Ticker::ETH, *order_id}); - trader2->add_order(common::market_order{Ticker::ETH, sell, 10.0}); + cycle.wait_for_order(make_cancel_order(common::Ticker::ETH, *order_id)); + trader2->add_order(make_market_order(Ticker::ETH, sell, 10.0)); cycle.on_tick(0); @@ -54,11 +56,12 @@ TEST_P(IntegrationBasicCancellation, OneOfTwoOrdersCancelledResultsInMatch) trader2->get_portfolio().modify_holdings(Ticker::ETH, 100.0); TestMatchingCycle cycle{traders_}; - auto order_id = cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); + auto order_id = + cycle.wait_for_order(make_limit_order(Ticker::ETH, buy, 100.0, 10.0)); // Assume non-cancelled order got through EXPECT_TRUE(order_id.has_value()); - cycle.wait_for_order(common::cancel_order{common::Ticker::ETH, *order_id}); - trader2->add_order(common::market_order{Ticker::ETH, sell, 10.0}); + cycle.wait_for_order(make_cancel_order(common::Ticker::ETH, *order_id)); + trader2->add_order(make_market_order(Ticker::ETH, sell, 10.0)); cycle.on_tick(0); diff --git a/exchange/test/src/unit/matching/basic_matching.cpp b/exchange/test/src/unit/matching/basic_matching.cpp index 92c82634..5c8d8615 100644 --- a/exchange/test/src/unit/matching/basic_matching.cpp +++ b/exchange/test/src/unit/matching/basic_matching.cpp @@ -10,6 +10,7 @@ using nutc::common::Ticker; using nutc::common::Side::buy; using nutc::common::Side::sell; +using namespace nutc::test; class UnitBasicMatching : public ::testing::Test { protected: @@ -99,12 +100,13 @@ TEST_F(UnitBasicMatching, NoMatchThenMatchSell) tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order2{trader2, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order3{trader3, Ticker::ETH, sell, 2.0, 0.0}; + tagged_limit_order order4{trader1, Ticker::ETH, buy, 1.0, 1.0}; auto matches = add_to_engine_(order1); ASSERT_EQ(matches.size(), 0); matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 0); - matches = add_to_engine_(order1); + matches = add_to_engine_(order4); ASSERT_EQ(matches.size(), 0); matches = add_to_engine_(order3); ASSERT_EQ(matches.size(), 2); diff --git a/exchange/test/src/unit/matching/invalid_orders.cpp b/exchange/test/src/unit/matching/invalid_orders.cpp index 2a97dd91..fdc64736 100644 --- a/exchange/test/src/unit/matching/invalid_orders.cpp +++ b/exchange/test/src/unit/matching/invalid_orders.cpp @@ -39,8 +39,10 @@ TEST_F(UnitInvalidOrders, RemoveThenAddFunds) { trader1.get_portfolio().modify_capital(-TEST_STARTING_CAPITAL); - tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0}; tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0}; + tagged_limit_order order11{trader1, Ticker::ETH, buy, 1.0, 1.0}; + tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0}; + tagged_limit_order order22{trader2, Ticker::ETH, sell, 1.0, 1.0}; // Thrown out auto matches = add_to_engine_(order1); @@ -53,11 +55,11 @@ TEST_F(UnitInvalidOrders, RemoveThenAddFunds) trader1.get_portfolio().modify_capital(TEST_STARTING_CAPITAL); // Kept, but not matched - matches = add_to_engine_(order2); + matches = add_to_engine_(order22); ASSERT_EQ(matches.size(), 0); // Kept and matched - matches = add_to_engine_(order1); + matches = add_to_engine_(order11); ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", buy, 1, 1); } diff --git a/exchange/test/src/unit/matching/many_orders.cpp b/exchange/test/src/unit/matching/many_orders.cpp index 39049f30..3e297368 100644 --- a/exchange/test/src/unit/matching/many_orders.cpp +++ b/exchange/test/src/unit/matching/many_orders.cpp @@ -65,11 +65,12 @@ TEST_F(UnitManyOrders, CorrectTimePriority) TEST_F(UnitManyOrders, OnlyMatchesOne) { tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0}; + tagged_limit_order order11{trader1, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0}; auto matches = add_to_engine_(order1); ASSERT_EQ(matches.size(), 0); - matches = add_to_engine_(order1); + matches = add_to_engine_(order11); ASSERT_EQ(matches.size(), 0); matches = add_to_engine_(order2); diff --git a/exchange/test/src/unit/matching/order_fee_matching.cpp b/exchange/test/src/unit/matching/order_fee_matching.cpp index bc0b1beb..c7edf96a 100644 --- a/exchange/test/src/unit/matching/order_fee_matching.cpp +++ b/exchange/test/src/unit/matching/order_fee_matching.cpp @@ -109,6 +109,7 @@ TEST_F(UnitOrderFeeMatching, NoMatchThenMatchBuy) TEST_F(UnitOrderFeeMatching, NoMatchThenMatchSell) { tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0, 0}; + tagged_limit_order order11{trader1, Ticker::ETH, buy, 1.0, 1.0, 0}; tagged_limit_order order2{trader2, Ticker::ETH, buy, 1.0, 1.0, 0}; tagged_limit_order order3{trader3, Ticker::ETH, sell, 2.0, 0.0, 0}; @@ -116,7 +117,7 @@ TEST_F(UnitOrderFeeMatching, NoMatchThenMatchSell) ASSERT_EQ(matches.size(), 0); matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 0); - matches = add_to_engine_(order1); + matches = add_to_engine_(order11); ASSERT_EQ(matches.size(), 0); matches = add_to_engine_(order3); ASSERT_EQ(matches.size(), 2); @@ -284,8 +285,10 @@ TEST_F(UnitOrderFeeMatching, NotEnoughToEnough) { trader1.get_portfolio().modify_capital(-TEST_STARTING_CAPITAL + 1); - tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0, 0}; tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0, 0}; + tagged_limit_order order11{trader1, Ticker::ETH, buy, 1.0, 1.0, 0}; + tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0, 0}; + tagged_limit_order order22{trader2, Ticker::ETH, sell, 1.0, 1.0, 0}; // Thrown out auto matches = add_to_engine_(order1); @@ -301,11 +304,11 @@ TEST_F(UnitOrderFeeMatching, NotEnoughToEnough) trader1.get_portfolio().modify_capital(0.5); // Kept, but not matched - matches = add_to_engine_(order2); + matches = add_to_engine_(order22); ASSERT_EQ(matches.size(), 0); // Kept and matched - matches = add_to_engine_(order1); + matches = add_to_engine_(order11); ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", buy, 1, 1); diff --git a/exchange/test/src/util/macros.cpp b/exchange/test/src/util/macros.cpp index d202a7f2..3c31a7b5 100644 --- a/exchange/test/src/util/macros.cpp +++ b/exchange/test/src/util/macros.cpp @@ -1,6 +1,38 @@ #include "macros.hpp" +#include "common/util.hpp" + namespace nutc::test { + +limit_order +make_limit_order( + common::Ticker ticker, common::Side side, common::decimal_quantity quantity, + common::decimal_price price, bool ioc +) +{ + return {ticker, + side, + quantity, + price, + ioc, + common::get_time(), + common::generate_order_id()}; +} + +common::market_order +make_market_order( + common::Ticker ticker, common::Side side, common::decimal_quantity quantity +) +{ + return {ticker, side, quantity, common::get_time()}; +} + +common::cancel_order +make_cancel_order(common::Ticker ticker, common::order_id_t order_id) +{ + return {ticker, order_id, common::get_time()}; +} + bool order_equality(const common::market_order& order1, const common::market_order& order2) { diff --git a/exchange/test/src/util/macros.hpp b/exchange/test/src/util/macros.hpp index ec17e393..896ae378 100644 --- a/exchange/test/src/util/macros.hpp +++ b/exchange/test/src/util/macros.hpp @@ -2,6 +2,7 @@ #include "common/messages_wrapper_to_exchange.hpp" #include "common/types/algorithm/base_algorithm.hpp" #include "common/types/ticker.hpp" +#include "common/util.hpp" #include "exchange/orders/storage/order_storage.hpp" #include "exchange/traders/trader_container.hpp" @@ -30,6 +31,18 @@ PrintTo(const AlgoLanguage& op, std::ostream* os) namespace nutc::test { +limit_order make_limit_order( + common::Ticker ticker, common::Side side, common::decimal_quantity quantity, + common::decimal_price price, bool ioc = false +); + +common::market_order make_market_order( + common::Ticker ticker, common::Side side, common::decimal_quantity quantity +); + +common::cancel_order +make_cancel_order(common::Ticker ticker, common::order_id_t order_id); + bool validate_match( const nutc::common::match& match, common::Ticker ticker, const std::string& buyer_id, const std::string& seller_id, common::Side side, diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 00000000..811cc038 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,43 @@ +# ---- Base ---- +# Use a specific Node.js version on a lightweight Alpine Linux base. +FROM node:20-alpine AS base +# Install OpenSSL, which is a required dependency for Prisma's query engine on Alpine. +RUN apk add --no-cache openssl +WORKDIR /app +# Copy package.json and lock file to leverage Docker layer caching. +COPY package*.json ./ + +# ---- Dependencies ---- +# A separate stage for installing only production dependencies. +FROM base AS deps +# Copy the prisma schema BEFORE installing dependencies. +COPY prisma ./prisma +RUN npm install --omit=dev + +# ---- Builder ---- +# A stage for building the application. +FROM base AS builder +COPY . . +RUN npm install +RUN npx prisma generate +RUN npm run build + +# ---- Production Image ---- +# The final, lightweight image that will run in production. +FROM base AS runner +# Copy only the necessary files from previous stages. +COPY --from=deps /app/node_modules ./node_modules +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/prisma ./prisma +COPY package.json . + +# Set the environment to production. +ENV NODE_ENV=production + +# Expose the port the application listens on. +EXPOSE 3000 + +# The command to start the application. +CMD ["npm", "start"] + diff --git a/web/app/api/protected/db/user/createAlgo/route.ts b/web/app/api/protected/db/user/createAlgo/route.ts index dcbcb48d..95a26cbd 100644 --- a/web/app/api/protected/db/user/createAlgo/route.ts +++ b/web/app/api/protected/db/user/createAlgo/route.ts @@ -57,7 +57,10 @@ export async function POST(req: Request) { }, ); if (!submission_response.ok) { + const text = await submission_response.text(); + console.error(text); console.log("Failed to lint/sandbox"); + return NextResponse.json({ message: text }, { status: 500 }); } const { success, message } = await submission_response.json(); console.log("Success state: " + success); diff --git a/web/app/api/protected/db/user/createUser/generateApplicationEmail.ts b/web/app/api/protected/db/user/createUser/generateApplicationEmail.ts index 85201b75..a0a65da2 100644 --- a/web/app/api/protected/db/user/createUser/generateApplicationEmail.ts +++ b/web/app/api/protected/db/user/createUser/generateApplicationEmail.ts @@ -22,7 +22,7 @@ export async function GenerateApplicationEmail(user: User, profile: Profile) { const link = process.env.AUTH0_BASE_URL + `/api/handleReview?token=${token}`; const accept_link = link + "&accept=true"; const deny_link = link + "&accept=false"; - const resume_link = `${process.env.S3_ENDPOINT}/nutc/${s3Key?.Resume?.at(-1)?.s3Key + const resume_link = `${process.env.S3_ENDPOINT}/${s3Key?.Resume?.at(-1)?.s3Key }`; const mailOptions = { diff --git a/web/app/api/protected/db/user/createUser/route.ts b/web/app/api/protected/db/user/createUser/route.ts index 684d468b..e85602b9 100644 --- a/web/app/api/protected/db/user/createUser/route.ts +++ b/web/app/api/protected/db/user/createUser/route.ts @@ -29,7 +29,7 @@ export async function POST(req: Request) { uid: user.uid, }, data: { - participantState: ParticipantState.WAITING, + participantState: ParticipantState.ACCEPTED, }, }); @@ -37,13 +37,13 @@ export async function POST(req: Request) { data: profile, }); - await SendEmail( - "info@nutc.io", - session?.user.email, - "Application Submitted", - "Your NUTC application was submitted. You will hear back in the next 72 hours.", - ); - await GenerateApplicationEmail(user, profile); + // await SendEmail( + // "info@nutc.io", + // session?.user.email, + // "Application Submitted", + // "Your NUTC application was submitted. You will hear back in the next 72 hours.", + // ); + // await GenerateApplicationEmail(user, profile); return NextResponse.json({ status: 200 }); } catch (error) { diff --git a/web/app/api/protected/db/user/getTeam/route.ts b/web/app/api/protected/db/user/getTeam/route.ts index 06c6c206..504cc32b 100644 --- a/web/app/api/protected/db/user/getTeam/route.ts +++ b/web/app/api/protected/db/user/getTeam/route.ts @@ -19,7 +19,7 @@ export async function GET() { var teamMemberNames: String[] = []; for (const teamMember of teamMembers || []) { - if (teamMember.uid == user?.uid) continue; + // if (teamMember.uid == user?.uid) continue; teamMemberNames.push(`${teamMember.profile?.firstName} ${teamMember.profile?.lastName}`); } diff --git a/web/app/api/protected/db/user/setTeam/route.ts b/web/app/api/protected/db/user/setTeam/route.ts index ac31d91b..067fa62d 100644 --- a/web/app/api/protected/db/user/setTeam/route.ts +++ b/web/app/api/protected/db/user/setTeam/route.ts @@ -12,12 +12,25 @@ export async function POST(req: Request) { const { newTeam } = await req.json(); - console.log(`Updated team to be ${newTeam}`); - await prisma.team.upsert({ where: { name: newTeam }, update: {}, create: { name: newTeam } }); + // Set team to null if newTeam is an empty string or null + const teamNameToSet = newTeam || null; + // Only create/upsert the team if a valid name is provided + if (teamNameToSet) { + console.log(`Updated team to be ${teamNameToSet}`); + await prisma.team.upsert({ + where: { name: teamNameToSet }, + update: {}, + create: { name: teamNameToSet }, + }); + } else { + console.log(`Removing user from team.`); + } + + // Update the user with either the new team name or null await prisma.user.update({ where: { uid: session.user.sub }, - data: { teamName: newTeam } + data: { teamName: teamNameToSet }, }); return NextResponse.json({ message: "success", status: 200 }); diff --git a/web/app/contact/page.tsx b/web/app/contact/page.tsx index 85e3c180..ed2d8489 100644 --- a/web/app/contact/page.tsx +++ b/web/app/contact/page.tsx @@ -89,7 +89,6 @@ export default function Contact() { Get in touch

- We're currently accepting new sponsors for NUTC Fall 2024. If you're interested in sponsoring the event, or if you have any questions, please fill out the form below and we'll get back to you as soon as possible. diff --git a/web/app/dash/faq/page.tsx b/web/app/dash/faq/page.tsx index d9c96b0f..b98cd366 100644 --- a/web/app/dash/faq/page.tsx +++ b/web/app/dash/faq/page.tsx @@ -8,6 +8,10 @@ const faqs = [ answer: "NUTC is a competition to design a trading algorithm that maximizes a metric, usually PnL (net profit and loss) against other algorithms in the competition. In other words, you want your algorithm to buy and sell securities in a way that maximizes the amount of money you end up with.", }, + { + question: "How do teams work?", + answer: "Go to the partner settings tab to enter a team name. If 2 people enter the same team name, they are on the same team. The last submitted algorithm from any person on your team will be the one to compete in that case." + }, { question: "How can I get started?", answer: @@ -16,7 +20,7 @@ const faqs = [ { question: "What are the functions in the algorithm template?", answer: - "There are four main functions that allow you to interact with the exchange. place_market_order allows you to place orders for the exchange at a given price/quantity, and you can call this in any function (including __init__). on_orderbook_update is called when a new order is placed by another algorithm (BUY or SELL). on_trade_update is called when two orders match (one BUY, one SELL). This could be your order or two other orders. on_account_update is called when one of *your* orders matches with another order. Together, all of these functions are sufficient for you to maintain a complete copy of the local orderbook - this is highly recommended.", + "There are five main functions that allow you to interact with the exchange. place_market_order and place_limit_order allow you to place orders, and you can call these in any function (including __init__). on_orderbook_update is called when a new order is placed by another algorithm (BUY or SELL). on_trade_update is called when two orders match (one BUY, one SELL). This could be your order or two other orders. on_account_update is called when one of *your* orders matches with another order. Together, all of these functions are sufficient for you to maintain a complete copy of the local orderbook - this is highly recommended.", }, { question: "What does a zero-quantity order mean?", @@ -26,7 +30,7 @@ const faqs = [ { question: "How does place_market_order work?", answer: - "You can place an order (BUY or SELL) at a given price/quantity for a given stock ticker. Importantly, it returns True if the order was placed, or False if it was not placed (due to you placing more than 30 orders in a minute). You may want to handle the case where you aren't able to place an order due to the rate limit.", + "You can place an order (BUY or SELL) at a given price/quantity for a given stock ticker. Importantly, it returns True if the order was placed, or False if it was not placed (due to you exceeding the rate limit). You may want to handle the case where you aren't able to place an order due to the rate limit.", }, { question: "How much starting capital do I have?", answer: "100,000" }, { @@ -56,7 +60,7 @@ const faqs = [ }, { question: "When is the deadline to submit algorithms?", - answer: "11:59pm on 5/3.", + answer: "11:59pm on 10/17.", }, { question: "How are algorithms evaluated?", @@ -70,7 +74,7 @@ const faqs = [ }, { question: "What if I have other questions or have issues with the website?", - answer: "Please reach out in the piazza", + answer: "Please reach out in the Discord", }, ]; diff --git a/web/app/dash/group/page.tsx b/web/app/dash/group/page.tsx index e535cf60..be99eaac 100644 --- a/web/app/dash/group/page.tsx +++ b/web/app/dash/group/page.tsx @@ -32,7 +32,11 @@ export default function Groups() { }); if (response.ok) { - await Swal.fire({ icon: 'success', title: 'Team Updated', text: `Team is now ${newTeamName}` }); + if (newTeamName == null || newTeamName == "") { + await Swal.fire({ icon: 'success', title: 'Team Updated', text: `Left team` }); + } else { + await Swal.fire({ icon: 'success', title: 'Team Updated', text: `Team is now ${newTeamName}` }); + } } else { await Swal.fire({ icon: 'error', title: 'Setting team failed', text: 'Please contact nuft@u.northwestern.edu' }); } @@ -51,7 +55,7 @@ export default function Groups() {

- To form a team, create a team name and have your teammates enter the same on their computers. + To form a team, create a team name and have your teammate enter the same on their computer.

diff --git a/web/app/dash/submissions/[id]/graphs.tsx b/web/app/dash/submissions/[id]/graphs.tsx index 0db7ad03..0417d72b 100644 --- a/web/app/dash/submissions/[id]/graphs.tsx +++ b/web/app/dash/submissions/[id]/graphs.tsx @@ -3,18 +3,22 @@ import { ArrowDownTrayIcon } from "@heroicons/react/16/solid"; import { Algo, AlgoFile } from "@prisma/client"; import React, { useEffect, useState } from "react"; +import LogViewer from "./log-viewer"; +import { propagateServerField } from "next/dist/server/lib/render-server"; export function AlgoGraphs({ algo, userId, s3Endpoint, + id }: { algo: Algo & { algoFile: AlgoFile }; userId: string; s3Endpoint: string; + id: string }) { const [showLogs, setShowLogs] = useState(false); - const upTime = new Date(algo?.algoFile?.createdAt).getTime() + 1000; + const upTime = new Date(algo?.uploadedAt).getTime() + 1000; const sandboxTimeMs = 300000; const baseEndpoint = `${process.env.NEXT_PUBLIC_NGINX_ENDPOINT}/d-solo/cdk4teh4zl534a/nutc-dev?orgId=1&var-traderid=${algo.algoFileS3Key}&from=${upTime}&theme=dark`; const [url, setUrl] = useState(baseEndpoint + `&refresh=5s`); @@ -38,12 +42,12 @@ export function AlgoGraphs({ Download Logs - ) : null} + ) : }

Profit and Loss

diff --git a/web/app/dash/submissions/[id]/log-viewer.tsx b/web/app/dash/submissions/[id]/log-viewer.tsx new file mode 100644 index 00000000..22755102 --- /dev/null +++ b/web/app/dash/submissions/[id]/log-viewer.tsx @@ -0,0 +1,115 @@ +'use client' + +import React, { useEffect, useRef, useState, useCallback, useLayoutEffect } from 'react'; +import { useVirtualizer } from '@tanstack/react-virtual'; + +const MAX_LOGS = 20_000; + +interface LogViewerProps { + id: string; +} + +/** + * A client component that connects to a WebSocket and displays a stream of log messages. + * It features auto-scrolling that intelligently pauses when the user scrolls up. + */ +export default function LogViewer({ id }: LogViewerProps) { + // State to hold the array of log messages + const [logs, setLogs] = useState([]); + // State to track if the user has manually scrolled up, pausing auto-scroll + const [isPaused, setIsPaused] = useState(false); + const parentRef = useRef(null); + + // Effect to manage the WebSocket lifecycle + useEffect(() => { + // Ensure this code only runs on the client + if (typeof window === 'undefined') { + return; + } + + const wsUrl = `wss://${process.env.NEXT_PUBLIC_LOG_WS_ENDPOINT}/ws?algo_id=${id}`; + const ws = new WebSocket(wsUrl); + + // When the connection opens, send an "init" message + ws.onopen = () => { + console.log("WebSocket connection established. Sending 'init' message."); + ws.send("init"); + }; + + ws.onmessage = (event) => { + setLogs(prev => { + if (prev.length >= MAX_LOGS) { + ws.close(); + return prev; + } + return [...prev, event.data] + }); + }; + + // Cleanup function to close the WebSocket connection when the component unmounts + return () => { + ws.close(); + }; + }, [id]); // Re-run the effect if the `id` prop changes + + const rowVirtualizer = useVirtualizer({ + count: logs.length, + getScrollElement: () => parentRef.current, + // Estimate size of each row for initial render + estimateSize: () => 24, + // Add overscan for smoother scrolling + overscan: 10, + }); + + // This effect handles auto-scrolling to the bottom for new logs + useLayoutEffect(() => { + if (!isPaused && logs.length > 0) { + rowVirtualizer.scrollToIndex(logs.length - 1, { align: 'end' }); + } + }, [logs.length, isPaused, rowVirtualizer]); + + const handleScroll = useCallback(() => { + if (parentRef.current) { + const { scrollTop, scrollHeight, clientHeight } = parentRef.current; + // Use a small buffer to reliably detect the bottom + const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10; + setIsPaused(!isAtBottom); + } + }, []); + + const isAtMax = logs.length >= MAX_LOGS; + const virtualItems = rowVirtualizer.getVirtualItems(); + + return ( +
+
+

Live Sandbox Logs

+
+ {isAtMax ? "Live logs paused due to high volume, download log file after run" : isPaused ? 'Scroll to bottom to resume' : 'Auto-scrolling'} +
+
+
+
+ {/* Map over the virtual items provided by the hook */} + {virtualItems.map((virtualItem) => ( +
+ {virtualItem.index + 1} + {logs[virtualItem.index]} +
+ ))} +
+
+
+ ); +} diff --git a/web/app/dash/submissions/[id]/page.tsx b/web/app/dash/submissions/[id]/page.tsx index 4e978b1e..f359c70d 100644 --- a/web/app/dash/submissions/[id]/page.tsx +++ b/web/app/dash/submissions/[id]/page.tsx @@ -5,6 +5,7 @@ import { ArrowDownTrayIcon } from "@heroicons/react/16/solid"; import { notFound, redirect } from "next/navigation"; import React from "react"; import { AlgoGraphs } from "./graphs"; +import LogViewer from "./log-viewer"; export default async function SubmissionPage(props: { params: { id: string }; @@ -54,13 +55,14 @@ export default async function SubmissionPage(props: { Download Submission bool: - """Place a market order - DO NOT MODIFY""" +const pythonCodeString = `from enum import Enum +class Side(Enum): + BUY = 0 + SELL = 1 -class Strategy: - """Template for a strategy.""" +class Ticker(Enum): + ETH = 0 + BTC = 1 + LTC = 2 - def __init__(self) -> None: - """Your initialization code goes here.""" +def place_market_order(side: Side, ticker: Ticker, quantity: float) -> bool: + return True - def on_trade_update(self, ticker: str, side: str, price: float, quantity: float) -> None: - print(f"Python Trade update: {ticker} {side} {price} {quantity}") +def place_limit_order(side: Side, ticker: Ticker, quantity: float, price: float, ioc: bool = False) -> int: + return 0 - def on_orderbook_update(self, ticker: str, side: str, price: float, quantity: float) -> None: - print(f"Python Orderbook update: {ticker} {side} {price} {quantity}") +def cancel_order(ticker: Ticker, order_id: int) -> bool: + return True - def on_account_update(self, ticker: str, side: str, price: float, quantity: float, capital_remaining: float) -> None: - print(f"Python Account update: {ticker} {side} {price} {quantity} {capital_remaining}") +# You can use print() and view the logs after sandbox run has completed +class Strategy: + def __init__(self) -> None: + pass + def on_trade_update(self, ticker: Ticker, side: Side, quantity: float, price: float) -> None: + pass + def on_orderbook_update( + self, ticker: Ticker, side: Side, quantity: float, price: float + ) -> None: + pass + def on_account_update( + self, + ticker: Ticker, + side: Side, + price: float, + quantity: float, + capital_remaining: float, + ) -> None: + pass `; -const cppCodeString = `// Place a market order - DO NOT MODIFY -bool place_market_order(std::string const& side, std::string const& ticker, double quantity); +const cppCodeString = `#include +#include +enum class Side { buy = 0, sell = 1 }; +enum class Ticker : std::uint8_t { ETH = 0, BTC = 1, LTC = 2 }; +bool place_market_order(Side side, Ticker ticker, float quantity); +std::int64_t place_limit_order(Side side, Ticker ticker, float quantity, + float price, bool ioc = false); + +bool cancel_order(Ticker ticker, std::int64_t order_id); +void println(const std::string &text); -// Template for a strategy class Strategy { public: - Strategy() { // Your initialization code goes here } - - // Called whenever two orders match. Could be one of your orders, or two other people's orders. - void on_trade_update(std::string ticker, std::string side, double quantity, double price) { } - - // Called whenever the orderbook changes. This could be because of a trade, or because of a new order, or both. - void on_orderbook_update(std::string ticker, std::string side, double quantity, double price) { } - - // Called whenever one of your orders is filled. - void on_account_update(std::string ticker, std::string side, double price, double quantity, double capital_remaining) { } -} + Strategy() {} + void on_trade_update(Ticker ticker, Side side, float quantity, float price) {} + void on_orderbook_update(Ticker ticker, Side side, float quantity, + float price) {} + void on_account_update(Ticker ticker, Side side, float price, float quantity, + float capital_remaining) {} +}; `; const PythonCodeBlock = () => { @@ -78,7 +102,7 @@ export default function Template() { className="mt-4 inline-flex items-center justify-center gap-x-2 rounded bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 focus:outline-none focus-visible:ring focus-visible:ring-indigo-500 focus-visible:ring-opacity-50" >
@@ -99,7 +123,7 @@ export default function Template() { className="mt-4 inline-flex items-center justify-center gap-x-2 rounded bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 focus:outline-none focus-visible:ring focus-visible:ring-indigo-500 focus-visible:ring-opacity-50" >