diff --git a/exchange/CMakeLists.txt b/exchange/CMakeLists.txt index 72f925cf..5fac60f9 100644 --- a/exchange/CMakeLists.txt +++ b/exchange/CMakeLists.txt @@ -56,8 +56,8 @@ add_library( src/exchange/matching_cycle/base/base_cycle.cpp src/exchange/matching_cycle/cycle_interface.cpp - src/exchange/traders/trader_types/generic_trader.cpp src/exchange/traders/trader_types/bot_trader.cpp + src/exchange/traders/portfolio/trader_portfolio.cpp src/exchange/theo/brownian.cpp diff --git a/exchange/benchmark/src/generic_trader.cpp b/exchange/benchmark/src/generic_trader.cpp index f191de64..7bdd5a92 100644 --- a/exchange/benchmark/src/generic_trader.cpp +++ b/exchange/benchmark/src/generic_trader.cpp @@ -12,9 +12,9 @@ BM_ModifyHoldings(benchmark::State& state) for (auto _ : state) { for (uint32_t i = 0; i < adds; i++) { - trader.modify_holdings(common::Ticker::ETH, 1.0); - trader.modify_holdings(common::Ticker::BTC, 1.0); - trader.modify_holdings(common::Ticker::LTC, 1.0); + trader.get_portfolio().modify_holdings(common::Ticker::ETH, 1.0); + trader.get_portfolio().modify_holdings(common::Ticker::BTC, 1.0); + trader.get_portfolio().modify_holdings(common::Ticker::LTC, 1.0); } } } diff --git a/exchange/benchmark/src/helpers/benchmark_trader.hpp b/exchange/benchmark/src/helpers/benchmark_trader.hpp index f93c2456..2298bd30 100644 --- a/exchange/benchmark/src/helpers/benchmark_trader.hpp +++ b/exchange/benchmark/src/helpers/benchmark_trader.hpp @@ -1,7 +1,7 @@ #pragma once -#include "exchange/traders/trader_types/generic_trader.hpp" #include "common/types/decimal.hpp" +#include "exchange/traders/trader_types/generic_trader.hpp" #include #include @@ -23,12 +23,6 @@ class BenchmarkTrader : public exchange::GenericTrader { benchmark::DoNotOptimize(str.size()); } - void - notify_position_change(common::position change) final - { - benchmark::DoNotOptimize(change); - } - IncomingMessageQueue read_orders() final { diff --git a/exchange/src/common/types/decimal.hpp b/exchange/src/common/types/decimal.hpp index 44cae7c3..6debcd86 100644 --- a/exchange/src/common/types/decimal.hpp +++ b/exchange/src/common/types/decimal.hpp @@ -13,7 +13,7 @@ namespace nutc::common { -namespace detail { +namespace { template consteval T pow10(int pow) @@ -23,13 +23,13 @@ pow10(int pow) } return pow == 0 ? 1 : 10 * pow10(pow - 1); } -} // namespace detail +} // namespace // THIS CLASS PRIORITIZES PRECISION OVER AVOIDING OVERFLOW template class Decimal { using decimal_type = std::int64_t; - static constexpr std::int64_t MULTIPLIER = detail::pow10(Scale); + static constexpr std::int64_t MULTIPLIER = pow10(Scale); decimal_type value_{}; @@ -43,6 +43,12 @@ class Decimal { value_(other.get_underlying() / 100) {} + // TODO: this should be more generic but I don't have time right now + constexpr explicit Decimal(const Decimal& other) + requires(Scale >= 4) + : value_(other.get_underlying() * 100) + {} + Decimal operator-() const; decimal_type get_underlying() const; void set_underlying(decimal_type value); diff --git a/exchange/src/exchange/bots/bot_container.cpp b/exchange/src/exchange/bots/bot_container.cpp index d6584c06..8a77ff15 100644 --- a/exchange/src/exchange/bots/bot_container.cpp +++ b/exchange/src/exchange/bots/bot_container.cpp @@ -18,12 +18,12 @@ BotContainer::generate_orders( { variance_calculator_.record_price(midprice); - common::decimal_price cumulative_interest_limit{}; + common::decimal_price cumulative_interest_limit{}; common::decimal_quantity cumulative_quantity_held{}; for (const auto& bot : bots_) { - cumulative_interest_limit += bot->get_interest_limit(); - cumulative_quantity_held += bot->get_holdings(); + cumulative_interest_limit += bot->get_portfolio().get_capital(); + cumulative_quantity_held += bot->get_portfolio().get_holdings(bot->get_ticker()); } return generate_orders( diff --git a/exchange/src/exchange/bots/bot_types/market_maker.cpp b/exchange/src/exchange/bots/bot_types/market_maker.cpp index fa3d8197..213b2114 100644 --- a/exchange/src/exchange/bots/bot_types/market_maker.cpp +++ b/exchange/src/exchange/bots/bot_types/market_maker.cpp @@ -41,19 +41,15 @@ MarketMakerBot::place_orders_( { // Approximation common::decimal_quantity total_quantity = { - compute_capital_tolerance_() / (theo + spread_offset) + get_portfolio().compute_capital_tolerance() / (theo + spread_offset) }; - // Placing orders on both sides - total_quantity /= 2.0; + // Placing orders on both sides and divide by two + total_quantity /= 4.0; for (const auto& [price_delta, quantity_factor] : PRICE_LEVELS) { decimal_price price = (side == Side::buy) ? theo - price_delta - spread_offset : theo + price_delta + spread_offset; - - if (price <= 0.0) [[unlikely]] - return; - auto order_id = add_limit_order( side, total_quantity * quantity_factor, price, /*ioc=*/false ); diff --git a/exchange/src/exchange/bots/bot_types/retail.cpp b/exchange/src/exchange/bots/bot_types/retail.cpp index c92402ff..24b727fe 100644 --- a/exchange/src/exchange/bots/bot_types/retail.cpp +++ b/exchange/src/exchange/bots/bot_types/retail.cpp @@ -11,7 +11,7 @@ RetailBot::take_action(const shared_bot_state& state) { static std::uniform_real_distribution<> dis{0.0, 1}; - auto p_trade = common::decimal_price{1.0} - get_capital_utilization(); + auto p_trade = common::decimal_price{1.0} - get_portfolio().get_capital_utilization(); double noise_factor = dis(gen_); @@ -25,8 +25,8 @@ RetailBot::take_action(const shared_bot_state& state) common::decimal_price noised_theo = state.THEO + generate_gaussian_noise(0, .1); common::decimal_quantity quantity{ - (common::decimal_price{1.0} - get_capital_utilization()) * get_interest_limit() - / noised_theo + (common::decimal_price{1.0} - get_portfolio().get_capital_utilization()) + * get_portfolio().get_capital() / noised_theo }; quantity *= RETAIL_ORDER_SIZE; diff --git a/exchange/src/exchange/matching/engine.cpp b/exchange/src/exchange/matching/engine.cpp index dbe3af6d..874d3ad7 100644 --- a/exchange/src/exchange/matching/engine.cpp +++ b/exchange/src/exchange/matching/engine.cpp @@ -92,10 +92,10 @@ attempt_match_(OrderPairT& orders, common::decimal_price order_fee) GenericTrader* buyer = buy_order.trader; GenericTrader* seller = sell_order.trader; - if (!buyer->can_leverage() && buyer->get_capital() < total_price) [[unlikely]] + if (!buyer->can_leverage() && buyer->get_portfolio().get_capital() < total_price) [[unlikely]] return glz::unexpected(MatchFailure::buyer_failure); if (!seller->can_leverage() - && seller->get_holdings(buy_order.ticker) < match_quantity) [[unlikely]] + && seller->get_portfolio().get_holdings(buy_order.ticker) < match_quantity) [[unlikely]] return glz::unexpected(MatchFailure::seller_failure); if (buyer == seller) [[unlikely]] { return glz::unexpected(MatchFailure::buyer_failure); diff --git a/exchange/src/exchange/matching/order_pair.hpp b/exchange/src/exchange/matching/order_pair.hpp index 4e4baed3..7cae34f0 100644 --- a/exchange/src/exchange/matching/order_pair.hpp +++ b/exchange/src/exchange/matching/order_pair.hpp @@ -111,12 +111,14 @@ class OrderPair { // It's only used for metrics std::string match_type = fmt::format("{}->{}", seller.trader->get_type(), buyer.trader->get_type()); - common::match m{ + // TODO: can just use TraderPortfolio instead of entire trader + common::match match{ position, buyer.trader->get_id(), seller.trader->get_id(), - buyer.trader->get_capital(), seller.trader->get_capital() + buyer.trader->get_portfolio().get_capital(), + seller.trader->get_portfolio().get_capital() }; - m.match_type = match_type; - return m; + match.match_type = match_type; + return match; } common::decimal_quantity @@ -133,11 +135,11 @@ class OrderPair { CompositeOrderBook& orderbook ) { - get_underlying_order().trader->notify_match( + get_underlying_order().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->notify_match( + get_underlying_order().trader->get_portfolio().notify_match( {match.position.ticker, common::Side::sell, match.position.quantity, match.position.price * (common::decimal_price{1.0} - order_fee)} ); diff --git a/exchange/src/exchange/matching_cycle/base/base_cycle.cpp b/exchange/src/exchange/matching_cycle/base/base_cycle.cpp index 78d4b3b5..eae0a061 100644 --- a/exchange/src/exchange/matching_cycle/base/base_cycle.cpp +++ b/exchange/src/exchange/matching_cycle/base/base_cycle.cpp @@ -65,8 +65,8 @@ BaseMatchingCycle::match_orders_(std::vector orders) if (order.quantity <= 0.0) return; // TODO: delegate elsewhere - if (order.quantity + order.trader->get_open_bids() - + order.trader->get_open_asks() + if (order.quantity + order.trader->get_portfolio().get_open_bids() + + order.trader->get_portfolio().get_open_asks() > max_cumulative_order_volume_) { return; } diff --git a/exchange/src/exchange/metrics/on_tick_metrics.cpp b/exchange/src/exchange/metrics/on_tick_metrics.cpp index 3ab7d20f..b80d43f8 100644 --- a/exchange/src/exchange/metrics/on_tick_metrics.cpp +++ b/exchange/src/exchange/metrics/on_tick_metrics.cpp @@ -3,6 +3,7 @@ #include "common/messages_wrapper_to_exchange.hpp" #include "exchange/metrics/prometheus.hpp" #include "exchange/orders/ticker_data.hpp" +#include "exchange/traders/portfolio/trader_portfolio.hpp" #include "exchange/traders/trader_container.hpp" #include "prometheus.hpp" @@ -17,9 +18,9 @@ TickerMetricsPusher::create_gauge_(const std::string& gauge_name) } Counter -TickerMetricsPusher::create_counter_(const std::string& gauge_name) +TickerMetricsPusher::create_counter_(const std::string& counter_name) { - return ps::BuildCounter().Name(gauge_name).Register(*Prometheus::get_registry()); + return ps::BuildCounter().Name(counter_name).Register(*Prometheus::get_registry()); } void @@ -131,7 +132,7 @@ TickerMetricsPusher::report_trader_stats(const TickerContainer& tickers) { auto report_holdings = [&](const auto& trader) { for (auto [ticker, info] : tickers) { - double amount_held{trader.get_holdings(ticker)}; + double amount_held{trader.get_portfolio().get_holdings(ticker)}; per_trader_holdings_gauge .Add({ {"ticker", common::to_string(ticker)}, @@ -142,23 +143,25 @@ TickerMetricsPusher::report_trader_stats(const TickerContainer& tickers) } }; - auto portfolio_value = [&](const auto& trader) { + auto portfolio_value = [&](const TraderPortfolio& portfolio) { double pnl = 0.0; for (auto [ticker, info] : tickers) { - double amount_held{trader.get_holdings(ticker)}; + double amount_held{portfolio.get_holdings(ticker)}; double midprice{info.get_orderbook().get_midprice()}; pnl += amount_held * midprice; } return pnl; }; + + auto calculate_pnl = [&](const TraderPortfolio& portfolio) { + return portfolio.get_capital_delta() + portfolio_value(portfolio); + }; auto track_trader = [&](GenericTrader& trader) { report_holdings(trader); + auto& portfolio = trader.get_portfolio(); - double capital{trader.get_capital()}; - double pnl{ - trader.get_capital() + portfolio_value(trader) - - trader.get_initial_capital() - }; + double capital{portfolio.get_capital()}; + double pnl{calculate_pnl(portfolio)}; per_trader_pnl_gauge .Add({ diff --git a/exchange/src/exchange/metrics/on_tick_metrics.hpp b/exchange/src/exchange/metrics/on_tick_metrics.hpp index 09175bdb..cb60ee91 100644 --- a/exchange/src/exchange/metrics/on_tick_metrics.hpp +++ b/exchange/src/exchange/metrics/on_tick_metrics.hpp @@ -1,8 +1,8 @@ #pragma once +#include "common/messages_exchange_to_wrapper.hpp" #include "exchange/orders/ticker_container.hpp" #include "exchange/traders/trader_container.hpp" -#include "common/messages_exchange_to_wrapper.hpp" #include #include @@ -42,7 +42,7 @@ class TickerMetricsPusher { void report_matches(const std::vector& orders); private: - Gauge create_gauge_(const std::string& gauge_name); - Counter create_counter_(const std::string& counter_name); + static Gauge create_gauge_(const std::string& gauge_name); + static Counter create_counter_(const std::string& counter_name); }; } // namespace nutc::exchange diff --git a/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp b/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp index 94b29ea8..54d0ef96 100644 --- a/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp +++ b/exchange/src/exchange/orders/orderbook/limit_orderbook.cpp @@ -2,7 +2,6 @@ #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 { @@ -36,7 +35,7 @@ LimitOrderBook::change_quantity( order_list::iterator order, common::decimal_quantity quantity_delta ) { - order->trader->notify_position_change( + order->trader->get_portfolio().notify_position_change( {order->ticker, order->side, quantity_delta, order->price} ); @@ -47,7 +46,7 @@ LimitOrderBook::change_quantity( void LimitOrderBook::remove_order(order_list::iterator order) { - order->trader->notify_position_change( + order->trader->get_portfolio().notify_position_change( {order->ticker, order->side, -(order->quantity), order->price} ); if (order->side == common::Side::buy) @@ -72,7 +71,7 @@ LimitOrderBook::remove_order( LimitOrderBook::order_list::iterator LimitOrderBook::add_order(const tagged_limit_order& order) { - order.trader->notify_position_change( + order.trader->get_portfolio().notify_position_change( {order.ticker, order.side, order.quantity, order.price} ); diff --git a/exchange/src/exchange/traders/portfolio/trader_portfolio.cpp b/exchange/src/exchange/traders/portfolio/trader_portfolio.cpp new file mode 100644 index 00000000..0a1f08ab --- /dev/null +++ b/exchange/src/exchange/traders/portfolio/trader_portfolio.cpp @@ -0,0 +1,131 @@ +#include "trader_portfolio.hpp" + +namespace nutc::exchange { +TraderPortfolio::TraderPortfolio(common::decimal_price initial_capital) : + initial_capital_{initial_capital} +{} + +common::decimal_price +TraderPortfolio::compute_capital_tolerance() const +{ + return (common::decimal_price{1.0} - get_capital_utilization()) + * (get_initial_capital()); +} + +void +TraderPortfolio::notify_match(common::position match) +{ + common::decimal_price total_cap = match.price * match.quantity; + if (match.side == common::Side::buy) { + modify_holdings(match.ticker, match.quantity); + modify_capital(total_cap * -1.0); + } + else { + modify_holdings(match.ticker, -(match.quantity)); + modify_capital(total_cap); + } +} + +void +TraderPortfolio::notify_position_change(common::position order) +{ + decimal_high_precision notional_value = + order.price.high_precision_multiply(order.quantity); + + if (order.side == common::Side::buy) { + open_bids_ += order.quantity; + long_interest_ += notional_value; + } + else { + open_asks_ += order.quantity; + short_interest_ += notional_value; + } +} + +void +TraderPortfolio::modify_capital(common::decimal_price change_in_capital) +{ + capital_delta_ += decimal_high_precision{change_in_capital}; +} + +common::decimal_price +TraderPortfolio::get_capital_delta() const +{ + return common::decimal_price{capital_delta_}; +} + +common::decimal_price +TraderPortfolio::get_long_interest() const +{ + return common::decimal_price{long_interest_}; +} + +common::decimal_price +TraderPortfolio::get_short_interest() const +{ + return common::decimal_price{short_interest_}; +} + +common::decimal_price +TraderPortfolio::get_capital_utilization() const +{ + common::decimal_price capital_util = + (get_long_interest() + get_short_interest()) / get_initial_capital(); + assert(0 <= capital_util && capital_util <= 1.0); + return capital_util; +} + +common::decimal_price +TraderPortfolio::get_initial_capital() const +{ + return initial_capital_; +} + +common::decimal_quantity +TraderPortfolio::get_holdings(common::Ticker ticker) const +{ + auto ticker_index = std::to_underlying(ticker); + assert(0 <= ticker_index && ticker_index < holdings_.size()); + return holdings_[ticker_index]; +} + +common::decimal_quantity +TraderPortfolio::modify_holdings( + common::Ticker ticker, common::decimal_quantity change_in_holdings +) +{ + auto ticker_index = std::to_underlying(ticker); + assert(0 <= ticker_index && ticker_index < holdings_.size()); + return holdings_[ticker_index] += change_in_holdings; +} + +common::decimal_price +TraderPortfolio::get_capital() const +{ + return initial_capital_ + common::decimal_price{capital_delta_}; +} + +common::decimal_quantity +TraderPortfolio::get_open_bids() const +{ + return open_bids_; +} + +common::decimal_quantity +TraderPortfolio::get_open_asks() const +{ + return open_asks_; +} + +void +TraderPortfolio::modify_open_bids(common::decimal_quantity delta) +{ + open_bids_ += delta; +} + +void +TraderPortfolio::modify_open_asks(common::decimal_quantity delta) +{ + open_asks_ += delta; +} +} // namespace nutc::exchange diff --git a/exchange/src/exchange/traders/portfolio/trader_portfolio.hpp b/exchange/src/exchange/traders/portfolio/trader_portfolio.hpp new file mode 100644 index 00000000..0d2b7f5b --- /dev/null +++ b/exchange/src/exchange/traders/portfolio/trader_portfolio.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "common/types/decimal.hpp" +#include "common/types/position.hpp" +#include "common/types/ticker.hpp" + +namespace nutc::exchange { +class TraderPortfolio { + using decimal_price = common::decimal_price; + using decimal_quantity = common::decimal_quantity; + using decimal_high_precision = common::decimal_high_precision; + + decimal_price initial_capital_; + decimal_quantity open_bids_; + decimal_quantity open_asks_; + decimal_high_precision capital_delta_; + decimal_high_precision short_interest_; + decimal_high_precision long_interest_; + std::array holdings_{}; + +public: + explicit TraderPortfolio(decimal_price initial_capital); + + common::decimal_price compute_capital_tolerance() const; + + void notify_match(common::position match); + + void notify_position_change(common::position order); + + void modify_capital(decimal_price change_in_capital); + + decimal_price get_capital_delta() const; + + decimal_price get_long_interest() const; + + decimal_price get_short_interest() const; + + decimal_price get_capital_utilization() const; + + decimal_price get_initial_capital() const; + + decimal_quantity get_holdings(common::Ticker ticker) const; + + decimal_quantity + modify_holdings(common::Ticker ticker, decimal_quantity change_in_holdings); + + decimal_price get_capital() const; + + decimal_quantity get_open_bids() const; + + decimal_quantity get_open_asks() const; + + void modify_open_bids(decimal_quantity delta); + + void modify_open_asks(decimal_quantity delta); +}; +} // 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 c46d6fd6..f9a50551 100644 --- a/exchange/src/exchange/traders/trader_types/bot_trader.cpp +++ b/exchange/src/exchange/traders/trader_types/bot_trader.cpp @@ -18,21 +18,4 @@ BotTrader::generate_gaussian_noise(double mean, double stddev) return distr(gen); } -void -BotTrader::notify_position_change(common::position order) -{ - assert(order.ticker == TICKER); - - common::decimal_high_precision total_cap = - order.price.high_precision_multiply(order.quantity); - if (order.side == common::Side::buy) { - modify_long_capital(total_cap); - } - else { - modify_short_capital(total_cap); - } - - 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 a9e3b121..9a4bb264 100644 --- a/exchange/src/exchange/traders/trader_types/bot_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/bot_trader.hpp @@ -13,22 +13,11 @@ namespace nutc::exchange { class BotTrader : public GenericTrader { const common::Ticker TICKER; - const common::decimal_price INTEREST_LIMIT; - common::decimal_high_precision short_interest_; - common::decimal_high_precision long_interest_; - IncomingMessageQueue orders_; public: - common::decimal_quantity - get_holdings() const - { - return GenericTrader::get_holdings(TICKER); - } - BotTrader(common::Ticker ticker, common::decimal_price interest_limit) : - GenericTrader(generate_user_id(), interest_limit), TICKER(ticker), - INTEREST_LIMIT(interest_limit) + GenericTrader(generate_user_id(), interest_limit), TICKER(ticker) {} BotTrader(const BotTrader& other) = delete; @@ -36,6 +25,12 @@ class BotTrader : public GenericTrader { BotTrader& operator=(const BotTrader& other) = delete; BotTrader& operator=(BotTrader&& other) = delete; + common::Ticker + get_ticker() const + { + return TICKER; + } + bool can_leverage() const final { @@ -46,38 +41,8 @@ class BotTrader : public GenericTrader { disable() final {} - [[nodiscard]] common::decimal_price - get_capital_utilization() const - { - common::decimal_price capital_util = - (get_long_interest() + get_short_interest()) / get_interest_limit(); - assert(capital_util <= 1.0); - // assert(capital_util >= 0); - return capital_util; - } - - [[nodiscard]] common::decimal_price - get_long_interest() const - { - return common::decimal_price{long_interest_}; - } - - [[nodiscard]] common::decimal_price - get_interest_limit() const - { - return INTEREST_LIMIT; - } - - [[nodiscard]] common::decimal_price - get_short_interest() const - { - return common::decimal_price{short_interest_}; - } - ~BotTrader() override = default; - void notify_position_change(common::position order) final; - /** * midprice, theo */ @@ -97,7 +62,9 @@ class BotTrader : public GenericTrader { [[nodiscard]] common::decimal_price compute_net_exposure_() const { - return (get_long_interest() - get_short_interest()); + return ( + get_portfolio().get_long_interest() - get_portfolio().get_short_interest() + ); } [[nodiscard]] common::order_id_t @@ -123,41 +90,16 @@ class BotTrader : public GenericTrader { orders_.emplace_back(common::market_order{TICKER, side, quantity}); } - common::decimal_price - compute_capital_tolerance_() - { - return (common::decimal_price{1.0} - get_capital_utilization()) - * (get_interest_limit() / 2.0); - } - - void - modify_short_capital(common::decimal_high_precision delta) - { - short_interest_ += delta; - } - - void - modify_long_capital(common::decimal_high_precision delta) - { - long_interest_ += delta; - } - void send_message(const std::string&) override {} private: - static inline uint64_t - get_and_increment_user_id() - { - static uint64_t user_id = 0; - return user_id++; - } - static std::string generate_user_id() { - return "BOT_" + std::to_string(get_and_increment_user_id()); + static uint64_t bot_id = 0; + return "BOT_" + std::to_string(bot_id++); } }; diff --git a/exchange/src/exchange/traders/trader_types/generic_trader.cpp b/exchange/src/exchange/traders/trader_types/generic_trader.cpp deleted file mode 100644 index f22e4493..00000000 --- a/exchange/src/exchange/traders/trader_types/generic_trader.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "generic_trader.hpp" - -#include "common/types/decimal.hpp" - -namespace nutc::exchange { - -void -GenericTrader::notify_match(common::position match) -{ - common::decimal_price total_cap = match.price * match.quantity; - if (match.side == common::Side::buy) { - modify_holdings(match.ticker, match.quantity); - modify_capital(total_cap * -1.0); - } - else { - modify_holdings(match.ticker, -(match.quantity)); - modify_capital(total_cap); - } -} - -} // namespace nutc::exchange diff --git a/exchange/src/exchange/traders/trader_types/generic_trader.hpp b/exchange/src/exchange/traders/trader_types/generic_trader.hpp index c6d2e547..d7d8fe5c 100644 --- a/exchange/src/exchange/traders/trader_types/generic_trader.hpp +++ b/exchange/src/exchange/traders/trader_types/generic_trader.hpp @@ -3,6 +3,7 @@ #include "common/messages_wrapper_to_exchange.hpp" #include "common/types/decimal.hpp" #include "common/types/position.hpp" +#include "exchange/traders/portfolio/trader_portfolio.hpp" #include #include @@ -13,15 +14,11 @@ namespace nutc::exchange { 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_{}; + TraderPortfolio state_; public: - explicit GenericTrader(std::string user_id, common::decimal_price capital) : - user_id_(std::move(user_id)), initial_capital_(capital) + explicit GenericTrader(std::string user_id, common::decimal_price initial_capital) : + user_id_(std::move(user_id)), state_{initial_capital} {} GenericTrader(GenericTrader&&) = default; @@ -51,86 +48,21 @@ class GenericTrader { return user_id_; } - [[nodiscard]] common::decimal_quantity - get_open_bids() const + const TraderPortfolio& + get_portfolio() const { - return open_bids_; + return state_; } - [[nodiscard]] common::decimal_quantity - get_open_asks() const + TraderPortfolio& + get_portfolio() { - 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; + return state_; } // For metrics purposes virtual const std::string& get_type() const = 0; - virtual common::decimal_price - get_capital() const - { - return initial_capital_ + capital_delta_; - } - - // TODO: improve with find - common::decimal_quantity - get_holdings(common::Ticker ticker) const - { - auto ticker_index = std::to_underlying(ticker); - assert(ticker_index < holdings_.size()); - return holdings_[ticker_index]; - } - - common::decimal_quantity - modify_holdings(common::Ticker ticker, common::decimal_quantity change_in_holdings) - { - auto ticker_index = std::to_underlying(ticker); - assert(ticker_index < holdings_.size()); - return holdings_[ticker_index] += change_in_holdings; - } - - void - modify_capital(common::decimal_price change_in_capital) - { - capital_delta_ += change_in_capital; - } - - common::decimal_price - get_capital_delta() const - { - return capital_delta_; - } - - common::decimal_price - get_initial_capital() const - { - return initial_capital_; - } - - 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; using IncomingMessageQueue = std::vector; diff --git a/exchange/test/src/integration/tests/basic.cpp b/exchange/test/src/integration/tests/basic.cpp index de24517f..72121dcb 100644 --- a/exchange/test/src/integration/tests/basic.cpp +++ b/exchange/test/src/integration/tests/basic.cpp @@ -24,14 +24,18 @@ TEST_P(IntegrationBasicAlgo, ConfirmOrderReceived) { start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT trader2->add_order(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}); ASSERT_EQ( - double{trader2->get_capital() - trader2->get_initial_capital()}, 100.0 * 10.0 + double{ + trader2->get_portfolio().get_capital() + - trader2->get_portfolio().get_initial_capital() + }, + 100.0 * 10.0 ); } @@ -39,14 +43,17 @@ TEST_P(IntegrationBasicAlgo, ConfirmOrderFeeApplied) { start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT trader2->add_order(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}); ASSERT_EQ( - double{trader2->get_capital() - trader2->get_initial_capital()}, + double{ + trader2->get_portfolio().get_capital() + - trader2->get_portfolio().get_initial_capital() + }, 100.0 * 10.0 / 2 ); } @@ -55,7 +62,7 @@ TEST_P(IntegrationBasicAlgo, RemoveIOCOrder) { auto& trader1 = start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); TestMatchingCycle cycle{traders_}; @@ -74,7 +81,7 @@ TEST_P(IntegrationBasicAlgo, MarketOrderBuy) { start_wrappers(traders_, GetParam(), "buy_market_order_1000"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); TestMatchingCycle cycle{traders_}; @@ -86,9 +93,9 @@ TEST_P(IntegrationBasicAlgo, MarketOrderSell) { auto& trader1 = start_wrappers(traders_, GetParam(), "sell_market_order_1000"); auto trader2 = traders_.add_trader(0); - trader1.modify_holdings(Ticker::ETH, 1000.0); + trader1.get_portfolio().modify_holdings(Ticker::ETH, 1000.0); trader2->add_order({Ticker::ETH, buy, 1.0, 100.0}); - trader2->modify_capital(1000.0); + trader2->get_portfolio().modify_capital(1000.0); TestMatchingCycle cycle{traders_}; @@ -100,7 +107,7 @@ TEST_P(IntegrationBasicAlgo, ManyUpdates) start_wrappers(traders_, GetParam(), "confirm_1000"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 100000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 100000.0); // NOLINT TestMatchingCycle cycle{traders_}; @@ -125,7 +132,7 @@ TEST_P(IntegrationBasicAlgo, OrderVolumeLimitsPreventGoingAboveLimit) cycle.on_tick(0); } - ASSERT_EQ(static_cast(trader1.get_open_bids()), 10.0); + ASSERT_EQ(static_cast(trader1.get_portfolio().get_open_bids()), 10.0); } TEST_P(IntegrationBasicAlgo, OnTradeUpdate) @@ -133,7 +140,7 @@ TEST_P(IntegrationBasicAlgo, OnTradeUpdate) start_wrappers(traders_, GetParam(), "buy_tsla_on_trade"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 10000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 10000.0); // NOLINT TestMatchingCycle cycle{traders_}; @@ -150,7 +157,7 @@ TEST_P(IntegrationBasicAlgo, MultipleLevelOrder) auto& trader1 = start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT TestMatchingCycle cycle{traders_}; @@ -158,13 +165,16 @@ TEST_P(IntegrationBasicAlgo, MultipleLevelOrder) trader2->add_order({Ticker::ETH, sell, 45.0, 1.0}); cycle.wait_for_order(limit_order{Ticker::ETH, buy, 100.0, 10.0}); - ASSERT_EQ(trader1.get_capital() - trader1.get_initial_capital(), -100.0); + ASSERT_EQ( + trader1.get_portfolio().get_capital() - trader1.get_portfolio().get_initial_capital(), + -100.0 + ); } TEST_P(IntegrationBasicAlgo, OnAccountUpdateSell) { auto& trader1 = start_wrappers(traders_, GetParam(), "sell_tsla_on_account"); - trader1.modify_holdings(Ticker::ETH, 1000.0); + 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}); @@ -184,7 +194,7 @@ TEST_P(IntegrationBasicAlgo, OnAccountUpdateBuy) start_wrappers(traders_, GetParam(), "buy_tsla_on_account"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); TestMatchingCycle cycle{traders_}; @@ -206,7 +216,7 @@ TEST_P(IntegrationBasicAlgo, AlgoStartDelay) auto start = std::chrono::high_resolution_clock::now(); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT trader2->add_order({Ticker::ETH, sell, 100.0, 100.0}); TestMatchingCycle cycle{traders_}; @@ -227,7 +237,7 @@ TEST_P(IntegrationBasicAlgo, DisableTrader) { auto& trader1 = start_wrappers(traders_, GetParam(), "buy_tsla_at_100"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 1000.0); // NOLINT + trader2->get_portfolio().modify_holdings(Ticker::ETH, 1000.0); // NOLINT trader2->add_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 46b2b638..cacb5f05 100644 --- a/exchange/test/src/integration/tests/cancellation.cpp +++ b/exchange/test/src/integration/tests/cancellation.cpp @@ -32,7 +32,7 @@ TEST_P(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) { auto& trader1 = start_wrappers(traders_, GetParam(), "cancel_limit_order"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 100.0); + 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}); @@ -42,16 +42,16 @@ TEST_P(IntegrationBasicCancellation, CancelMessagePreventsOrderFromExecuting) cycle.on_tick(0); - EXPECT_EQ(trader1.get_capital_delta(), 0); - EXPECT_EQ(trader2->get_capital_delta(), 0); - EXPECT_EQ(trader1.get_holdings(Ticker::ETH), 0); + EXPECT_EQ(trader1.get_portfolio().get_capital_delta(), 0); + EXPECT_EQ(trader2->get_portfolio().get_capital_delta(), 0); + EXPECT_EQ(trader1.get_portfolio().get_holdings(Ticker::ETH), 0); } TEST_P(IntegrationBasicCancellation, OneOfTwoOrdersCancelledResultsInMatch) { auto& trader1 = start_wrappers(traders_, GetParam(), "partial_cancel_limit_order"); auto trader2 = traders_.add_trader(0); - trader2->modify_holdings(Ticker::ETH, 100.0); + 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}); @@ -62,9 +62,9 @@ TEST_P(IntegrationBasicCancellation, OneOfTwoOrdersCancelledResultsInMatch) cycle.on_tick(0); - EXPECT_EQ(double{trader1.get_capital_delta()}, -200); - EXPECT_EQ(double{trader2->get_capital_delta()}, 200); - EXPECT_EQ(double{trader1.get_holdings(Ticker::ETH)}, 10); + EXPECT_EQ(double{trader1.get_portfolio().get_capital_delta()}, -200); + EXPECT_EQ(double{trader2->get_portfolio().get_capital_delta()}, 200); + EXPECT_EQ(double{trader1.get_portfolio().get_holdings(Ticker::ETH)}, 10); } INSTANTIATE_TEST_SUITE_P( diff --git a/exchange/test/src/unit/matching/basic_matching.cpp b/exchange/test/src/unit/matching/basic_matching.cpp index 635c9c93..92c82634 100644 --- a/exchange/test/src/unit/matching/basic_matching.cpp +++ b/exchange/test/src/unit/matching/basic_matching.cpp @@ -23,9 +23,9 @@ class UnitBasicMatching : public ::testing::Test { void SetUp() override { - trader1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader3.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader3.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } void diff --git a/exchange/test/src/unit/matching/composite_orderbook_test.cpp b/exchange/test/src/unit/matching/composite_orderbook_test.cpp index 33aa28e7..e6b8f6b9 100644 --- a/exchange/test/src/unit/matching/composite_orderbook_test.cpp +++ b/exchange/test/src/unit/matching/composite_orderbook_test.cpp @@ -22,8 +22,8 @@ class UnitCompositeOrderBookTest : public ::testing::Test { void SetUp() override { - trader_1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader_2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader_1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader_2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } nutc::exchange::CompositeOrderBook container_{Ticker::ETH}; diff --git a/exchange/test/src/unit/matching/invalid_orders.cpp b/exchange/test/src/unit/matching/invalid_orders.cpp index 30a118fc..2a97dd91 100644 --- a/exchange/test/src/unit/matching/invalid_orders.cpp +++ b/exchange/test/src/unit/matching/invalid_orders.cpp @@ -22,8 +22,8 @@ class UnitInvalidOrders : public ::testing::Test { void SetUp() override { - trader1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } nutc::exchange::CompositeOrderBook orderbook_{Ticker::ETH}; @@ -37,7 +37,7 @@ class UnitInvalidOrders : public ::testing::Test { TEST_F(UnitInvalidOrders, RemoveThenAddFunds) { - trader1.modify_capital(-TEST_STARTING_CAPITAL); + 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}; @@ -50,7 +50,7 @@ TEST_F(UnitInvalidOrders, RemoveThenAddFunds) matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 0); - trader1.modify_capital(TEST_STARTING_CAPITAL); + trader1.get_portfolio().modify_capital(TEST_STARTING_CAPITAL); // Kept, but not matched matches = add_to_engine_(order2); @@ -64,7 +64,7 @@ TEST_F(UnitInvalidOrders, RemoveThenAddFunds) TEST_F(UnitInvalidOrders, MatchingInvalidFunds) { - trader1.modify_capital(-TEST_STARTING_CAPITAL); + trader1.get_portfolio().modify_capital(-TEST_STARTING_CAPITAL); tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0}; @@ -85,10 +85,10 @@ TEST_F(UnitInvalidOrders, SimpleManyInvalidOrder) TestTrader t3{"C", TEST_STARTING_CAPITAL}; TestTrader t4{"D", TEST_STARTING_CAPITAL}; - t1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - t2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - t3.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - t4.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + t1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + t2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + t3.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + t4.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); tagged_limit_order order1{t1, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order2{t2, Ticker::ETH, buy, 1.0, 1.0}; @@ -115,8 +115,8 @@ TEST_F(UnitInvalidOrders, InvalidSellerHoldings) TestTrader t1{"A", TEST_STARTING_CAPITAL}; TestTrader t2{"B", TEST_STARTING_CAPITAL}; - t1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - t2.modify_holdings(Ticker::ETH, -DEFAULT_QUANTITY); + t1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + t2.get_portfolio().modify_holdings(Ticker::ETH, -DEFAULT_QUANTITY); tagged_limit_order order1{t1, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order2{t2, Ticker::ETH, sell, 1.0, 1.0}; @@ -127,7 +127,7 @@ TEST_F(UnitInvalidOrders, InvalidSellerHoldings) matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 0); - t2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY * 2.0); + t2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY * 2.0); matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 1); } @@ -137,8 +137,8 @@ TEST_F(UnitInvalidOrders, InvalidBuyerHoldingsDoesntStopMatch) TestTrader t1{"A", TEST_STARTING_CAPITAL}; TestTrader t2{"B", TEST_STARTING_CAPITAL}; - t1.modify_holdings(Ticker::ETH, -DEFAULT_QUANTITY); - t2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + t1.get_portfolio().modify_holdings(Ticker::ETH, -DEFAULT_QUANTITY); + t2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); tagged_limit_order order1{t1, Ticker::ETH, buy, 1.0, 1.0}; tagged_limit_order order2{t2, Ticker::ETH, sell, 1.0, 1.0}; diff --git a/exchange/test/src/unit/matching/many_orders.cpp b/exchange/test/src/unit/matching/many_orders.cpp index d52028af..39049f30 100644 --- a/exchange/test/src/unit/matching/many_orders.cpp +++ b/exchange/test/src/unit/matching/many_orders.cpp @@ -24,10 +24,10 @@ class UnitManyOrders : public ::testing::Test { void SetUp() override { - trader1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader3.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader4.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader3.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader4.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } nutc::exchange::CompositeOrderBook orderbook_{Ticker::ETH}; diff --git a/exchange/test/src/unit/matching/match_ioc.cpp b/exchange/test/src/unit/matching/match_ioc.cpp index 9db73d02..9e80f620 100644 --- a/exchange/test/src/unit/matching/match_ioc.cpp +++ b/exchange/test/src/unit/matching/match_ioc.cpp @@ -22,8 +22,8 @@ class UnitMatchIOC : public ::testing::Test { void SetUp() override { - trader1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } nutc::exchange::CompositeOrderBook orderbook_{Ticker::ETH}; @@ -177,7 +177,7 @@ TEST_F(UnitMatchIOC, MultipleIOCOrdersMatchingAtDifferentPriceLevels) TEST_F(UnitMatchIOC, IOCOrderWithInsufficientFunds) { tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0, false}; - trader2.modify_holdings( + trader2.get_portfolio().modify_holdings( Ticker::ETH, -DEFAULT_QUANTITY ); // Remove all ETH holdings from trader2 tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0, true}; @@ -200,7 +200,7 @@ TEST_F(UnitMatchIOC, IOCOrderWithNoCounterpartOrders) TEST_F(UnitMatchIOC, IOCOrderWithNoCounterpartOrdersAndInsufficientFunds) { tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0, true}; - trader1.modify_holdings( + trader1.get_portfolio().modify_holdings( Ticker::ETH, -DEFAULT_QUANTITY ); // Remove all ETH holdings from trader1 @@ -224,10 +224,10 @@ TEST_F(UnitMatchIOC, MultipleIOCOrdersWithNoCounterpartOrdersAndInsufficientFund { tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0, true}; tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0, true}; - trader1.modify_holdings( + trader1.get_portfolio().modify_holdings( Ticker::ETH, -DEFAULT_QUANTITY ); // Remove all ETH holdings from trader1 - trader2.modify_holdings( + trader2.get_portfolio().modify_holdings( Ticker::ETH, -DEFAULT_QUANTITY ); // Remove all ETH holdings from trader2 diff --git a/exchange/test/src/unit/matching/match_market.cpp b/exchange/test/src/unit/matching/match_market.cpp index 146da83d..c0ac1c7a 100644 --- a/exchange/test/src/unit/matching/match_market.cpp +++ b/exchange/test/src/unit/matching/match_market.cpp @@ -24,10 +24,10 @@ class UnitMatchMarket : public ::testing::Test { void SetUp() override { - trader1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader3.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader4.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader3.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader4.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } nutc::exchange::CompositeOrderBook orderbook_{Ticker::ETH}; diff --git a/exchange/test/src/unit/matching/order_fee_matching.cpp b/exchange/test/src/unit/matching/order_fee_matching.cpp index a3f56418..bc0b1beb 100644 --- a/exchange/test/src/unit/matching/order_fee_matching.cpp +++ b/exchange/test/src/unit/matching/order_fee_matching.cpp @@ -22,9 +22,9 @@ class UnitOrderFeeMatching : public ::testing::Test { void SetUp() override { - trader1.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader2.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader3.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader1.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader2.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader3.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); } void @@ -53,8 +53,8 @@ TEST_F(UnitOrderFeeMatching, SimpleMatch) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", sell, 1, 1); - ASSERT_EQ(trader1.get_capital_delta(), -1.5); - ASSERT_EQ(trader2.get_capital_delta(), .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), .5); } TEST_F(UnitOrderFeeMatching, MultipleMatches) @@ -78,15 +78,15 @@ TEST_F(UnitOrderFeeMatching, MultipleMatches) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", sell, 1, 4); - ASSERT_EQ(trader1.get_capital_delta(), -4 * 1.5); - ASSERT_EQ(trader2.get_capital_delta(), 4 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -4 * 1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 4 * .5); matches = add_to_engine_(sell1); ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", sell, 1, 3); - ASSERT_EQ(trader1.get_capital_delta(), -4 * 1.5 + -3 * 1.5); - ASSERT_EQ(trader2.get_capital_delta(), 4 * .5 + 3 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -4 * 1.5 + -3 * 1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 4 * .5 + 3 * .5); } TEST_F(UnitOrderFeeMatching, NoMatchThenMatchBuy) @@ -102,8 +102,8 @@ TEST_F(UnitOrderFeeMatching, NoMatchThenMatchBuy) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "DEF", "ABC", buy, 1, 1); - ASSERT_EQ(trader1.get_capital_delta(), .5); - ASSERT_EQ(trader2.get_capital_delta(), -1.5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), .5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -1.5); } TEST_F(UnitOrderFeeMatching, NoMatchThenMatchSell) @@ -123,9 +123,9 @@ TEST_F(UnitOrderFeeMatching, NoMatchThenMatchSell) ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "GHI", sell, 1, 1); ASSERT_EQ_MATCH(matches[1], Ticker::ETH, "DEF", "GHI", sell, 1, 1); - ASSERT_EQ(trader1.get_capital_delta(), -1.5); - ASSERT_EQ(trader2.get_capital_delta(), -1.5); - ASSERT_EQ(trader3.get_capital_delta(), 1); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -1.5); + ASSERT_EQ(trader3.get_portfolio().get_capital_delta(), 1); } TEST_F(UnitOrderFeeMatching, PassivePriceMatchWithVolume) @@ -139,8 +139,8 @@ TEST_F(UnitOrderFeeMatching, PassivePriceMatchWithVolume) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", sell, 2, 2); - ASSERT_EQ(trader1.get_capital_delta(), -2 * 2 * 1.5); - ASSERT_EQ(trader2.get_capital_delta(), 2 * 2 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -2 * 2 * 1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 2 * 2 * .5); } TEST_F(UnitOrderFeeMatching, PartialFill) @@ -154,8 +154,8 @@ TEST_F(UnitOrderFeeMatching, PartialFill) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "ABC", "DEF", sell, 1, 1); - ASSERT_EQ(trader1.get_capital_delta(), -1 * 1.5); - ASSERT_EQ(trader2.get_capital_delta(), 1 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -1 * 1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 1 * .5); } TEST_F(UnitOrderFeeMatching, MultipleFill) @@ -174,8 +174,8 @@ TEST_F(UnitOrderFeeMatching, MultipleFill) ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "ABC", "DEF", sell, 1, 1); ASSERT_EQ_MATCH(matches.at(1), Ticker::ETH, "ABC", "DEF", sell, 1, 1); - ASSERT_EQ(trader1.get_capital_delta(), -2 * 1 * 1.5); - ASSERT_EQ(trader2.get_capital_delta(), 2 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -2 * 1 * 1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 2 * .5); } TEST_F(UnitOrderFeeMatching, MultiplePartialFill) @@ -194,8 +194,8 @@ TEST_F(UnitOrderFeeMatching, MultiplePartialFill) ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "ABC", "DEF", sell, 1, 1); ASSERT_EQ_MATCH(matches.at(1), Ticker::ETH, "ABC", "DEF", sell, 1, 1); - ASSERT_EQ(trader1.get_capital_delta(), -2 * 1 * 1.5); - ASSERT_EQ(trader2.get_capital_delta(), 2 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), -2 * 1 * 1.5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 2 * .5); } TEST_F(UnitOrderFeeMatching, SimpleMatchReversed) @@ -208,8 +208,8 @@ TEST_F(UnitOrderFeeMatching, SimpleMatchReversed) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "DEF", "ABC", buy, 1, 1); - ASSERT_EQ(trader2.get_capital_delta(), -1 * 1 * 1.5); - ASSERT_EQ(trader1.get_capital_delta(), 1 * .5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -1 * 1 * 1.5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), 1 * .5); } TEST_F(UnitOrderFeeMatching, PassivePriceMatchReversed) @@ -223,8 +223,8 @@ TEST_F(UnitOrderFeeMatching, PassivePriceMatchReversed) ASSERT_EQ(matches.size(), 1); ASSERT_EQ(matches.at(0).position.price, 1.0); ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "DEF", "ABC", buy, 1, 1); - ASSERT_EQ(trader2.get_capital_delta(), -1 * 1 * 1.5); - ASSERT_EQ(trader1.get_capital_delta(), 1 * .5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -1 * 1 * 1.5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), 1 * .5); } TEST_F(UnitOrderFeeMatching, PartialFillReversed) @@ -236,8 +236,8 @@ TEST_F(UnitOrderFeeMatching, PartialFillReversed) matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "DEF", "ABC", buy, 1, 1); - ASSERT_EQ(trader2.get_capital_delta(), -1 * 1 * 1.5); - ASSERT_EQ(trader1.get_capital_delta(), 1 * .5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -1 * 1 * 1.5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), 1 * .5); } TEST_F(UnitOrderFeeMatching, MultipleFillReversed) @@ -256,8 +256,8 @@ TEST_F(UnitOrderFeeMatching, MultipleFillReversed) ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "DEF", "ABC", buy, 1, 1); ASSERT_EQ_MATCH(matches.at(1), Ticker::ETH, "DEF", "ABC", buy, 1, 1); - ASSERT_EQ(trader2.get_capital_delta(), -2 * 1 * 1.5); - ASSERT_EQ(trader1.get_capital_delta(), 2 * .5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -2 * 1 * 1.5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), 2 * .5); } TEST_F(UnitOrderFeeMatching, MultiplePartialFillReversed) @@ -276,13 +276,13 @@ TEST_F(UnitOrderFeeMatching, MultiplePartialFillReversed) ASSERT_EQ_MATCH(matches.at(0), Ticker::ETH, "DEF", "ABC", buy, 1, 1); ASSERT_EQ_MATCH(matches.at(1), Ticker::ETH, "DEF", "ABC", buy, 1, 1); - ASSERT_EQ(trader2.get_capital_delta(), -2 * 1 * 1.5); - ASSERT_EQ(trader1.get_capital_delta(), 2 * .5); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), -2 * 1 * 1.5); + ASSERT_EQ(trader1.get_portfolio().get_capital_delta(), 2 * .5); } TEST_F(UnitOrderFeeMatching, NotEnoughToEnough) { - trader1.modify_capital(-TEST_STARTING_CAPITAL + 1); + 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}; @@ -295,11 +295,10 @@ TEST_F(UnitOrderFeeMatching, NotEnoughToEnough) matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 0); - ASSERT_EQ(trader1.get_capital(), 1); - ASSERT_EQ(trader2.get_capital_delta(), 0); + ASSERT_EQ(trader1.get_portfolio().get_capital(), 1); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 0); - trader1.modify_capital(0.5); - ; + trader1.get_portfolio().modify_capital(0.5); // Kept, but not matched matches = add_to_engine_(order2); @@ -310,13 +309,13 @@ TEST_F(UnitOrderFeeMatching, NotEnoughToEnough) ASSERT_EQ(matches.size(), 1); ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "ABC", "DEF", buy, 1, 1); - ASSERT_EQ(trader1.get_capital(), 0); - ASSERT_EQ(trader2.get_capital_delta(), 1 * .5); + ASSERT_EQ(trader1.get_portfolio().get_capital(), 0); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 1 * .5); } TEST_F(UnitOrderFeeMatching, MatchingInvalidFunds) { - trader1.modify_capital(-TEST_STARTING_CAPITAL + 1); + trader1.get_portfolio().modify_capital(-TEST_STARTING_CAPITAL + 1); tagged_limit_order order1{trader1, Ticker::ETH, buy, 1.0, 1.0, 0}; tagged_limit_order order2{trader2, Ticker::ETH, sell, 1.0, 1.0, 0}; @@ -329,8 +328,8 @@ TEST_F(UnitOrderFeeMatching, MatchingInvalidFunds) matches = add_to_engine_(order2); ASSERT_EQ(matches.size(), 0); - ASSERT_EQ(trader1.get_capital(), 1); - ASSERT_EQ(trader2.get_capital_delta(), 0); + ASSERT_EQ(trader1.get_portfolio().get_capital(), 1); + ASSERT_EQ(trader2.get_portfolio().get_capital_delta(), 0); } TEST_F(UnitOrderFeeMatching, SimpleManyInvalidOrder) @@ -340,10 +339,10 @@ TEST_F(UnitOrderFeeMatching, SimpleManyInvalidOrder) TestTrader trader6{"C", TEST_STARTING_CAPITAL}; TestTrader trader7{"D", TEST_STARTING_CAPITAL}; - trader4.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader5.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader6.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); - trader7.modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader4.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader5.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader6.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); + trader7.get_portfolio().modify_holdings(Ticker::ETH, DEFAULT_QUANTITY); tagged_limit_order order1{trader4, Ticker::ETH, buy, 1.0, 1.0, 0}; tagged_limit_order order2{trader5, Ticker::ETH, buy, 1.0, 1.0, 0}; @@ -364,8 +363,8 @@ TEST_F(UnitOrderFeeMatching, SimpleManyInvalidOrder) ASSERT_EQ_MATCH(matches[0], Ticker::ETH, "A", "D", sell, 1, 1); ASSERT_EQ_MATCH(matches[1], Ticker::ETH, "C", "D", sell, 1, 1); - ASSERT_EQ(trader4.get_capital_delta(), -1.5); - ASSERT_EQ(trader5.get_capital_delta(), 0); - ASSERT_EQ(trader6.get_capital_delta(), -1 * 1 * 1.5); - ASSERT_EQ(trader7.get_capital_delta(), 2 * .5); + ASSERT_EQ(trader4.get_portfolio().get_capital_delta(), -1.5); + ASSERT_EQ(trader5.get_portfolio().get_capital_delta(), 0); + ASSERT_EQ(trader6.get_portfolio().get_capital_delta(), -1 * 1 * 1.5); + ASSERT_EQ(trader7.get_portfolio().get_capital_delta(), 2 * .5); }