From f4cfcd68e118a8ad9af750b1c90da353fb7a5994 Mon Sep 17 00:00:00 2001 From: Steven Ewald Date: Mon, 7 Oct 2024 13:16:52 -0500 Subject: [PATCH] Added order volume limits --- .github/workflows/exchange-ci.yml | 2 +- exchange/config.yml | 47 +++++---- exchange/src/common/types/decimal.cpp | 28 +++--- exchange/src/common/types/decimal.hpp | 27 +++++- .../src/exchange/config/dynamic/config.cpp | 8 +- .../src/exchange/config/dynamic/config.hpp | 1 + exchange/src/exchange/main.cpp | 13 ++- .../matching_cycle/base/base_cycle.cpp | 33 ++++--- .../matching_cycle/base/base_cycle.hpp | 8 +- .../exchange/matching_cycle/dev/dev_cycle.hpp | 8 +- .../matching_cycle/sandbox/sandbox_cycle.hpp | 4 +- .../orders/orderbook/limit_orderbook.cpp | 1 + .../traders/trader_types/algo_trader.hpp | 4 - .../traders/trader_types/bot_trader.cpp | 4 +- .../traders/trader_types/bot_trader.hpp | 32 ------- .../traders/trader_types/generic_trader.hpp | 38 +++++++- exchange/src/wrapper/main.cpp | 2 +- .../test_algos/cpp/many_orders.hpp | 95 +++++++++++++++++++ .../test_algos/python/many_orders.py | 88 +++++++++++++++++ exchange/test/src/integration/tests/basic.cpp | 15 +++ exchange/test/src/util/helpers/test_cycle.cpp | 5 +- exchange/test/src/util/helpers/test_cycle.hpp | 7 +- .../test/src/util/helpers/test_trader.hpp | 4 - 23 files changed, 362 insertions(+), 112 deletions(-) create mode 100644 exchange/test/src/integration/test_algos/cpp/many_orders.hpp create mode 100644 exchange/test/src/integration/test_algos/python/many_orders.py diff --git a/.github/workflows/exchange-ci.yml b/.github/workflows/exchange-ci.yml index 5e55a54e..821df8c3 100644 --- a/.github/workflows/exchange-ci.yml +++ b/.github/workflows/exchange-ci.yml @@ -356,7 +356,7 @@ jobs: run: cmake --install build --config Release --prefix . - name: Move config file - run: cp -r config.yml build/test/config.yml + run: cp config.yml build/test/config.yml - name: Move test algo files run: cp -r -L test/src/integration/test_algos build/test/test_algos diff --git a/exchange/config.yml b/exchange/config.yml index 3a205a28..4e0b7cbd 100644 --- a/exchange/config.yml +++ b/exchange/config.yml @@ -5,6 +5,7 @@ global: order_fee: .0000 sandbox_trial_seconds: 300 + max_cumulative_order_volume: 5000 tickers: - ticker: "ETH" @@ -20,28 +21,26 @@ tickers: std_dev_capital: 2000 # perfect: 5/250000/5000, 25/1000/200 + - ticker: "LTC" + start_price: 50 + bots: + - type: "market_maker" + number_of_bots: 5 + average_capital: 200000 + std_dev_capital: 50000 + - type: "retail" + number_of_bots: 25 + average_capital: 5000 + std_dev_capital: 2000 - # - ticker: "LTC" - # start_price: 200 - # bots: - # - type: "market_maker" - # number_of_bots: 10 - # average_capital: 100000 - # std_dev_capital: 25000 - # - # - type: "retail" - # number_of_bots: 100 - # average_capital: 7500 - # std_dev_capital: 100 - # - # - ticker: "BTC" - # start_price: 50 - # bots: - # - type: "market_maker" - # number_of_bots: 10 - # average_capital: 100000 - # std_dev_capital: 10000 - # - type: "retail" - # number_of_bots: 100 - # average_capital: 2000 - # std_dev_capital: 1000 + - ticker: "BTC" + start_price: 200 + bots: + - type: "market_maker" + number_of_bots: 25 + average_capital: 200000 + std_dev_capital: 50000 + - type: "retail" + number_of_bots: 125 + average_capital: 5000 + std_dev_capital: 2000 diff --git a/exchange/src/common/types/decimal.cpp b/exchange/src/common/types/decimal.cpp index a9bf1c7c..700a9a75 100644 --- a/exchange/src/common/types/decimal.cpp +++ b/exchange/src/common/types/decimal.cpp @@ -2,56 +2,56 @@ namespace nutc::common { -template +template Decimal Decimal::operator-() const { return -value_; } -template +template typename Decimal::decimal_type Decimal::get_underlying() const { return value_; } -template +template void Decimal::set_underlying(decimal_type value) { value_ = value; } -template +template Decimal Decimal::operator-(const Decimal& other) const { return value_ - other.value_; } -template +template Decimal Decimal::operator+(const Decimal& other) const { return value_ + other.value_; } -template +template Decimal Decimal::operator/(const Decimal& other) const { return value_ * MULTIPLIER / other.value_; } -template +template Decimal Decimal::operator*(const Decimal& other) const { return (value_ * other.value_) / MULTIPLIER; } -template +template Decimal& Decimal::operator*=(const Decimal& other) { @@ -60,7 +60,7 @@ Decimal::operator*=(const Decimal& other) return *this; } -template +template Decimal& Decimal::operator/=(const Decimal& other) { @@ -69,7 +69,7 @@ Decimal::operator/=(const Decimal& other) return *this; } -template +template Decimal& Decimal::operator+=(const Decimal& other) { @@ -77,26 +77,26 @@ Decimal::operator+=(const Decimal& other) return *this; } -template +template bool Decimal::operator==(double other) const { return value_ == static_cast(other * MULTIPLIER); } -template +template Decimal::operator double() const { return static_cast(value_) / static_cast(MULTIPLIER); } -template +template Decimal::operator float() const { return static_cast(value_) / static_cast(MULTIPLIER); } -template +template Decimal Decimal::difference(const Decimal& other) const { diff --git a/exchange/src/common/types/decimal.hpp b/exchange/src/common/types/decimal.hpp index 57d5508b..4c69a83c 100644 --- a/exchange/src/common/types/decimal.hpp +++ b/exchange/src/common/types/decimal.hpp @@ -24,7 +24,7 @@ pow10(int pow) } } // namespace detail -template +template class Decimal { using decimal_type = std::int64_t; static constexpr std::int64_t MULTIPLIER = detail::pow10(Scale); @@ -88,6 +88,7 @@ class Decimal { } friend std::hash>; + friend std::numeric_limits>; friend glz::meta>; }; @@ -97,7 +98,7 @@ using decimal_quantity = Decimal; } // namespace nutc::common namespace std { -template +template struct hash> { std::size_t operator()(const nutc::common::Decimal& obj) const @@ -105,10 +106,30 @@ struct hash> { return std::hash{}(obj.value_); } }; + +// TODO: add unit tests +template +class numeric_limits> { +public: + static nutc::common::Decimal + max() + { + return std::numeric_limits< + typename nutc::common::Decimal::decimal_type>::max(); + } + + static nutc::common::Decimal + min() + { + return std::numeric_limits< + typename nutc::common::Decimal::decimal_type>::min(); + } +}; + } // namespace std /// \cond -template +template struct glz::meta> { using t = nutc::common::Decimal; static constexpr auto value = object(&t::value_); diff --git a/exchange/src/exchange/config/dynamic/config.cpp b/exchange/src/exchange/config/dynamic/config.cpp index 1910e493..adc79ad3 100644 --- a/exchange/src/exchange/config/dynamic/config.cpp +++ b/exchange/src/exchange/config/dynamic/config.cpp @@ -99,16 +99,22 @@ Config::get_global_config_(const YAML::Node& full_config) const auto& wait_secs = global["wait_secs"]; const auto& sandbox_secs = global["sandbox_trial_seconds"]; const auto& order_fee = global["order_fee"]; + const auto& max_order_volume = global["max_cumulative_order_volume"]; + if (!starting_capital.IsDefined()) throw_undef_err("global/starting_capital"); if (!wait_secs.IsDefined()) throw_undef_err("global/wait_secs"); if (!sandbox_secs.IsDefined()) throw_undef_err("global/sandbox_trial_seconds"); + if (!max_order_volume.IsDefined()) + throw_undef_err("global/max_cumulative_order_volume"); + return { common::decimal_price(starting_capital.as()), wait_secs.as(), sandbox_secs.as(), - order_fee.IsDefined() ? order_fee.as() : 0 + order_fee.IsDefined() ? order_fee.as() : 0, + common::decimal_quantity(max_order_volume.as()) }; } diff --git a/exchange/src/exchange/config/dynamic/config.hpp b/exchange/src/exchange/config/dynamic/config.hpp index a265ddbb..3e637d1d 100644 --- a/exchange/src/exchange/config/dynamic/config.hpp +++ b/exchange/src/exchange/config/dynamic/config.hpp @@ -17,6 +17,7 @@ struct global_config { const size_t WAIT_SECS; const unsigned int SANDBOX_TRIAL_SECS; const double ORDER_FEE; + const common::decimal_quantity MAX_CUMULATIVE_OPEN_ORDER_VOLUME; }; class Config { diff --git a/exchange/src/exchange/main.cpp b/exchange/src/exchange/main.cpp index 6820eb00..815126cf 100644 --- a/exchange/src/exchange/main.cpp +++ b/exchange/src/exchange/main.cpp @@ -24,15 +24,22 @@ create_cycle(TraderContainer& traders, const auto& mode) const auto& ticker_config = Config::get().get_tickers(); double order_fee = Config::get().constants().ORDER_FEE; auto tickers = TickerContainer(ticker_config, traders); + auto max_order_volume = Config::get().constants().MAX_CUMULATIVE_OPEN_ORDER_VOLUME; switch (mode) { case Mode::normal: - return std::make_unique(tickers, traders, order_fee); + return std::make_unique( + tickers, traders, order_fee, max_order_volume + ); case Mode::sandbox: - return std::make_unique(tickers, traders, order_fee); + return std::make_unique( + tickers, traders, order_fee, max_order_volume + ); case Mode::bots_only: case Mode::dev: - return std::make_unique(tickers, traders, order_fee); + return std::make_unique( + tickers, traders, order_fee, max_order_volume + ); } std::unreachable(); diff --git a/exchange/src/exchange/matching_cycle/base/base_cycle.cpp b/exchange/src/exchange/matching_cycle/base/base_cycle.cpp index 55486998..78d4b3b5 100644 --- a/exchange/src/exchange/matching_cycle/base/base_cycle.cpp +++ b/exchange/src/exchange/matching_cycle/base/base_cycle.cpp @@ -55,20 +55,27 @@ BaseMatchingCycle::match_orders_(std::vector orders) { std::vector matches; - for (OrderVariant& order_variant : orders) { - auto match_incoming_order = [&](OrderT& order) { - auto& ticker_data = tickers_[order.ticker]; - auto& orderbook = ticker_data.get_orderbook(); - if constexpr (std::is_same_v) { - orderbook.remove_order(order.order_id); - } - else { - if (order.quantity <= 0.0) - return; - auto tmp = match_order(order, orderbook, order_fee_); - std::copy(tmp.begin(), tmp.end(), std::back_inserter(matches)); + auto match_incoming_order = [&](OrderT& order) { + auto& ticker_data = tickers_[order.ticker]; + auto& orderbook = ticker_data.get_orderbook(); + if constexpr (std::is_same_v) { + orderbook.remove_order(order.order_id); + } + else { + if (order.quantity <= 0.0) + return; + // TODO: delegate elsewhere + if (order.quantity + order.trader->get_open_bids() + + order.trader->get_open_asks() + > max_cumulative_order_volume_) { + return; } - }; + auto tmp = match_order(order, orderbook, order_fee_); + std::copy(tmp.begin(), tmp.end(), std::back_inserter(matches)); + } + }; + + for (OrderVariant& order_variant : orders) { std::visit(match_incoming_order, order_variant); } return matches; diff --git a/exchange/src/exchange/matching_cycle/base/base_cycle.hpp b/exchange/src/exchange/matching_cycle/base/base_cycle.hpp index 801b8228..28573211 100644 --- a/exchange/src/exchange/matching_cycle/base/base_cycle.hpp +++ b/exchange/src/exchange/matching_cycle/base/base_cycle.hpp @@ -12,12 +12,16 @@ class BaseMatchingCycle : public MatchingCycleInterface { TickerContainer tickers_; TraderContainer& traders_; common::decimal_price order_fee_; + common::decimal_quantity max_cumulative_order_volume_; public: // Require transfer of ownership BaseMatchingCycle( - TickerContainer tickers, TraderContainer& traders, common::decimal_price order_fee - ) : tickers_(std::move(tickers)), traders_(traders), order_fee_(order_fee) + TickerContainer tickers, TraderContainer& traders, + common::decimal_price order_fee, common::decimal_quantity max_order_volume + ) : + tickers_(std::move(tickers)), traders_(traders), order_fee_(order_fee), + max_cumulative_order_volume_{max_order_volume} {} protected: diff --git a/exchange/src/exchange/matching_cycle/dev/dev_cycle.hpp b/exchange/src/exchange/matching_cycle/dev/dev_cycle.hpp index d844ca96..9652866d 100644 --- a/exchange/src/exchange/matching_cycle/dev/dev_cycle.hpp +++ b/exchange/src/exchange/matching_cycle/dev/dev_cycle.hpp @@ -13,8 +13,12 @@ class DevMatchingCycle : public BaseMatchingCycle { TickerMetricsPusher pusher; public: - DevMatchingCycle(TickerContainer tickers, TraderContainer& traders, common::decimal_price order_fee) : - BaseMatchingCycle(std::move(tickers), traders, order_fee), pusher(traders) + DevMatchingCycle( + TickerContainer tickers, TraderContainer& traders, + common::decimal_price order_fee, common::decimal_quantity max_order_volume + ) : + BaseMatchingCycle(std::move(tickers), traders, order_fee, max_order_volume), + pusher(traders) {} protected: diff --git a/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp b/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp index 5da465bd..c69b73ca 100644 --- a/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp +++ b/exchange/src/exchange/matching_cycle/sandbox/sandbox_cycle.hpp @@ -8,8 +8,8 @@ namespace nutc::exchange { class SandboxMatchingCycle : public DevMatchingCycle { public: - SandboxMatchingCycle(TickerContainer tickers, TraderContainer& traders, common::decimal_price order_fee) : - DevMatchingCycle(std::move(tickers), traders, order_fee) + SandboxMatchingCycle(TickerContainer tickers, TraderContainer& traders, common::decimal_price order_fee, common::decimal_quantity max_order_volume) : + DevMatchingCycle(std::move(tickers), traders, order_fee, max_order_volume) {} private: diff --git a/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp b/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp index b1c56a99..94b29ea8 100644 --- a/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp +++ b/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp @@ -2,6 +2,7 @@ #include "common/types/decimal.hpp" #include "common/util.hpp" +#include "exchange/config/dynamic/config.hpp" #include "exchange/orders/storage/order_storage.hpp" namespace nutc::exchange { diff --git a/exchange/src/exchange/traders/trader_types/algo_trader.hpp b/exchange/src/exchange/traders/trader_types/algo_trader.hpp index cb518208..30da57b4 100644 --- a/exchange/src/exchange/traders/trader_types/algo_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/algo_trader.hpp @@ -62,10 +62,6 @@ class AlgoTrader : public GenericTrader { return wrapper_handle_->read_shared(); } - - void - notify_position_change(common::position) final - {} }; } // namespace nutc::exchange diff --git a/exchange/src/exchange/traders/trader_types/bot_trader.cpp b/exchange/src/exchange/traders/trader_types/bot_trader.cpp index 1605dfef..07f7fa8e 100644 --- a/exchange/src/exchange/traders/trader_types/bot_trader.cpp +++ b/exchange/src/exchange/traders/trader_types/bot_trader.cpp @@ -26,12 +26,12 @@ BotTrader::notify_position_change(common::position order) common::decimal_price total_cap = order.price * order.quantity; if (order.side == common::Side::buy) { modify_long_capital(total_cap); - modify_open_bids(order.quantity); } else { modify_short_capital(total_cap); - modify_open_asks(order.quantity); } + + GenericTrader::notify_position_change(order); } } // 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 8d24fcba..c2fcd245 100644 --- a/exchange/src/exchange/traders/trader_types/bot_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/bot_trader.hpp @@ -17,8 +17,6 @@ class BotTrader : public GenericTrader { common::decimal_price short_interest_; common::decimal_price long_interest_; - common::decimal_quantity open_bids_; - common::decimal_quantity open_asks_; IncomingMessageQueue orders_; public: @@ -76,18 +74,6 @@ class BotTrader : public GenericTrader { return short_interest_; } - [[nodiscard]] common::decimal_quantity - get_open_bids() const - { - return open_bids_; - } - - [[nodiscard]] common::decimal_quantity - get_open_asks() const - { - return open_asks_; - } - ~BotTrader() override = default; void notify_position_change(common::position order) final; @@ -156,24 +142,6 @@ class BotTrader : public GenericTrader { long_interest_ += delta; } - /** - * @brief Called by the bot(derived class) when a bid position is opened - */ - void - modify_open_bids(common::decimal_quantity delta) - { - open_bids_ += delta; - } - - /** - * @brief Called by the bot (derived class) when an ask position is opened - */ - void - modify_open_asks(common::decimal_quantity delta) - { - open_asks_ += delta; - } - void send_message(const std::string&) override {} diff --git a/exchange/src/exchange/traders/trader_types/generic_trader.hpp b/exchange/src/exchange/traders/trader_types/generic_trader.hpp index 563228fa..c6d2e547 100644 --- a/exchange/src/exchange/traders/trader_types/generic_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/generic_trader.hpp @@ -15,6 +15,8 @@ class GenericTrader { std::string user_id_; common::decimal_price initial_capital_; common::decimal_price capital_delta_; + common::decimal_quantity open_bids_; + common::decimal_quantity open_asks_; std::array holdings_{}; public: @@ -49,6 +51,30 @@ class GenericTrader { return user_id_; } + [[nodiscard]] common::decimal_quantity + get_open_bids() const + { + return open_bids_; + } + + [[nodiscard]] common::decimal_quantity + get_open_asks() const + { + return open_asks_; + } + + void + modify_open_bids(common::decimal_quantity delta) + { + open_bids_ += delta; + } + + void + modify_open_asks(common::decimal_quantity delta) + { + open_asks_ += delta; + } + // For metrics purposes virtual const std::string& get_type() const = 0; @@ -93,7 +119,17 @@ class GenericTrader { return initial_capital_; } - virtual void notify_position_change(common::position) = 0; + virtual void + notify_position_change(common::position order) + { + if (order.side == common::Side::buy) { + open_bids_ += order.quantity; + } + else { + open_asks_ += order.quantity; + } + } + virtual void notify_match(common::position); virtual void send_message(const std::string&) = 0; diff --git a/exchange/src/wrapper/main.cpp b/exchange/src/wrapper/main.cpp index 0d47ed34..4867754b 100644 --- a/exchange/src/wrapper/main.cpp +++ b/exchange/src/wrapper/main.cpp @@ -24,7 +24,7 @@ void catch_sigterm(int) { quill::flush(); - std::exit(0); + std::terminate(); } int diff --git a/exchange/test/src/integration/test_algos/cpp/many_orders.hpp b/exchange/test/src/integration/test_algos/cpp/many_orders.hpp new file mode 100644 index 00000000..c832f7f4 --- /dev/null +++ b/exchange/test/src/integration/test_algos/cpp/many_orders.hpp @@ -0,0 +1,95 @@ +#include + +#include + +enum class Side { buy = 0, sell = 1 }; +enum class Ticker : std::uint8_t { ETH = 0, BTC = 1, LTC = 2 }; // NOLINT + +/** + * 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 (Side::buy or Side::sell) + * @param ticker Ticker of the order to place (Ticker::ETH, Ticker::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(Side side, Ticker ticker, float 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 (Side::buy or Side::sell) + * @param ticker Ticker of the order to place (Ticker::ETH, Ticker::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( + Side side, Ticker ticker, float quantity, float price, bool ioc = false +); + +bool cancel_order(Ticker ticker, std::int64_t order_id); + +class Strategy { +public: + Strategy() + { + for (int i = 1; i < 21; i++) { + place_limit_order(Side::buy, Ticker::ETH, 1, static_cast(i)); + } + } + + /** + * 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 (Ticker::ETH, Ticker::BTC, + * or "LTC) + * @param side Side of the orders that were matched (Side::buy or Side::sell) + * @param price Price that trade was executed at + * @quantity quantity Volume traded + */ + void + on_trade_update(Ticker ticker, Side side, float quantity, float 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 (Ticker::ETH, Ticker::BTC, or + * "LTC") + * @param side Which orderbook as updated (Side::buy or Side::sell) + * @param price Price of orderbook that has an update + * @param quantity Volume placed into orderbook + */ + void + on_orderbook_update(Ticker ticker, Side side, float quantity, float price) + {} + + /** + * Called whenever one of your orders is filled. + * + * @param ticker Ticker of order that was fulfilled (Ticker::ETH, Ticker::BTC, or + * "LTC") + * @param side Side of order that was fulfilled (Side::buy or Side::sell) + * @param price Price that order was fulfilled at + * @param quantity Amount of capital after fulfilling order + */ + void + on_account_update( + Ticker ticker, Side side, float price, float quantity, float capital_remaining + ) + {} +}; diff --git a/exchange/test/src/integration/test_algos/python/many_orders.py b/exchange/test/src/integration/test_algos/python/many_orders.py new file mode 100644 index 00000000..25fff1f6 --- /dev/null +++ b/exchange/test/src/integration/test_algos/python/many_orders.py @@ -0,0 +1,88 @@ +from enum import Enum + +class Side(Enum): + BUY = 0 + SELL = 1 + +class Ticker(Enum): + ETH = 0 + BTC = 1 + LTC = 2 + +def place_market_order(side: Side, ticker: Ticker, quantity: float) -> None: + return + +def place_limit_order(side: Side, ticker: Ticker, quantity: float, price: float, ioc: bool = False) -> int: + return 0 + +def cancel_order(ticker: Ticker, order_id: int) -> int: + return 0 + +class Strategy: + """Template for a strategy.""" + + def __init__(self) -> None: + """Your initialization code goes here.""" + for i in range(1, 22): + place_limit_order(Side.BUY, Ticker.ETH, 1, i) + + def on_trade_update(self, ticker: Ticker, side: Side, quantity: float, price: float) -> None: + """Called whenever two orders match. Could be one of your orders, or two other people's orders. + + Parameters + ---------- + ticker + Ticker of orders that were matched + side + + price + Price that trade was executed at + quantity + Volume traded + """ + print(f"Python Trade update: {ticker} {side} {price} {quantity}") + + def on_orderbook_update( + self, ticker: Ticker, side: Side, quantity: float, price: float + ) -> None: + """Called whenever the orderbook changes. This could be because of a trade, or because of a new order, or both. + + Parameters + ---------- + ticker + Ticker that has an orderbook update + side + Which orderbook was updated + price + Price of orderbook that has an update + quantity + Volume placed into orderbook + """ + print(f"Python Orderbook update: {ticker} {side} {price} {quantity}") + + def on_account_update( + self, + ticker: Ticker, + side: Side, + quantity: float, + price: float, + capital_remaining: float, + ) -> None: + """Called whenever one of your orders is filled. + + Parameters + ---------- + ticker + Ticker of order that was fulfilled + side + Side of order that was fulfilled + price + Price that order was fulfilled at + quantity + Volume of order that was fulfilled + capital_remaining + Amount of capital after fulfilling order + """ + print( + f"Python Account update: {ticker} {side} {price} {quantity} {capital_remaining}" + ) diff --git a/exchange/test/src/integration/tests/basic.cpp b/exchange/test/src/integration/tests/basic.cpp index 2039f425..de24517f 100644 --- a/exchange/test/src/integration/tests/basic.cpp +++ b/exchange/test/src/integration/tests/basic.cpp @@ -113,6 +113,21 @@ TEST_P(IntegrationBasicAlgo, ManyUpdates) cycle.wait_for_order(limit_order{Ticker::ETH, buy, 10.0, 100.0}); } +TEST_P(IntegrationBasicAlgo, OrderVolumeLimitsPreventGoingAboveLimit) +{ + auto& trader1 = start_wrappers(traders_, GetParam(), "many_orders"); + + 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.on_tick(0); + } + + ASSERT_EQ(static_cast(trader1.get_open_bids()), 10.0); +} + TEST_P(IntegrationBasicAlgo, OnTradeUpdate) { start_wrappers(traders_, GetParam(), "buy_tsla_on_trade"); diff --git a/exchange/test/src/util/helpers/test_cycle.cpp b/exchange/test/src/util/helpers/test_cycle.cpp index ad63b364..b9403238 100644 --- a/exchange/test/src/util/helpers/test_cycle.cpp +++ b/exchange/test/src/util/helpers/test_cycle.cpp @@ -1,8 +1,8 @@ #include "test_cycle.hpp" +#include "common/logging/logging.hpp" #include "common/messages_wrapper_to_exchange.hpp" #include "common/util.hpp" -#include "common/logging/logging.hpp" #include #include @@ -54,6 +54,9 @@ TestMatchingCycle::match_orders_(std::vector orders) // This is kinda bad practice. We shouldn't return an optional based on a template // parameter, we should just return void or the object itself. However, I am lazy and // this is a test function. Peace +// +// Also todo: this doesnt work correctly when we have orders of the same type that need +// to be processed. Rework this entire thing later template std::optional TestMatchingCycle::wait_for_order( diff --git a/exchange/test/src/util/helpers/test_cycle.hpp b/exchange/test/src/util/helpers/test_cycle.hpp index d783caeb..5f92c620 100644 --- a/exchange/test/src/util/helpers/test_cycle.hpp +++ b/exchange/test/src/util/helpers/test_cycle.hpp @@ -1,5 +1,6 @@ #pragma once +#include "common/types/decimal.hpp" #include "common/util.hpp" #include "exchange/matching_cycle/base/base_cycle.hpp" #include "exchange/traders/trader_container.hpp" @@ -14,8 +15,10 @@ class TestMatchingCycle : public exchange::BaseMatchingCycle { public: TestMatchingCycle( - exchange::TraderContainer& traders, common::decimal_price order_fee = 0.0 - ) : exchange::BaseMatchingCycle{{}, traders, order_fee} + exchange::TraderContainer& traders, common::decimal_price order_fee = 0.0, + common::decimal_quantity max_order_volume = + std::numeric_limits::max() + ) : exchange::BaseMatchingCycle{{}, traders, order_fee, max_order_volume} {} // Note: uses tick=0. If using something that relies on tick, it will not work diff --git a/exchange/test/src/util/helpers/test_trader.hpp b/exchange/test/src/util/helpers/test_trader.hpp index e3d01ced..17baecb5 100644 --- a/exchange/test/src/util/helpers/test_trader.hpp +++ b/exchange/test/src/util/helpers/test_trader.hpp @@ -26,10 +26,6 @@ class TestTrader final : public exchange::GenericTrader { send_message(const std::string&) final {} - virtual void - notify_position_change(common::position) final - {} - IncomingMessageQueue read_orders() final {