Skip to content

Example orderbook subscribe sol. #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8629f99
squashed:
papadpickle Feb 22, 2022
8d04f8a
* using MAINNET.endpoint instead of magic numbers
papadpickle Mar 1, 2022
587e1aa
* added on_close callbacks to handle error in wss connections and sto…
papadpickle Mar 2, 2022
46f206a
style fixup
Mar 2, 2022
2ade336
replaced mtx with shared_ptr trick
papadpickle Mar 4, 2022
3671454
style fixup
Mar 4, 2022
06c7e5d
* added accountSubscriber template class
papadpickle Mar 5, 2022
400efea
minor interface change
papadpickle Apr 1, 2022
2efe235
Merge branch 'main' into example_orderbook_subscribe_sol
papadpickle Apr 1, 2022
3a514b0
style fixup
Apr 1, 2022
c644ad2
Merge branch 'main' into example_orderbook_subscribe_sol
papadpickle May 19, 2022
d014f4c
merged upstream changes and minor improvement
papadpickle May 19, 2022
17946c9
style fixup
May 19, 2022
701d150
merge upstream
papadpickle May 21, 2022
a8b9e1a
decoding the message at account subscriber before passing the data to…
papadpickle May 21, 2022
322b78c
style fixup
May 21, 2022
f8a589f
corrected expired order culling
papadpickle May 22, 2022
b84d2c4
storing whole fill event in trades instead of only price. client can …
papadpickle May 22, 2022
155320f
using leafNode as order struct itself
papadpickle May 22, 2022
e625ded
Merge branch 'mschneider:main' into origin/example_orderbook_subscrib…
papadpickle May 22, 2022
b77315d
added NativeToUi conversion functionality
papadpickle May 24, 2022
d886062
minor log adjustment
papadpickle May 30, 2022
7ac7765
added utest for NativeToUi conversions
papadpickle Jun 4, 2022
1ade8c2
strict concurrency mtxing
papadpickle Jun 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ add_executable(example-get-account-info getAccountInfo.cpp)
add_executable(example-account-subscribe accountSubscribe.cpp)
add_executable(example-send-transaction sendTransaction.cpp)
add_executable(example-place-order placeOrder.cpp)
add_executable(example-orderbook-subscribe orderbookSubscribe.cpp)

# link
target_link_libraries(example-get-account-info ${CONAN_LIBS} sol)
target_link_libraries(example-account-subscribe ${CONAN_LIBS} sol)
target_link_libraries(example-send-transaction ${CONAN_LIBS} sol)
target_link_libraries(example-place-order ${CONAN_LIBS} sol)
target_link_libraries(example-place-order ${CONAN_LIBS} sol)
target_link_libraries(example-orderbook-subscribe ${CONAN_LIBS} sol)
84 changes: 84 additions & 0 deletions examples/orderbookSubscribe.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include <cpr/cpr.h>
#include <spdlog/spdlog.h>

#include <chrono>
#include <mutex>
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_client.hpp>

#include "mango_v3.hpp"
#include "orderbook/levelOne.hpp"
#include "orderbook/orderbook.hpp"
#include "solana.hpp"
#include "subscriptions/bookSide.hpp"
#include "subscriptions/trades.hpp"

class updateLogger {
public:
updateLogger(mango_v3::orderbook::book& orderbook,
mango_v3::subscription::trades& trades)
: orderbook(orderbook), trades(trades) {
orderbook.registerUpdateCallback(std::bind(&updateLogger::logUpdate, this));
trades.registerUpdateCallback(std::bind(&updateLogger::logUpdate, this));
}

void start() {
orderbook.subscribe();
trades.subscribe();
}

void logUpdate() {
const std::scoped_lock lock(updateMtx);
auto level1Snapshot = orderbook.getLevel1();
if (level1Snapshot.valid()) {
spdlog::info("============Update============");
spdlog::info("Latest trade: {}", trades.getLastTrade()
? to_string(trades.getLastTrade())
: "not received yet");
spdlog::info("Bid-Ask {}-{}", level1Snapshot.highestBid,
level1Snapshot.lowestAsk);
spdlog::info("MidPrice: {}", level1Snapshot.midPoint);
spdlog::info("Spread: {0:.2f} bps", level1Snapshot.spreadBps);

constexpr auto depth = 2;
spdlog::info("Market depth -{}%: {}", depth, orderbook.getDepth(-depth));
spdlog::info("Market depth +{}%: {}", depth, orderbook.getDepth(depth));
}
}

private:
std::mutex updateMtx;
mango_v3::orderbook::book& orderbook;
mango_v3::subscription::trades& trades;
};

