From 0bb5ec7afe60a9b83b144f4d66add41960500e4e Mon Sep 17 00:00:00 2001 From: stevenewald Date: Thu, 19 Sep 2024 21:44:35 -0600 Subject: [PATCH] Parameterize cancellation integration tests --- .../src/common/types/algorithm/algorithm.hpp | 2 + .../types/algorithm/local_algorithm.cpp | 11 ++- .../test_algos/cpp/cancel_limit_order.hpp | 94 ++++++++++++++++++ .../cpp/partial_cancel_limit_order.hpp | 95 +++++++++++++++++++ exchange/test/src/integration/tests/basic.cpp | 20 ---- .../src/integration/tests/cancellation.cpp | 21 ++-- exchange/test/src/util/macros.hpp | 22 ++++- 7 files changed, 231 insertions(+), 34 deletions(-) create mode 100644 exchange/test/src/integration/test_algos/cpp/cancel_limit_order.hpp create mode 100644 exchange/test/src/integration/test_algos/cpp/partial_cancel_limit_order.hpp diff --git a/exchange/src/common/types/algorithm/algorithm.hpp b/exchange/src/common/types/algorithm/algorithm.hpp index e9b95873..d66418b1 100644 --- a/exchange/src/common/types/algorithm/algorithm.hpp +++ b/exchange/src/common/types/algorithm/algorithm.hpp @@ -3,6 +3,8 @@ #include "local_algorithm.hpp" #include "remote_algorithm.hpp" +#include + namespace nutc::common { using algorithm_variant = std::variant; diff --git a/exchange/src/common/types/algorithm/local_algorithm.cpp b/exchange/src/common/types/algorithm/local_algorithm.cpp index 49bf460d..f7cab22a 100644 --- a/exchange/src/common/types/algorithm/local_algorithm.cpp +++ b/exchange/src/common/types/algorithm/local_algorithm.cpp @@ -3,8 +3,11 @@ #include "base_algorithm.hpp" #include "common/file_operations/file_operations.hpp" +#include + #include +#include #include namespace nutc::common { @@ -13,9 +16,11 @@ LocalAlgorithm::compile_cpp_() const { assert(get_language() == AlgoLanguage::cpp); static constexpr std::string_view TEMPLATE_PATH = "test_algos/cpp/template.cpp"; - std::string output_path = fmt::format("/tmp/algo_cpp_path.so", filepath_.string()); + std::string binary_output = (boost::filesystem::temp_directory_path() + / boost::filesystem::unique_path("%%%%-%%%%-%%%%.tmp")) + .string(); std::string command = fmt::format( - "g++ -std=c++20 -fPIC -shared -o {} -include {} {}", output_path, + "g++ -std=c++20 -fPIC -shared -o {} -include {} {}", binary_output, filepath_.string(), TEMPLATE_PATH ); @@ -26,7 +31,7 @@ LocalAlgorithm::compile_cpp_() const fmt::format("Compilation of {} failed", filepath_.string()) ); } - return output_path; + return binary_output; } LocalAlgorithm::LocalAlgorithm(AlgoLanguage language, std::filesystem::path filepath) : diff --git a/exchange/test/src/integration/test_algos/cpp/cancel_limit_order.hpp b/exchange/test/src/integration/test_algos/cpp/cancel_limit_order.hpp new file mode 100644 index 00000000..7437947e --- /dev/null +++ b/exchange/test/src/integration/test_algos/cpp/cancel_limit_order.hpp @@ -0,0 +1,94 @@ +#include + +#include + +/** + * Place a market order + * + * IMPORTANT: + * You should handle the case where the order fails due to rate limiting + * (maybe wait and try again?) + * + * @param side Side of the order to place ("BUY" or "SELL") + * @param ticker Ticker of the order to place ("ETH", "BTC", or "LTC") + * @param quantity Volume of the order to place + * + * @return true if order succeeded, false if order failed due to rate limiting + */ +bool +place_market_order(std::string const& side, std::string const& ticker, double quantity); + +/** + * Place a limit order + * + * IMPORTANT: + * You should handle the case where the order fails due to rate limiting + * (maybe wait and try again?) + * + * @param side Side of the order to place ("BUY" or "SELL") + * @param ticker Ticker of the order to place ("ETH", "BTC", or "LTC") + * @param quantity Volume of the order to place + * @param price Price of the order to place + * @param ioc Immediate or cancel + * + * @return true if order succeeded, false if order failed due to rate limiting + */ +std::int64_t place_limit_order( + std::string const& side, std::string const& ticker, double quantity, double price, + bool ioc = false +); + +bool cancel_order(std::string const& ticker, std::int64_t order_id); + +class Strategy { +public: + Strategy() + { + auto order_id = place_limit_order("BUY", "ETH", 100, 10); + cancel_order("ETH", order_id); + } + + /** + * Called whenever two orders match. Could be one of your orders, or two other + * people's orders. + * + * @param ticker Ticker of the orders that were matched ("ETH", "BTC", or + * "LTC) + * @param side Side of the orders that were matched ("BUY" or "SELL") + * @param price Price that trade was executed at + * @quantity quantity Volume traded + */ + 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. + * + * @param ticker Ticker that has an orderbook update ("ETH", "BTC", or "LTC") + * @param side Which orderbook as updated ("BUY" or "SELL") + * @param price Price of orderbook that has an update + * @param quantity Volume placed into orderbook + */ + void + on_orderbook_update( + std::string ticker, std::string side, double quantity, double price + ) + {} + + /** + * Called whenever one of your orders is filled. + * + * @param ticker Ticker of order that was fulfilled ("ETH", "BTC", or "LTC") + * @param side Side of order that was fulfilled ("BUY" or "SELL") + * @param price Price that order was fulfilled at + * @param quantity Amount of capital after fulfilling order + */ + void + on_account_update( + std::string ticker, std::string side, double price, double quantity, + double capital_remaining + ) + {} +}; diff --git a/exchange/test/src/integration/test_algos/cpp/partial_cancel_limit_order.hpp b/exchange/test/src/integration/test_algos/cpp/partial_cancel_limit_order.hpp new file mode 100644 index 00000000..d645959d --- /dev/null +++ b/exchange/test/src/integration/test_algos/cpp/partial_cancel_limit_order.hpp @@ -0,0 +1,95 @@ +#include + +#include + +/** + * Place a market order + * + * IMPORTANT: + * You should handle the case where the order fails due to rate limiting + * (maybe wait and try again?) + * + * @param side Side of the order to place ("BUY" or "SELL") + * @param ticker Ticker of the order to place ("ETH", "BTC", or "LTC") + * @param quantity Volume of the order to place + * + * @return true if order succeeded, false if order failed due to rate limiting + */ +bool +place_market_order(std::string const& side, std::string const& ticker, double quantity); + +/** + * Place a limit order + * + * IMPORTANT: + * You should handle the case where the order fails due to rate limiting + * (maybe wait and try again?) + * + * @param side Side of the order to place ("BUY" or "SELL") + * @param ticker Ticker of the order to place ("ETH", "BTC", or "LTC") + * @param quantity Volume of the order to place + * @param price Price of the order to place + * @param ioc Immediate or cancel + * + * @return true if order succeeded, false if order failed due to rate limiting + */ +std::int64_t place_limit_order( + std::string const& side, std::string const& ticker, double quantity, double price, + bool ioc = false +); + +bool cancel_order(std::string const& ticker, std::int64_t order_id); + +class Strategy { +public: + Strategy() + { + auto order_id = place_limit_order("BUY", "ETH", 100, 10); + place_limit_order("BUY", "ETH", 100, 20); + cancel_order("ETH", order_id); + } + + /** + * Called whenever two orders match. Could be one of your orders, or two other + * people's orders. + * + * @param ticker Ticker of the orders that were matched ("ETH", "BTC", or + * "LTC) + * @param side Side of the orders that were matched ("BUY" or "SELL") + * @param price Price that trade was executed at + * @quantity quantity Volume traded + */ + 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. + * + * @param ticker Ticker that has an orderbook update ("ETH", "BTC", or "LTC") + * @param side Which orderbook as updated ("BUY" or "SELL") + * @param price Price of orderbook that has an update + * @param quantity Volume placed into orderbook + */ + void + on_orderbook_update( + std::string ticker, std::string side, double quantity, double price + ) + {} + + /** + * Called whenever one of your orders is filled. + * + * @param ticker Ticker of order that was fulfilled ("ETH", "BTC", or "LTC") + * @param side Side of order that was fulfilled ("BUY" or "SELL") + * @param price Price that order was fulfilled at + * @param quantity Amount of capital after fulfilling order + */ + void + on_account_update( + std::string ticker, std::string side, double price, double quantity, + double capital_remaining + ) + {} +}; diff --git a/exchange/test/src/integration/tests/basic.cpp b/exchange/test/src/integration/tests/basic.cpp index e2d68b0a..149c981c 100644 --- a/exchange/test/src/integration/tests/basic.cpp +++ b/exchange/test/src/integration/tests/basic.cpp @@ -7,24 +7,6 @@ #include -namespace nutc::common { -void -PrintTo(const AlgoLanguage& op, std::ostream* os) -{ - switch (op) { - case AlgoLanguage::cpp: - *os << "CPP"; - break; - case AlgoLanguage::python: - *os << "PYTHON"; - break; - default: - *os << "UNKNOWN_LANGUAGE"; - break; - } -} -} // namespace nutc::common - namespace nutc::test { using common::limit_order; using nutc::common::AlgoLanguage; @@ -125,7 +107,6 @@ TEST_P(IntegrationBasicAlgo, MarketOrderSell) cycle.wait_for_order(limit_order{Ticker::BTC, buy, 1.0, 100.0}); } - TEST_P(IntegrationBasicAlgo, ManyUpdates) { start_wrappers(traders_, GetParam(), "confirm_1000"); @@ -256,7 +237,6 @@ TEST_P(IntegrationBasicAlgo, DisableTrader) ASSERT_TRUE(orders.empty()); } - INSTANTIATE_TEST_SUITE_P( IntegrationBasic, IntegrationBasicAlgo, ::testing::Values(AlgoLanguage::python, AlgoLanguage::cpp) diff --git a/exchange/test/src/integration/tests/cancellation.cpp b/exchange/test/src/integration/tests/cancellation.cpp index 06ad81af..46b2b638 100644 --- a/exchange/test/src/integration/tests/cancellation.cpp +++ b/exchange/test/src/integration/tests/cancellation.cpp @@ -10,7 +10,7 @@ namespace nutc::test { using nutc::common::AlgoLanguage; -class IntegrationBasicCancellation : public ::testing::Test { +class IntegrationBasicCancellation : public ::testing::TestWithParam { protected: using Ticker = nutc::common::Ticker; using nutc::common::Side::buy; @@ -18,9 +18,9 @@ class IntegrationBasicCancellation : public ::testing::Test { exchange::TraderContainer traders_; }; -TEST_F(IntegrationBasicCancellation, CancelMessageHasSameIdAsOrder) +TEST_P(IntegrationBasicCancellation, CancelMessageHasSameIdAsOrder) { - start_wrappers(traders_, AlgoLanguage::python, "cancel_limit_order"); + 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}); @@ -28,10 +28,9 @@ TEST_F(IntegrationBasicCancellation, CancelMessageHasSameIdAsOrder) cycle.wait_for_order(common::cancel_order{common::Ticker::ETH, *order_id}); } -TEST_F(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) +TEST_P(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) { - auto& trader1 = - start_wrappers(traders_, AlgoLanguage::python, "cancel_limit_order"); + auto& trader1 = start_wrappers(traders_, GetParam(), "cancel_limit_order"); auto trader2 = traders_.add_trader(0); trader2->modify_holdings(Ticker::ETH, 100.0); TestMatchingCycle cycle{traders_}; @@ -48,10 +47,9 @@ TEST_F(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) EXPECT_EQ(trader1.get_holdings(Ticker::ETH), 0); } -TEST_F(IntegrationBasicCancellation, OneOfTwoOrdersCancelledResultsInMatch) +TEST_P(IntegrationBasicCancellation, OneOfTwoOrdersCancelledResultsInMatch) { - auto& trader1 = - start_wrappers(traders_, AlgoLanguage::python, "partial_cancel_limit_order"); + auto& trader1 = start_wrappers(traders_, GetParam(), "partial_cancel_limit_order"); auto trader2 = traders_.add_trader(0); trader2->modify_holdings(Ticker::ETH, 100.0); TestMatchingCycle cycle{traders_}; @@ -68,4 +66,9 @@ TEST_F(IntegrationBasicCancellation, OneOfTwoOrdersCancelledResultsInMatch) EXPECT_EQ(double{trader2->get_capital_delta()}, 200); EXPECT_EQ(double{trader1.get_holdings(Ticker::ETH)}, 10); } + +INSTANTIATE_TEST_SUITE_P( + IntegrationBasicCancellation, IntegrationBasicCancellation, + ::testing::Values(AlgoLanguage::python, AlgoLanguage::cpp) +); } // namespace nutc::test diff --git a/exchange/test/src/util/macros.hpp b/exchange/test/src/util/macros.hpp index a1870e8e..ec17e393 100644 --- a/exchange/test/src/util/macros.hpp +++ b/exchange/test/src/util/macros.hpp @@ -1,5 +1,6 @@ #include "common/messages_exchange_to_wrapper.hpp" #include "common/messages_wrapper_to_exchange.hpp" +#include "common/types/algorithm/base_algorithm.hpp" #include "common/types/ticker.hpp" #include "exchange/orders/storage/order_storage.hpp" #include "exchange/traders/trader_container.hpp" @@ -9,6 +10,24 @@ using tagged_limit_order = nutc::exchange::tagged_limit_order; using tagged_market_order = nutc::exchange::tagged_market_order; using TraderContainer = nutc::exchange::TraderContainer; +namespace nutc::common { +inline void +PrintTo(const AlgoLanguage& op, std::ostream* os) +{ + switch (op) { + case AlgoLanguage::cpp: + *os << "CPP"; + break; + case AlgoLanguage::python: + *os << "PYTHON"; + break; + default: + *os << "UNKNOWN_LANGUAGE"; + break; + } +} +} // namespace nutc::common + namespace nutc::test { bool validate_match( @@ -84,8 +103,7 @@ order_equality(const common::market_order& order1, const common::market_order& o << "Expected market order with" \ << " ticker =" << (nutc::common::to_string(ticker_)) \ << ", side = " << static_cast(side_) << ", price = " << (price_) \ - << ", quantity = " << (quantity_) << ". Actual update: client_id = " \ - << "" \ + << ", quantity = " << (quantity_) << ". Actual update: client_id = " << "" \ << ", ticker = " << nutc::common::to_string((update).ticker) \ << ", side = " << static_cast((update).side) \ << ", price = " << double((update).price) \