Skip to content

Commit

Permalink
Merge pull request #310 from northwesternfintech/portfolio-class
Browse files Browse the repository at this point in the history
Abstracted portfolio stats from GenericTrader to TraderPortfolio
  • Loading branch information
stevenewald authored Oct 14, 2024
2 parents f354047 + ce280a9 commit b4c8233
Show file tree
Hide file tree
Showing 28 changed files with 381 additions and 348 deletions.
2 changes: 1 addition & 1 deletion exchange/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions exchange/benchmark/src/generic_trader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
8 changes: 1 addition & 7 deletions exchange/benchmark/src/helpers/benchmark_trader.hpp
Original file line number Diff line number Diff line change
@@ -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 <benchmark/benchmark.h>
#include <fmt/format.h>
Expand All @@ -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
{
Expand Down
12 changes: 9 additions & 3 deletions exchange/src/common/types/decimal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace nutc::common {

namespace detail {
namespace {
template <typename T>
consteval T
pow10(int pow)
Expand All @@ -23,13 +23,13 @@ pow10(int pow)
}
return pow == 0 ? 1 : 10 * pow10<T>(pow - 1);
}
} // namespace detail
} // namespace

// THIS CLASS PRIORITIZES PRECISION OVER AVOIDING OVERFLOW
template <std::uint8_t Scale>
class Decimal {
using decimal_type = std::int64_t;
static constexpr std::int64_t MULTIPLIER = detail::pow10<decimal_type>(Scale);
static constexpr std::int64_t MULTIPLIER = pow10<decimal_type>(Scale);

decimal_type value_{};

Expand All @@ -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<Scale - 2>& other)
requires(Scale >= 4)
: value_(other.get_underlying() * 100)
{}

Decimal operator-() const;
decimal_type get_underlying() const;
void set_underlying(decimal_type value);
Expand Down
6 changes: 3 additions & 3 deletions exchange/src/exchange/bots/bot_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
10 changes: 3 additions & 7 deletions exchange/src/exchange/bots/bot_types/market_maker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down
6 changes: 3 additions & 3 deletions exchange/src/exchange/bots/bot_types/retail.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_);

Expand All @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions exchange/src/exchange/matching/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 8 additions & 6 deletions exchange/src/exchange/matching/order_pair.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -133,11 +135,11 @@ class OrderPair {
CompositeOrderBook& orderbook
)
{
get_underlying_order<side::buy>().trader->notify_match(
get_underlying_order<side::buy>().trader->get_portfolio().notify_match(
{match.position.ticker, common::Side::buy, match.position.quantity,
match.position.price * (common::decimal_price{1.0} + order_fee)}
);
get_underlying_order<side::sell>().trader->notify_match(
get_underlying_order<side::sell>().trader->get_portfolio().notify_match(
{match.position.ticker, common::Side::sell, match.position.quantity,
match.position.price * (common::decimal_price{1.0} - order_fee)}
);
Expand Down
4 changes: 2 additions & 2 deletions exchange/src/exchange/matching_cycle/base/base_cycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ BaseMatchingCycle::match_orders_(std::vector<OrderVariant> 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;
}
Expand Down
23 changes: 13 additions & 10 deletions exchange/src/exchange/metrics/on_tick_metrics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand Down Expand Up @@ -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)},
Expand All @@ -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({
Expand Down
6 changes: 3 additions & 3 deletions exchange/src/exchange/metrics/on_tick_metrics.hpp
Original file line number Diff line number Diff line change
@@ -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 <hash_table7.hpp>
#include <prometheus/counter.h>
Expand Down Expand Up @@ -42,7 +42,7 @@ class TickerMetricsPusher {
void report_matches(const std::vector<common::match>& 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
7 changes: 3 additions & 4 deletions exchange/src/exchange/orders/orderbook/limit_orderbook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}
);

Expand All @@ -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)
Expand All @@ -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}
);

Expand Down
Loading

0 comments on commit b4c8233

Please sign in to comment.