int main() {
const auto& config = mango_v3::MAINNET;
const solana::rpc::Connection solCon;
const auto group = solCon.getAccountInfo<mango_v3::MangoGroup>(config.group);

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

const auto perpMarketPk = group.perpMarkets[marketIndex].perpMarket;
auto market =
solCon.getAccountInfo<mango_v3::PerpMarket>(perpMarketPk.toBase58());
assert(market.mangoGroup.toBase58() == config.group);

mango_v3::subscription::bookSide bids(mango_v3::Buy, market.bids.toBase58());
mango_v3::subscription::bookSide asks(mango_v3::Sell, market.asks.toBase58());
mango_v3::subscription::trades trades(market.eventQueue.toBase58());

mango_v3::orderbook::book orderbook(bids, asks);

updateLogger logger(orderbook, trades);
logger.start();

while (true) {
}
return 0;
}

// void updateReceived() {}
92 changes: 92 additions & 0 deletions include/mango_v3.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <cstdint>
#include <stack>
#include <string>

#include "fixedp.h"
Expand All @@ -15,6 +16,8 @@ const int MAX_PAIRS = 15;
const int QUOTE_INDEX = 15;
const int EVENT_SIZE = 200;
const int EVENT_QUEUE_SIZE = 256;
const int BOOK_NODE_SIZE = 88;
const int BOOK_SIZE = 1024;

