#include "OrderBook.h"
OrderBook::OrderBook(const std::string& symbol)
: mSymbol(symbol)
, mInitBookWidth(0)
{
#ifndef MAP_BASED_ORDERBOOK
mBidLevels.reserve(10000);
mAskLevels.reserve(10000);
#endif
}
std::string OrderBook::toStr(int depth, int spacing)
{
std::string book = "";
int widthBid = 0;
int widthAsk = 0;
try
{
int currentDepth = 0;
std::string bids = "";
std::string asks = "";
std::vector<std::string> bidsBuf;
std::vector<std::string> asksBuf;
for(const auto& it: mBidLevels)
{
if(depth > 0) currentDepth++;
if(currentDepth > depth) break;
std::stringstream ss;
ss << std::fixed << std::setprecision(5) << it.first;
bool isFirstLine = false;
if(bids.size() == 0) isFirstLine = true;
bids += ss.str() + ' ' + std::to_string(it.second) + "|\n";
if(isFirstLine) widthBid = bids.size();
}
currentDepth = 0;
for(const auto& it: mAskLevels)
{
if(depth > 0) currentDepth++;
if(currentDepth > depth) break;
std::stringstream ss;
ss << std::fixed << std::setprecision(5) << it.first;
bool isFirstLine = false;
if(asks.size() == 0) isFirstLine = true;
asks += ss.str() + ' ' + std::to_string(it.second) + "\n";
if(isFirstLine) widthAsk = asks.size();
}
if(bids.size() == 0 && asks.size() == 0) return "";
boost::split(bidsBuf, bids, boost::is_any_of("\n"));
boost::split(asksBuf, asks, boost::is_any_of("\n"));
int maxSize = std::max(bidsBuf.size(), asksBuf.size());
if(depth > 0) maxSize = std::min(depth, maxSize);
bidsBuf.resize(maxSize, "");
asksBuf.resize(maxSize, "");
std::string bookSpacing(spacing, ' ');
for(unsigned int i = 0; i < maxSize; i++)
{
if(bidsBuf[i].size() == 0)
{
bidsBuf[i].resize(asksBuf[i].size() + 1, ' ');
bidsBuf[i][asksBuf[i].size()] = '|';
}
else if(asksBuf[i].size() == 0)
{
asksBuf[i].resize(bidsBuf[i].size() + 1, ' ');
asksBuf[i] = '|' + asksBuf[i];
}
if(book == "")
{
try
{
//throws an exception I could not fix just yet... quite infuriating
//last value of halfLineSize right befor the throw is 04
int halfLineSize = std::max(bidsBuf[i].size(), asksBuf[i].size());
std::string bidLine = "BID";
bidLine = std::string((halfLineSize - bidLine.size()) * 0.5, ' ') + "BID" + std::string((halfLineSize - bidLine.size() - mSymbol.size()) * 0.5, ' ');
std::string askLine = "ASK";
askLine = std::string((halfLineSize - askLine.size() - mSymbol.size()) * 0.5, ' ') + "ASK" + std::string((halfLineSize - askLine.size()) * 0.5, ' ');
std::string bookTitle = bidLine + mSymbol + askLine + "\n";
book += bookTitle;
if(!mInitBookWidth) mInitBookWidth = bookTitle.size();
}
catch(const std::exception& e)
{}
}
std::string line = bidsBuf[i] + asksBuf[i] + "\n";
book += bookSpacing + line;
}
}
catch(const std::exception& e)
{}
return book;
}
#ifdef ITERATOR_BASED_ORDERBOOK
void OrderBook::addOrder(EntrySide side, double price, double volume)
{
std::map<double, double>::iterator it;
Entry lookupEntry;
lookupEntry.side = side;
if(side == EntrySide::BID)
{
it = add(mBidLevels, price, volume);
lookupEntry.bidIt = it;
}
else if(side == EntrySide::ASK)
{
it = add(mAskLevels, price, volume);
lookupEntry.askIt = it;
}
std::greater<int> aa;
mLookup.emplace(price, lookupEntry);
}
void OrderBook::removeOrder(EntrySide side, double price)
{
auto it = mLookup.find(price);
if(it != mLookup.end())
{
if(side == EntrySide::BID)
remove(it->second.bidIt, mBidLevels);
else if(side == EntrySide::ASK)
remove(it->second.askIt, mAskLevels);
}
mLookup.erase(it);
}
template <class T>
typename T::iterator OrderBook::add(T& levels, double price, double volume)
{
auto [it, status] = levels.try_emplace(price, volume);
if(!status) it->second = volume;
return it;
}
template<class T>
void OrderBook::remove(typename T::iterator it, T& levels)
{
levels.erase(it);
}
#else
void OrderBook::addOrder(EntrySide side, double price, double volume)
{
#ifdef MAP_BASED_ORDERBOOK
if(side == EntrySide::BID)
add(mBidLevels, price, volume);
else if(side == EntrySide::ASK)
add(mAskLevels, price, volume);
#else
if(side == EntrySide::BID)
add(mBidLevels, price, volume, std::greater<double>());
else if(side == EntrySide::ASK)
add(mAskLevels, price, volume, std::less<double>());
#endif
}
void OrderBook::removeOrder(EntrySide side, double price)
{
//Unfortunately, for now, this will do O(log n) search time
#ifdef MAP_BASED_ORDERBOOK
if(side == EntrySide::BID)
remove(mBidLevels, price);
else if(side == EntrySide::ASK)
remove(mAskLevels, price);
#else
if(side == EntrySide::BID)
remove(mBidLevels, price, std::greater<double>());
else if(side == EntrySide::ASK)
remove(mAskLevels, price, std::less<double>());
#endif
}
#ifdef MAP_BASED_ORDERBOOK
template <class T>
void OrderBook::add(T& levels, double price, double volume)
{
auto [it, status] = levels.try_emplace(price, volume);
if(!status) it->second = volume;
}
template<class T>
void OrderBook::remove(T& levels, double price)
{
levels.erase(price);
}
#else
template <class T, class C>
void OrderBook::add(T& levels, double price, double volume, C comp)
{
auto it = std::lower_bound(levels.begin(), levels.end(), price,
[comp] (const auto& p, double _price) { return comp(p.first, _price); });
if(it != levels.end() && it->first == price) it->second = volume;
else levels.insert(it, {price, volume});
#ifdef ASSERT_ON_TOP_OF_BOOK_CROSSING
if(!(mAskLevels.empty() || mBidLevels.empty())) assert(mAskLevels.front().first > mBidLevels.front().first);
#endif
}
template<class T, class C>
void OrderBook::remove(T& levels, double price, C comp)
{
auto it = std::lower_bound(levels.begin(), levels.end(), price,
[comp] (const auto& p, double _price) { return comp(p.first, _price); });
if(it != levels.end() && it->first == price) levels.erase(it);
}
#endif
#endif
void OrderBook::clear()
{
#ifdef ITERATOR_BASED_ORDERBOOK
mLookup.clear();
#endif
mInitBookWidth = 0;
mBidLevels.clear();
mAskLevels.clear();
}