Skip to content

Commit

Permalink
Market trader engine part
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Mar 7, 2024
1 parent 0c06cf1 commit 73396ad
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 54 deletions.
42 changes: 35 additions & 7 deletions src/trading/common/include/market-trader-engine-context.hpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
#pragma once

#include <span>

#include "exchangeprivateapitypes.hpp"
#include "monetaryamount.hpp"
#include "opened-order.hpp"
#include "stringhelpers.hpp"
#include "trader-command.hpp"

namespace cct {
struct MarketTraderEngineContext {
class MarketTraderEngineContext {
public:
MarketTraderEngineContext(MonetaryAmount startAmountBase, MonetaryAmount startAmountQuote);

void placeNewOrder(TimePoint placedTime, MonetaryAmount volume, MonetaryAmount price, TradeSide side,
MonetaryAmount computeBuyFrom(TraderCommand traderCommand) const;

MonetaryAmount computeSellVolume(TraderCommand traderCommand) const;

void placeBuyOrder(TimePoint placedTime, MonetaryAmount remainingVolume, MonetaryAmount price,
MonetaryAmount matchedVolume = MonetaryAmount{});

auto totalNbOrders() const { return openedOrders.size() + closedOrders.size(); }
void placeBuyTakerOrder(TimePoint placedTime, MonetaryAmount avgPrice, MonetaryAmount matchedVolume);

void placeSellOrder(TimePoint placedTime, MonetaryAmount remainingVolume, MonetaryAmount price,
MonetaryAmount matchedVolume = MonetaryAmount{});

void placeSellTakerOrder(TimePoint placedTime, MonetaryAmount avgPrice, MonetaryAmount matchedVolume);

auto totalNbOrders() const { return _openedOrders.size() + _closedOrders.size(); }

auto nextOrderId() const { return ToString(totalNbOrders()); }

MonetaryAmount availableBaseAmount;
MonetaryAmount availableQuoteAmount;
OpenedOrderVector openedOrders;
ClosedOrderVector closedOrders;
MonetaryAmount availableBaseAmount() const { return _availableBaseAmount; }
MonetaryAmount availableQuoteAmount() const { return _availableQuoteAmount; }

std::span<const OpenedOrder> openedOrders() const { return _openedOrders; }
std::span<const ClosedOrder> closedOrders() const { return _closedOrders; }

bool matchOrder(const OpenedOrder &matchedOrder, MonetaryAmount matchedAmount, MonetaryAmount price,
TimePoint matchedTime);

void eraseNewlyClosedOrders(std::span<const OpenedOrder> newlyClosedOrders);

private:
MonetaryAmount _availableBaseAmount;
MonetaryAmount _availableQuoteAmount;
OpenedOrderVector _openedOrders;
ClosedOrderVector _closedOrders;
};
} // namespace cct
2 changes: 1 addition & 1 deletion src/trading/common/include/market-trader-engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class MarketTraderEngine {
void buy(const MarketOrderBook &marketOrderBook, TraderCommand traderCommand);
void sell(const MarketOrderBook &marketOrderBook, TraderCommand traderCommand);

void matchOpenedOrdersWith(const MarketOrderBook &marketOrderBook);
void checkOpenedOrdersMatching(const MarketOrderBook &marketOrderBook);

AbstractMarketTrader *_pMarketTrader = nullptr;
Market _market;
Expand Down
73 changes: 69 additions & 4 deletions src/trading/common/src/market-trader-engine-context.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,79 @@
#include "market-trader-engine-context.hpp"

#include "monetaryamount.hpp"
#include "timedef.hpp"
#include "trader-command.hpp"
#include "tradeside.hpp"

namespace cct {
MarketTraderEngineContext::MarketTraderEngineContext(MonetaryAmount startAmountBase, MonetaryAmount startAmountQuote)
: availableBaseAmount(startAmountBase), availableQuoteAmount(startAmountQuote) {}
: _availableBaseAmount(startAmountBase), _availableQuoteAmount(startAmountQuote) {}

void MarketTraderEngineContext::placeNewOrder(TimePoint placedTime, MonetaryAmount volume, MonetaryAmount price,
TradeSide side, MonetaryAmount matchedVolume) {
openedOrders.emplace_back(nextOrderId(), matchedVolume, volume, price, placedTime, side);
MonetaryAmount MarketTraderEngineContext::computeBuyFrom(TraderCommand traderCommand) const {
return (_availableQuoteAmount * traderCommand.amountIntensityPercentage()) / 100;
}

MonetaryAmount MarketTraderEngineContext::computeSellVolume(TraderCommand traderCommand) const {
return (_availableBaseAmount * traderCommand.amountIntensityPercentage()) / 100;
}

void MarketTraderEngineContext::placeBuyOrder(TimePoint placedTime, MonetaryAmount remainingVolume,
MonetaryAmount price, MonetaryAmount matchedVolume) {
_availableQuoteAmount -= matchedVolume.toNeutral() * price;
_openedOrders.emplace_back(nextOrderId(), matchedVolume, remainingVolume, price, placedTime, TradeSide::kBuy);
if (remainingVolume == 0) {
_openedOrders.emplace_back(nextOrderId(), matchedVolume, remainingVolume, price, placedTime, TradeSide::kBuy);
} else {
_closedOrders.emplace_back(nextOrderId(), matchedVolume, price, placedTime, placedTime, TradeSide::kBuy);
}
}

void MarketTraderEngineContext::placeBuyTakerOrder(TimePoint placedTime, MonetaryAmount avgPrice,
MonetaryAmount matchedVolume) {
_availableQuoteAmount -= matchedVolume.toNeutral() * avgPrice;
_closedOrders.emplace_back(nextOrderId(), matchedVolume, avgPrice, placedTime, placedTime, TradeSide::kBuy);
}

void MarketTraderEngineContext::placeSellOrder(TimePoint placedTime, MonetaryAmount remainingVolume,
MonetaryAmount price, MonetaryAmount matchedVolume) {
_availableBaseAmount -= matchedVolume;
_openedOrders.emplace_back(nextOrderId(), matchedVolume, remainingVolume, price, placedTime, TradeSide::kSell);
}

void MarketTraderEngineContext::placeSellTakerOrder(TimePoint placedTime, MonetaryAmount avgPrice,
MonetaryAmount matchedVolume) {
_availableBaseAmount -= matchedVolume;
_closedOrders.emplace_back(nextOrderId(), matchedVolume, avgPrice, placedTime, placedTime, TradeSide::kSell);
}

bool MarketTraderEngineContext::matchOrder(const OpenedOrder &matchedOrder, MonetaryAmount matchedAmount,
MonetaryAmount price, TimePoint matchedTime) {
_closedOrders.emplace_back(matchedOrder.id(), matchedAmount, price, matchedOrder.placedTime(), matchedTime,
matchedOrder.side());

if (matchedOrder.remainingVolume() == matchedAmount) {
// no more opened order, we can mark it for remove from the opened orders list
return true;
}

// Adjust remaining amount of the opened order
auto openedOrderIt = std::ranges::find_if(
_openedOrders, [&matchedOrder](const auto &openedOrder) { return matchedOrder.id() == openedOrder.id(); });

*openedOrderIt = OpenedOrder(matchedOrder.id(), matchedOrder.matchedVolume() + matchedAmount,
matchedOrder.remainingVolume() - matchedAmount, matchedOrder.price(),
matchedOrder.placedTime(), matchedOrder.side());

return false;
}

void MarketTraderEngineContext::eraseNewlyClosedOrders(std::span<const OpenedOrder> newlyClosedOrders) {
const auto [first, last] = std::ranges::remove_if(_openedOrders, [newlyClosedOrders](const OpenedOrder &openedOrder) {
return std::ranges::any_of(newlyClosedOrders, [&openedOrder](const auto &newlyClosedOrder) {
return openedOrder.id() == newlyClosedOrder.id();
});
});
_openedOrders.erase(first, last);
}

} // namespace cct
76 changes: 34 additions & 42 deletions src/trading/common/src/market-trader-engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ void MarketTraderEngine::tradeRange(std::span<const MarketOrderBook> marketOrder
auto tradesBeg = trades.begin();

for (const MarketOrderBook &marketOrderBook : marketOrderBooks) {
// First check opened orders status with new market order book data that may match some
checkOpenedOrdersMatching(marketOrderBook);

// We expect market data (order books and trades) to be sorted by time
const auto marketOrderBookTs = marketOrderBook.time();

Expand Down Expand Up @@ -76,27 +79,28 @@ void MarketTraderEngine::placeCommand(const MarketOrderBook &marketOrderBook, Tr
}

void MarketTraderEngine::buy(const MarketOrderBook &marketOrderBook, TraderCommand traderCommand) {
MonetaryAmount from =
(_marketTraderEngineContext.availableQuoteAmount * traderCommand.amountIntensityPercentage()) / 100;
const MonetaryAmount from = _marketTraderEngineContext.computeBuyFrom(traderCommand);

switch (traderCommand.priceStrategy()) {
case PriceStrategy::kMaker: {
MonetaryAmount price = marketOrderBook.highestBidPrice();
MonetaryAmount volume(from / price, _market.base());
const MonetaryAmount price = marketOrderBook.highestBidPrice();
const MonetaryAmount volume(from / price, _market.base());

_marketTraderEngineContext.placeNewOrder(marketOrderBook.time(), volume, price, TradeSide::kBuy);
_marketTraderEngineContext.placeBuyOrder(marketOrderBook.time(), volume, price);
break;
}
case PriceStrategy::kNibble: {
MonetaryAmount price = marketOrderBook.lowestAskPrice();
MonetaryAmount volume(from / price, _market.base());
MonetaryAmount matchedVolume = std::min(marketOrderBook.amountAtAskPrice(), volume);
const MonetaryAmount price = marketOrderBook.lowestAskPrice();
const MonetaryAmount volume(from / price, _market.base());
const MonetaryAmount matchedVolume = std::min(marketOrderBook.amountAtAskPrice(), volume);

_marketTraderEngineContext.placeNewOrder(marketOrderBook.time(), volume - matchedVolume, price, TradeSide::kBuy,
matchedVolume);
_marketTraderEngineContext.placeBuyOrder(marketOrderBook.time(), volume - matchedVolume, price, matchedVolume);
break;
}
case PriceStrategy::kTaker: {
const auto [totalMatchedAmount, avgPrice] = marketOrderBook.avgPriceAndMatchedAmountTaker(from);

_marketTraderEngineContext.placeBuyTakerOrder(marketOrderBook.time(), avgPrice, totalMatchedAmount);
break;
}
default:
Expand All @@ -105,55 +109,43 @@ void MarketTraderEngine::buy(const MarketOrderBook &marketOrderBook, TraderComma
}

void MarketTraderEngine::sell(const MarketOrderBook &marketOrderBook, TraderCommand traderCommand) {
MonetaryAmount volume =
(_marketTraderEngineContext.availableBaseAmount * traderCommand.amountIntensityPercentage()) / 100;
const MonetaryAmount volume = _marketTraderEngineContext.computeSellVolume(traderCommand);
switch (traderCommand.priceStrategy()) {
case PriceStrategy::kMaker: {
MonetaryAmount price = marketOrderBook.lowestAskPrice();
const MonetaryAmount price = marketOrderBook.lowestAskPrice();

_marketTraderEngineContext.placeNewOrder(marketOrderBook.time(), volume, price, TradeSide::kSell);
_marketTraderEngineContext.placeSellOrder(marketOrderBook.time(), volume, price);
break;
}
case PriceStrategy::kNibble: {
MonetaryAmount price = marketOrderBook.highestBidPrice();
MonetaryAmount matchedVolume = std::min(marketOrderBook.amountAtBidPrice(), volume);
const MonetaryAmount price = marketOrderBook.highestBidPrice();
const MonetaryAmount matchedVolume = std::min(marketOrderBook.amountAtBidPrice(), volume);

_marketTraderEngineContext.placeNewOrder(marketOrderBook.time(), volume - matchedVolume, price, TradeSide::kSell,
matchedVolume);
_marketTraderEngineContext.placeSellOrder(marketOrderBook.time(), volume - matchedVolume, price, matchedVolume);
break;
}
case PriceStrategy::kTaker:
case PriceStrategy::kTaker: {
const auto [totalMatchedAmount, avgPrice] = marketOrderBook.avgPriceAndMatchedAmountTaker(volume);

_marketTraderEngineContext.placeSellTakerOrder(marketOrderBook.time(), avgPrice, totalMatchedAmount);
break;
}
default:
unreachable();
}
}

void MarketTraderEngine::matchOpenedOrdersWith(const MarketOrderBook &marketOrderBook) {
void MarketTraderEngine::checkOpenedOrdersMatching(const MarketOrderBook &marketOrderBook) {
_newlyClosedOrders.clear();
for (OpenedOrder &openedOrder : _marketTraderEngineContext.openedOrders) {
const auto matchedParts =
marketOrderBook.computeMatchedParts(openedOrder.side(), openedOrder.remainingVolume(), openedOrder.price());

for (const auto &[price, amount] : matchedParts) {
_marketTraderEngineContext.closedOrders.emplace_back(openedOrder.id(), amount, price, openedOrder.placedTime(),
marketOrderBook.time(), openedOrder.side());

if (openedOrder.remainingVolume() == amount) {
_newlyClosedOrders.push_back(openedOrder);
} else {
openedOrder =
OpenedOrder(openedOrder.id(), openedOrder.matchedVolume() + amount, openedOrder.remainingVolume() - amount,
openedOrder.price(), openedOrder.placedTime(), openedOrder.side());
}
for (const OpenedOrder &openedOrder : _marketTraderEngineContext.openedOrders()) {
const auto [matchedVolume, avgPrice] = marketOrderBook.avgPriceAndMatchedVolume(
openedOrder.side(), openedOrder.remainingVolume(), openedOrder.price());

if (_marketTraderEngineContext.matchOrder(openedOrder, matchedVolume, avgPrice, marketOrderBook.time())) {
_newlyClosedOrders.push_back(openedOrder);
}
}
const auto [first, last] =
std::ranges::remove_if(_marketTraderEngineContext.openedOrders, [this](const OpenedOrder &openedOrder) {
return std::ranges::any_of(_newlyClosedOrders, [&openedOrder](const auto &newlyClosedOrder) {
return openedOrder.id() == newlyClosedOrder.id();
});
});
_marketTraderEngineContext.openedOrders.erase(first, last);

_marketTraderEngineContext.eraseNewlyClosedOrders(_newlyClosedOrders);
}
} // namespace cct

0 comments on commit 73396ad

Please sign in to comment.