Skip to content

Commit 06c7e5d

Browse files
committed
* added accountSubscriber template class
* removed unnecessary structs
1 parent 3671454 commit 06c7e5d

8 files changed

Lines changed: 218 additions & 303 deletions

File tree

examples/orderbookSubscribe.cpp

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,37 @@
22
#include <spdlog/spdlog.h>
33

44
#include <chrono>
5+
#include <mutex>
56
#include <websocketpp/client.hpp>
67
#include <websocketpp/config/asio_client.hpp>
78

89
#include "mango_v3.hpp"
9-
#include "orderbook/levelOne.hpp"
1010
#include "solana.hpp"
11+
#include "subscriptions/accountSubscriber.hpp"
1112
#include "subscriptions/orderbook.hpp"
12-
#include "subscriptions/trades.hpp"
1313

1414
class updateLogger {
1515
public:
16-
updateLogger(mango_v3::subscription::orderbook& orderbook,
17-
mango_v3::subscription::trades& trades)
16+
updateLogger(
17+
mango_v3::subscription::orderbook& orderbook,
18+
mango_v3::subscription::accountSubscriber<mango_v3::Trades>& trades)
1819
: orderbook(orderbook), trades(trades) {
1920
orderbook.registerUpdateCallback(std::bind(&updateLogger::logUpdate, this));
2021
trades.registerUpdateCallback(std::bind(&updateLogger::logUpdate, this));
21-
orderbook.registerCloseCallback(std::bind(&updateLogger::stop, this));
22-
trades.registerCloseCallback(std::bind(&updateLogger::stop, this));
22+
orderbook.registerCloseCallback(std::bind(&updateLogger::abort, this));
23+
trades.registerCloseCallback(std::bind(&updateLogger::abort, this));
24+
}
2325

26+
void start() {
2427
orderbook.subscribe();
2528
trades.subscribe();
2629
}
2730

2831
void logUpdate() {
32+
std::scoped_lock lock(logMtx);
2933
auto level1Snapshot = orderbook.getLevel1();
3034
if (level1Snapshot->valid()) {
31-
auto latestTrade = trades.getLastTrade();
35+
auto latestTrade = trades.getAccount()->getLastTrade();
3236
spdlog::info("============Update============");
3337
spdlog::info("Latest trade: {}",
3438
*latestTrade ? to_string(*latestTrade) : "not received yet");
@@ -43,39 +47,40 @@ class updateLogger {
4347
}
4448
}
4549

46-
void stop() {
50+
void abort() {
4751
spdlog::error("websocket subscription error");
4852
throw std::runtime_error("websocket subscription error");
4953
}
5054

5155
private:
5256
mango_v3::subscription::orderbook& orderbook;
53-
mango_v3::subscription::trades& trades;
57+
mango_v3::subscription::accountSubscriber<mango_v3::Trades>& trades;
58+
std::mutex logMtx;
5459
};
5560

5661
int main() {
57-
const auto& config = mango_v3::MAINNET;
62+
using namespace mango_v3;
63+
const auto& config = MAINNET;
5864
const solana::rpc::Connection solCon;
59-
const auto group = solCon.getAccountInfo<mango_v3::MangoGroup>(config.group);
65+
const auto group = solCon.getAccountInfo<MangoGroup>(config.group);
6066

6167
const auto symbolIt =
6268
std::find(config.symbols.begin(), config.symbols.end(), "SOL");
6369
const auto marketIndex = symbolIt - config.symbols.begin();
6470
assert(config.symbols[marketIndex] == "SOL");
6571

6672
const auto perpMarketPk = group.perpMarkets[marketIndex].perpMarket;
67-
auto market =
68-
solCon.getAccountInfo<mango_v3::PerpMarket>(perpMarketPk.toBase58());
73+
auto market = solCon.getAccountInfo<PerpMarket>(perpMarketPk.toBase58());
6974
assert(market.mangoGroup.toBase58() == config.group);
7075

71-
mango_v3::subscription::trades trades(market.eventQueue.toBase58());
72-
mango_v3::subscription::orderbook book(market.bids.toBase58(),
73-
market.asks.toBase58());
76+
subscription::accountSubscriber<Trades> trades(market.eventQueue.toBase58());
77+
subscription::orderbook book(market.bids.toBase58(), market.asks.toBase58());
7478

7579
updateLogger logger(book, trades);
80+
logger.start();
7681

7782
while (true) {
78-
std::this_thread::sleep_for(10000s);
83+
std::this_thread::sleep_for(100s);
7984
}
8085

8186
return 0;

include/mango_v3.hpp

Lines changed: 119 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
#pragma once
22

33
#include <cstdint>
4-
#include <mutex>
4+
#include <memory>
55
#include <stack>
66
#include <string>
77

88
#include "fixedp.h"
99
#include "int128.hpp"
10-
#include "orderbook/order.hpp"
1110
#include "solana.hpp"
1211

1312
namespace mango_v3 {
@@ -246,20 +245,79 @@ struct FreeNode {
246245
uint8_t padding[BOOK_NODE_SIZE - 8];
247246
};
248247

248+
struct Order {
249+
Order(uint64_t price, uint64_t quantity) : price(price), quantity(quantity) {}
250+
uint64_t price = 0;
251+
uint64_t quantity = 0;
252+
};
253+
254+
struct L1Orderbook {
255+
uint64_t highestBid = 0;
256+
uint64_t highestBidSize = 0;
257+
uint64_t lowestAsk = 0;
258+
uint64_t lowestAskSize = 0;
259+
double midPoint = 0.0;
260+
double spreadBps = 0.0;
261+
262+
bool valid() const {
263+
return ((highestBid && lowestAsk) && (lowestAsk > highestBid)) ? true
264+
: false;
265+
}
266+
};
267+
249268
class BookSide {
250269
public:
251270
BookSide(Side side) : side(side) {}
252271

272+
bool handleMsg(const nlohmann::json &msg) {
273+
// ignore subscription confirmation
274+
const auto itResult = msg.find("result");
275+
if (itResult != msg.end()) {
276+
return false;
277+
}
278+
279+
const std::string encoded = msg["params"]["result"]["value"]["data"][0];
280+
const std::string decoded = solana::b64decode(encoded);
281+
return update(decoded);
282+
}
283+
284+
Order getBestOrder() const {
285+
return (!orders->empty()) ? orders->front() : Order(0, 0);
286+
}
287+
288+
uint64_t getVolume(uint64_t price) const {
289+
if (side == Side::Buy) {
290+
return getVolume<std::greater_equal<uint64_t>>(price);
291+
} else {
292+
return getVolume<std::less_equal<uint64_t>>(price);
293+
}
294+
}
295+
296+
private:
297+
template <typename Op>
298+
uint64_t getVolume(uint64_t price) const {
299+
Op operation;
300+
uint64_t volume = 0;
301+
for (auto &&order : *orders) {
302+
if (operation(order.price, price)) {
303+
volume += order.quantity;
304+
} else {
305+
break;
306+
}
307+
}
308+
return volume;
309+
}
310+
253311
bool update(const std::string decoded) {
254312
if (decoded.size() != sizeof(BookSideRaw)) {
255313
throw std::runtime_error("invalid response length " +
256314
std::to_string(decoded.size()) + " expected " +
257315
std::to_string(sizeof(BookSideRaw)));
258316
}
259-
memcpy(&raw, decoded.data(), sizeof(BookSideRaw));
317+
memcpy(&(*raw), decoded.data(), sizeof(BookSideRaw));
260318

261-
auto iter = BookSide::BookSideRaw::iterator(side, raw);
262-
book::order_container newOrders;
319+
auto iter = BookSide::BookSideRaw::iterator(side, *raw);
320+
std::vector<Order> newOrders;
263321
while (iter.stack.size() > 0) {
264322
if ((*iter).tag == NodeType::LeafNode) {
265323
const auto leafNode =
@@ -272,37 +330,21 @@ class BookSide {
272330
!leafNode->timeInForce ||
273331
leafNode->timestamp + leafNode->timeInForce < nowUnix;
274332
if (isValid) {
275-
newOrders.orders.emplace_back((uint64_t)(leafNode->key >> 64),
276-
leafNode->quantity);
333+
newOrders.emplace_back((uint64_t)(leafNode->key >> 64),
334+
leafNode->quantity);
277335
}
278336
}
279337
++iter;
280338
}
281339

282-
if (!newOrders.orders.empty()) {
283-
std::scoped_lock lock(mtx);
284-
orders = std::move(newOrders);
340+
if (!newOrders.empty()) {
341+
orders = std::make_shared<std::vector<Order>>(std::move(newOrders));
285342
return true;
286343
} else {
287344
return false;
288345
}
289346
}
290347

291-
book::order getBestOrder() const {
292-
std::scoped_lock lock(mtx);
293-
return orders.getBest();
294-
}
295-
296-
uint64_t getVolume(uint64_t price) const {
297-
std::scoped_lock lock(mtx);
298-
if (side == Side::Buy) {
299-
return orders.getVolume<std::greater_equal<uint64_t>>(price);
300-
} else {
301-
return orders.getVolume<std::less_equal<uint64_t>>(price);
302-
}
303-
}
304-
305-
private:
306348
struct BookSideRaw {
307349
MetaData metaData;
308350
uint64_t bumpIndex;
@@ -348,10 +390,58 @@ class BookSide {
348390
};
349391
};
350392

351-
Side side;
352-
BookSideRaw raw;
353-
book::order_container orders;
354-
mutable std::mutex mtx;
393+
const Side side;
394+
std::shared_ptr<BookSideRaw> raw = std::make_shared<BookSideRaw>();
395+
std::shared_ptr<std::vector<Order>> orders =
396+
std::make_shared<std::vector<Order>>();
397+
};
398+
399+
class Trades {
400+
public:
401+
auto getLastTrade() const { return latestTrade; }
402+
403+
bool handleMsg(const nlohmann::json &msg) {
404+
// ignore subscription confirmation
405+
const auto itResult = msg.find("result");
406+
if (itResult != msg.end()) {
407+
return false;
408+
}
409+
410+
// all other messages are event queue updates
411+
const std::string method = msg["method"];
412+
const int subscription = msg["params"]["subscription"];
413+
const int slot = msg["params"]["result"]["context"]["slot"];
414+
const std::string data = msg["params"]["result"]["value"]["data"][0];
415+
416+
const auto decoded = solana::b64decode(data);
417+
const auto events = reinterpret_cast<const EventQueue *>(decoded.data());
418+
const auto seqNumDiff = events->header.seqNum - lastSeqNum;
419+
const auto lastSlot =
420+
(events->header.head + events->header.count) % EVENT_QUEUE_SIZE;
421+
422+
bool gotLatest = false;
423+
if (events->header.seqNum > lastSeqNum) {
424+
for (int offset = seqNumDiff; offset > 0; --offset) {
425+
const auto slot =
426+
(lastSlot - offset + EVENT_QUEUE_SIZE) % EVENT_QUEUE_SIZE;
427+
const auto &event = events->items[slot];
428+
429+
if (event.eventType == EventType::Fill) {
430+
const auto &fill = (FillEvent &)event;
431+
latestTrade = std::make_shared<uint64_t>(fill.price);
432+
gotLatest = true;
433+
}
434+
// no break; let's iterate to the last fill to get the latest fill order
435+
}
436+
}
437+
438+
lastSeqNum = events->header.seqNum;
439+
return gotLatest;
440+
}
441+
442+
private:
443+
uint64_t lastSeqNum = INT_MAX;
444+
std::shared_ptr<uint64_t> latestTrade = std::make_shared<uint64_t>(0);
355445
};
356446

357447
#pragma pack(pop)

include/orderbook/levelOne.hpp

Lines changed: 0 additions & 21 deletions
This file was deleted.

include/orderbook/order.hpp

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)