struct Config {
std::string endpoint;
Expand Down Expand Up @@ -135,8 +138,10 @@ struct EventQueueHeader {
uint64_t seqNum;
};

// todo: change to scoped enum class
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, want to open a new issue?

Copy link
Contributor Author

@papadpickle papadpickle Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. #22

enum EventType : uint8_t { Fill, Out, Liquidate };

// todo: change to scoped enum class
enum Side : uint8_t { Buy, Sell };

struct AnyEvent {
Expand Down Expand Up @@ -197,6 +202,93 @@ struct EventQueue {
AnyEvent items[EVENT_QUEUE_SIZE];
};

// todo: change to scoped enum class
enum NodeType : uint32_t {
Uninitialized = 0,
InnerNode,
LeafNode,
FreeNode,
LastFreeNode
};

struct AnyNode {
NodeType tag;
uint8_t padding[BOOK_NODE_SIZE - 4];
};

struct InnerNode {
NodeType tag;
uint32_t prefixLen;
__uint128_t key;
uint32_t children[2];
uint8_t padding[BOOK_NODE_SIZE - 32];
};

struct LeafNode {
NodeType tag;
uint8_t ownerSlot;
uint8_t orderType;
uint8_t version;
uint8_t timeInForce;
__uint128_t key;
solana::PublicKey owner;
uint64_t quantity;
uint64_t clientOrderId;
uint64_t bestInitial;
uint64_t timestamp;
};

struct FreeNode {
NodeType tag;
uint32_t next;
uint8_t padding[BOOK_NODE_SIZE - 8];
};

struct BookSide {
MetaData metaData;
uint64_t bumpIndex;
uint64_t freeListLen;
uint32_t freeListHead;
uint32_t rootNode;
uint64_t leafCount;
AnyNode nodes[BOOK_SIZE];

struct iterator {
Side side;
const BookSide &bookSide;
std::stack<uint32_t> stack;
uint32_t left, right;

iterator(Side side, const BookSide &bookSide)
: side(side), bookSide(bookSide) {
stack.push(bookSide.rootNode);
left = side == Side::Buy ? 1 : 0;
right = side == Side::Buy ? 0 : 1;
}

bool operator==(const iterator &other) const {
return &bookSide == &other.bookSide && stack.top() == other.stack.top();
}

iterator &operator++() {
if (stack.size() > 0) {
const auto &elem = **this;
stack.pop();

if (elem.tag == NodeType::InnerNode) {
const auto innerNode =
reinterpret_cast<const struct InnerNode *>(&elem);
stack.push(innerNode->children[right]);
stack.push(innerNode->children[left]);
}
}
return *this;
}

const AnyNode &operator*() const { return bookSide.nodes[stack.top()]; }
};
};

#pragma pack(pop)

// instructions are even tighter packed, every byte counts
Expand Down
21 changes: 21 additions & 0 deletions include/orderbook/levelOne.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

namespace mango_v3 {
namespace orderbook {

struct levelOne {
uint64_t highestBid;
uint64_t highestBidSize;
uint64_t lowestAsk;
uint64_t lowestAskSize;
double midPoint;
double spreadBps;

bool valid() const {
return ((highestBid && lowestAsk) && (lowestAsk > highestBid)) ? true
: false;
}
};

} // namespace orderbook
} // namespace mango_v3
22 changes: 22 additions & 0 deletions include/orderbook/order.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

namespace mango_v3 {
namespace orderbook {

struct order {
order(uint64_t price, uint64_t quantity) : price(price), quantity(quantity) {}

bool operator<(const order& compare) const {
return (price < compare.price) ? true : false;
}

bool operator>(const order& compare) const {
return (price > compare.price) ? true : false;
}

uint64_t price;
uint64_t quantity;
};

} // namespace orderbook
} // namespace mango_v3
74 changes: 74 additions & 0 deletions include/orderbook/orderbook.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#pragma once
#include <spdlog/spdlog.h>

#include <functional>
#include <mutex>

#include "levelOne.hpp"
#include "orderbook/order.hpp"
#include "subscriptions/bookSide.hpp"

namespace mango_v3 {
namespace orderbook {
class book {
public:
book(subscription::bookSide& bids, subscription::bookSide& asks)
: bids(bids), asks(asks) {
bids.registerUpdateCallback(std::bind(&book::updateCallback, this));
asks.registerUpdateCallback(std::bind(&book::updateCallback, this));
}

void registerUpdateCallback(std::function<void()> callback) {
onUpdateCb = callback;
}

void subscribe() {
bids.subscribe();
asks.subscribe();
}

void updateCallback() {
const std::scoped_lock lock(callbackMtx);
levelOne newL1;
auto bestBid = bids.getBestOrder();
auto bestAsk = asks.getBestOrder();
newL1.highestBid = bestBid.price;
newL1.highestBidSize = bestBid.quantity;
newL1.lowestAsk = bestAsk.price;
newL1.lowestAskSize = bestAsk.quantity;

if (newL1.valid()) {
newL1.midPoint = ((double)newL1.lowestAsk + newL1.highestBid) / 2;
newL1.spreadBps =
((newL1.lowestAsk - newL1.highestBid) * 10000) / newL1.midPoint;
{
const std::scoped_lock lock(levelOneMtx);
level1 = newL1;
}
onUpdateCb();
}
}

levelOne getLevel1() const {
const std::scoped_lock lock(levelOneMtx);
return level1;
}

uint64_t getDepth(int8_t percent) {
const std::scoped_lock lock(levelOneMtx);
auto price = (level1.midPoint * (100 + percent)) / 100;
return (percent > 0) ? asks.getVolume<std::less_equal<uint64_t>>(price)
: bids.getVolume<std::greater_equal<uint64_t>>(price);
}

private:
levelOne level1;
// todo:macos latomic not found issue, otherwise replace mtx with std::atomic
mutable std::mutex levelOneMtx;
std::function<void()> onUpdateCb;
std::mutex callbackMtx;
subscription::bookSide& bids;
subscription::bookSide& asks;
};
} // namespace orderbook
} // namespace mango_v3
8 changes: 5 additions & 3 deletions include/solana.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ class Connection {
///
json getAccountInfoRequest(const std::string &account,
const std::string &encoding = "base64",
const size_t offset = 0, const size_t length = 0);
const size_t offset = 0,
const size_t length = 0) const;
json getRecentBlockhashRequest(const std::string &commitment = "finalized");
json sendTransactionRequest(
const std::string &transaction, const std::string &encoding = "base58",
Expand All @@ -269,7 +270,8 @@ class Connection {
template <typename T>
inline T getAccountInfo(const std::string &account,
const std::string &encoding = "base64",
const size_t offset = 0, const size_t length = 0) {
const size_t offset = 0,
const size_t length = 0) const {
const json req = getAccountInfoRequest(account, encoding, offset, length);
cpr::Response r =
cpr::Post(cpr::Url{rpc_url_}, cpr::Body{req.dump()},
Expand Down Expand Up @@ -309,4 +311,4 @@ inline json accountSubscribeRequest(const std::string &account,
}
} // namespace subscription
} // namespace rpc
} // namespace solana
} // namespace solana
Loading