OMS
MarketMaker is an algo which is sending orders to OMS.
class MarketMaker {
public:
MarketMaker(Common::Logger *logger, TradeEngine *trade_engine, const
FeatureEngine *feature_engine,
OrderManager *order_manager,
const TradeEngineCfgHashMap &ticker_cfg);
auto onOrderBookUpdate(TickerId ticker_id, Price price, Side side, const M
arketOrderBook *book) noexcept -> void {
logger_->log("%:% %() % ticker:% price:% side:%\n", __FILE__, __LINE__, _
_FUNCTION__,
Common::getCurrentTimeStr(&time_str_), ticker_id, Common::price
ToString(price).c_str(),
Common::sideToString(side).c_str());
const auto bbo = book->getBBO();
const auto fair_price = feature_engine_->getMktPrice();
if (LIKELY(bbo->bid_price_ != Price_INVALID && bbo->ask_price_ != Price_I
NVALID && fair_price != Feature_INVALID)) {
logger_->log("%:% %() % % fair-price:%\n", __FILE__, __LINE__, __FUNC
TION__,
Common::getCurrentTimeStr(&time_str_),
bbo->toString().c_str(), fair_price);
const auto clip = ticker_cfg_.at(ticker_id).clip_;
const auto threshold = ticker_cfg_.at(ticker_id).threshold_;
OMS 1
const auto bid_price = bbo->bid_price_ - (fair_price - bbo->bid_price_ >=
threshold ? 0 : 1);
const auto ask_price = bbo->ask_price_ + (bbo->ask_price_ - fair_price >
= threshold ? 0 : 1);
order_manager_->moveOrders(ticker_id, bid_price, ask_price, clip); // ord
er sent to OMS
}
}
OMS sends the order to TradeEngine, when TradeEngine receives Fills it sends it
to the algorithm also to positon kepper
auto TradeEngine::onOrderUpdate(const Exchange::MEClientResponse *clie
nt_response) noexcept -> void {
logger_.log("%:% %() % %\n", __FILE__, __LINE__, __FUNCTION__, Commo
n::getCurrentTimeStr(&time_str_),
client_response->toString().c_str());
if (UNLIKELY(client_response->type_ == Exchange::ClientResponseType::FI
LLED))
position_keeper_.addFill(client_response);
algoOnOrderUpdate_(client_response);
}
Schema of clinet_response →
struct MEClientResponse {
ClientResponseType type_ = ClientResponseType::INVALID;
OMS 2
ClientId client_id_ = ClientId_INVALID;
TickerId ticker_id_ = TickerId_INVALID;
OrderId client_order_id_ = OrderId_INVALID;
OrderId market_order_id_ = OrderId_INVALID;
Side side_ = Side::INVALID;
Price price_ = Price_INVALID;
Qty exec_qty_ = Qty_INVALID;
Qty leaves_qty_ = Qty_INVALID;
};
addFill method in position keeper calulated pnl for every ticker id.
auto addFill(const Exchange::MEClientResponse *client_response, Logger *
logger) noexcept {
const auto old_position = position_;
const auto side_index = sideToIndex(client_response->side_);
const auto opp_side_index = sideToIndex(client_response->side_ == Side::
BUY ? Side::SELL : Side::BUY);
const auto side_value = sideToValue(client_response->side_);
position_ += client_response->exec_qty_ * side_value;
volume_ += client_response->exec_qty_;
if (old_position * sideToValue(client_response->side_) >= 0) { // opened / i
ncreased position.
open_vwap_[side_index] += (client_response->price_ * client_response->
exec_qty_);
} else { // decreased position.
const auto opp_side_vwap = open_vwap_[opp_side_index] / std::abs(old_
position);
open_vwap_[opp_side_index] = opp_side_vwap * std::abs(position_);
real_pnl_ += std::min(static_cast<int32_t>(client_response->exec_qty_), st
d::abs(old_position)) *
OMS 3
(opp_side_vwap - client_response->price_) * sideToValue(client_r
esponse->side_);
if (position_ * old_position < 0) { // flipped position to opposite sign.
open_vwap_[side_index] = (client_response->price_ * std::abs(position
_));
open_vwap_[opp_side_index] = 0;
}
}
if (!position_) { // flat
open_vwap_[sideToIndex(Side::BUY)] = open_vwap_[sideToIndex(Side::S
ELL)] = 0;
unreal_pnl_ = 0;
} else {
if (position_ > 0)
unreal_pnl_ =
(client_response->price_ - open_vwap_[sideToIndex(Side::BUY)] / st
d::abs(position_)) *
std::abs(position_);
else
unreal_pnl_ =
(open_vwap_[sideToIndex(Side::SELL)] / std::abs(position_) - client_re
sponse->price_) *
std::abs(position_);
}
total_pnl_ = unreal_pnl_ + real_pnl_;
std::string time_str;
logger->log("%:% %() % % %\n", __FILE__, __LINE__, __FUNCTION__, Com
mon::getCurrentTimeStr(&time_str),
toString(), client_response->toString().c_str());
}
OMS 4
The example for calculation is as follows -
🧠 Basic Idea
Every time your order executes (buy or sell), your system must:
1. Update your position (how many units you own — positive for buy, negative
for sell).
2. Update VWAP (average price at which your position was built).
3. Update PnL:
Realized PnL → profit/loss made on trades that are closed (you bought
and then sold, or vice versa).
Unrealized PnL → profit/loss on open positions (you haven’t closed them
yet).
📊 Step-by-Step Example
We’ll follow the same example the book uses.
1️⃣ You buy 10 units @ 100.0
Old position: 0 → New position: +10 (long)
You just opened a new long position.
Item Value
open_vwap (BUY) 10 × 100 = 1000
VWAP (BUY) 1000 / 10 = 100.0
Realized PnL 0 (nothing sold yet)
Unrealized PnL 0 (since no price change yet)
✅ You now own 10 units, bought at an average price of 100.
OMS 5
2️⃣ You buy 10 more @ 90.0
Old position: +10 → New position: +20
You added to your long position.
Item Value
open_vwap (BUY) 1000 + (10 × 90) = 1900
VWAP (BUY) 1900 / 20 = 95.0
Realized PnL 0 (nothing sold yet)
Unrealized PnL (90 - 95) × 20 = -100
📉 You now own 20 units with an average buy price of 95.
Since the last trade happened at 90 (below 95), you have an unrealized loss of
100.
3️⃣ You sell 10 @ 92.0
Old position: +20 → New position: +10
You partially closed your long position.
Item Value
Realized PnL (92 - 95) × 10 = -30
Unrealized PnL (92 - 95) × 10 = -30
open_vwap (BUY) stays 1900
VWAP (BUY) 1900 / 20 = 95.0
💡 You sold 10 units that you bought at 95, making a small realized loss of 30.
You still hold 10 units, worth slightly less (unrealized -30).
4️⃣ You sell 20 @ 97.0
Old position: +10 → New position: -10
You completely sold out your long and went short (you sold more than you
owned).
OMS 6
Item Value
Realized PnL (97 - 95) × 10 = +20 → previous (-30) + 20 = -10
open_vwap (SELL) 97 × 10 = 970
VWAP (SELL) 970 / 10 = 97.0
Unrealized PnL 0
📈 You closed your long (realizing -10 total PnL so far)
and opened a short position of 10 units at 97.
5️⃣ You sell 20 more @ 94.0
Old position: -10 → New position: -30
You increased your short position.
Item Value
open_vwap (SELL) 970 + (20 × 94) = 2850
VWAP (SELL) 2850 / 30 = 95.0
Realized PnL unchanged = -10
Unrealized PnL (95 - 94) × 30 = +30
📉 You’re now short 30 units at avg. 95.
Since price (94) < 95, your short is in profit (+30 unrealized).
6️⃣ You sell 10 more @ 90.0
Old position: -30 → New position: -40
You’re increasing your short again.
Item Value
open_vwap (SELL) 2850 + (10 × 90) = 3750
VWAP (SELL) 3750 / 40 = 93.75
Realized PnL still -10
Unrealized PnL (93.75 - 90) × 40 = +150
OMS 7
💡 You’re short 40 units at avg. 93.75.
Price is 90 → you could buy back lower → unrealized profit +150.
7️⃣ You buy 40 @ 88.0
Old position: -40 → New position: 0
You close your short completely.
Item Value
Realized PnL (93.75 - 88) × 40 = +230 → (-10 + 230) = +220 total
Unrealized PnL 0
open_vwap reset to 0
✅ You’re now flat (no position) with total realized profit = +220.
⚙️ In Code Terms ( PositionInfo::addFill() )
Here’s what the method is doing:
Step Code Logic Meaning
1 Save old_position Remember position before update
Compute side_index / Which side is this execution (BUY or
2
opp_side_index SELL)?
3 Update position_ Add or subtract executed quantity
4 Update open_vwap_ Add (price × qty) if position increased
If position decreased → update Compute realized profit/loss on
5
real_pnl_ closed quantity
If position flipped → reset Starting new position in opposite
6
open_vwap_ direction
Based on VWAP and latest execution
7 Compute unreal_pnl_
price
8 Compute total_pnl_ Sum of realized + unrealized
OMS 8