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
1312namespace 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+
249268class 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)
0 commit comments