I need help to optimise the code as it becomes slow with a large dataset. I have an exchange simulation program that takes in market prices from a csv and then allows the user to place bids and offers for the products (currencies) that are up for trade.
OrderBookEntry.cpp contains the constructors
#include "OrderBookEntry.h"
OrderBookEntry::OrderBookEntry( double _price,
double _amount,
std::string _timestamp,
std::string _product,
OrderBookType _orderType,
std::string _username)
: price(_price),
amount(_amount),
timestamp(_timestamp),
product(_product),
orderType(_orderType),
username(_username)
{
}
OrderBookType OrderBookEntry::stringToOrderBookType(std::string s)
{
if (s == "ask")
{
return OrderBookType::ask;
}
if (s == "bid")
{
return OrderBookType::bid;
}
return OrderBookType::unknown;
}
OrderBook.cpp processes bids and offers
#include <map>
#include <algorithm>
#include <iostream>
#include "OrderBook.h"
#include "CSVReader.h"
/** construct, reading a csv data file */
/* R1A: Retrieve the live order book from the Merklerex exchange simulation */
OrderBook::OrderBook(std::string filename)
{
orders = CSVReader::readCSV(filename);
}
/** return vector of all known products in the dataset*/
std::vector<std::string> OrderBook::getKnownProducts()
{
std::vector<std::string> products;
std::map<std::string,bool> prodMap;
for (OrderBookEntry& e : orders)
{
prodMap(e.product) = true;
}
// now flatten the map to a vector of strings
for (auto const& e : prodMap)
{
products.push_back(e.first);
}
return products;
}
/** return vector of Orders according to the sent filters*/
std::vector<OrderBookEntry> OrderBook::getOrders(OrderBookType type,
std::string product,
std::string timestamp)
{
std::vector<OrderBookEntry> orders_sub;
for (OrderBookEntry& e : orders)
{
if (e.orderType == type &&
e.product == product &&
e.timestamp == timestamp )
{
orders_sub.push_back(e);
}
}
return orders_sub;
}
void OrderBook::insertOrder(OrderBookEntry& order)
{
orders.push_back(order);
std::sort(orders.begin(), orders.end(), OrderBookEntry::compareByTimestamp);
}
/* R2D: Using the live order book from the exchange, decide if it should withdraw its bids at any point in time */
/* R3D: Using the live order book from the exchange, decide if it should withdraw its offers at any point in time */
void OrderBook::withdrawOrder(std::string time)
{
for (std::size_t i = orders.size() - 1; i < orders.size(); --i)
{
if(orders(i).timestamp == time && orders(i).username == "simuser")
{
orders.erase(orders.begin() + i);
}
}
}
std::vector<OrderBookEntry> OrderBook::matchAsksToBids(std::string product, std::string timestamp)
{
// asks = orderbook.asks
std::vector<OrderBookEntry> asks = getOrders(OrderBookType::ask,
product,
timestamp);
// bids = orderbook.bids
std::vector<OrderBookEntry> bids = getOrders(OrderBookType::bid,
product,
timestamp);
// sales = ()
std::vector<OrderBookEntry> sales;
// I put in a little check to ensure we have bids and asks
// to process.
if (asks.size() == 0 || bids.size() == 0)
{
std::cout << " OrderBook::matchAsksToBids no bids or asks" << std::endl;
return sales;
}
// sort asks lowest first
std::sort(asks.begin(), asks.end(), OrderBookEntry::compareByPriceAsc);
// sort bids highest first
std::sort(bids.begin(), bids.end(), OrderBookEntry::compareByPriceDesc);
// for ask in asks:
std::cout << "max ask " << asks(asks.size()-1).price << std::endl;
std::cout << "min ask " << asks(0).price << std::endl;
std::cout << "max bid " << bids(0).price << std::endl;
std::cout << "min bid " << bids(bids.size()-1).price << std::endl;
for (OrderBookEntry& ask : asks)
{
// for bid in bids:
for (OrderBookEntry& bid : bids)
{
// if bid.price >= ask.price # we have a match
if (bid.price >= ask.price)
{
// sale = new order()
// sale.price = ask.price
OrderBookEntry sale{ask.price, 0, timestamp,
product,
OrderBookType::asksale};
if (bid.username == "simuser")
{
sale.username = "simuser";
sale.orderType = OrderBookType::bidsale;
}
if (ask.username == "simuser")
{
sale.username = "simuser";
sale.orderType = OrderBookType::asksale;
}
// # now work out how much was sold and
// # create new bids and asks covering
// # anything that was not sold
// if bid.amount == ask.amount: # bid completely clears ask
if (bid.amount == ask.amount)
{
// sale.amount = ask.amount
sale.amount = ask.amount;
// sales.append(sale)
sales.push_back(sale);
// bid.amount = 0 # make sure the bid is not processed again
bid.amount = 0;
// # can do no more with this ask
// # go onto the next ask
// break
break;
}
// if bid.amount > ask.amount: # ask is completely gone slice the bid
if (bid.amount > ask.amount)
{
// sale.amount = ask.amount
sale.amount = ask.amount;
// sales.append(sale)
sales.push_back(sale);
// # we adjust the bid in place
// # so it can be used to process the next ask
// bid.amount = bid.amount - ask.amount
bid.amount = bid.amount - ask.amount;
// # ask is completely gone, so go to next ask
// break
break;
}
// if bid.amount < ask.amount # bid is completely gone, slice the ask
if (bid.amount < ask.amount &&
bid.amount > 0)
{
// sale.amount = bid.amount
sale.amount = bid.amount;
// sales.append(sale)
sales.push_back(sale);
// # update the ask
// # and allow further bids to process the remaining amount
// ask.amount = ask.amount - bid.amount
ask.amount = ask.amount - bid.amount;
// bid.amount = 0 # make sure the bid is not processed again
bid.amount = 0;
// # some ask remains so go to the next bid
// continue
continue;
}
}
}
}
return sales;
}
functions to place bids and asks in main
void MerkelMain::enterAsk()
{
std::cout << "Make an ask - enter the amount: product, price, amount, eg. ETH/BTC,200,0.5" << std::endl;
std::string input;
std::getline(std::cin, input);
std::vector<std::string> tokens = CSVReader::tokenise(input, ',');
if (tokens.size() != 3)
{
std::cout << "MerkelMain::enterAsk Bad input! " << input << std::endl;
}
else {
try {
OrderBookEntry obe = CSVReader::stringsToOBE(
tokens(1),
tokens(2),
currentTime,
tokens(0),
OrderBookType::ask
);
obe.username = "simuser";
if (wallet.canFulfillOrder(obe))
{
std::cout << "Wallet looks good. " << std::endl;
orderBook.insertOrder(obe);
}
else {
std::cout << "Wallet has insufficient funds . " << std::endl;
}
}catch (const std::exception& e)
{
std::cout << " MerkelMain::enterAsk Bad input " << std::endl;
}
}
}
void MerkelMain::enterBid()
{
std::cout << "Make a bid - enter the amount: product, price, amount, eg. ETH/BTC,200,0.5" << std::endl;
std::string input;
std::getline(std::cin, input);
std::vector<std::string> tokens = CSVReader::tokenise(input, ',');
if (tokens.size() != 3)
{
std::cout << "MerkelMain::enterBid Bad input! " << input << std::endl;
}
else {
try {
OrderBookEntry obe = CSVReader::stringsToOBE(
tokens(1),
tokens(2),
currentTime,
tokens(0),
OrderBookType::bid
);
obe.username = "simuser";
if (wallet.canFulfillOrder(obe))
{
std::cout << "Wallet looks good. " << std::endl;
orderBook.insertOrder(obe);
}
else {
std::cout << "Wallet has insufficient funds . " << std::endl;
}
}catch (const std::exception& e)
{
std::cout << " MerkelMain::enterBid Bad input " << std::endl;
}
}
}
CSVReader.cpp
#include <iostream>
#include <fstream>
#include "CSVReader.h"
CSVReader::CSVReader()
{
}
std::vector<OrderBookEntry> CSVReader::readCSV(std::string csvFilename)
{
std::vector<OrderBookEntry> entries;
std::ifstream csvFile{csvFilename};
std::string line;
if (csvFile.is_open())
{
while(std::getline(csvFile, line))
{
try {
OrderBookEntry obe = stringsToOBE(tokenise(line, ','));
entries.push_back(obe);
}catch(const std::exception& e)
{
std::cout << "CSVReader::readCSV bad data" << std::endl;
}
}// end of while
}
std::cout << "CSVReader::readCSV read " << entries.size() << " entries" << std::endl;
return entries;
}
std::vector<std::string> CSVReader::tokenise(std::string csvLine, char separator)
{
std::vector<std::string> tokens;
signed int start, end;
std::string token;
start = csvLine.find_first_not_of(separator, 0);
do{
end = csvLine.find_first_of(separator, start);
if (start == csvLine.length() || start == end) break;
if (end >= 0) token = csvLine.substr(start, end - start);
else token = csvLine.substr(start, csvLine.length() - start);
tokens.push_back(token);
start = end + 1;
}while(end > 0);
return tokens;
}
OrderBookEntry CSVReader::stringsToOBE(std::vector<std::string> tokens)
{
double price, amount;
if (tokens.size() != 5) // bad
{
std::cout << "Bad line " << std::endl;
throw std::exception{};
}
// we have 5 tokens
try {
price = std::stod(tokens(3));
amount = std::stod(tokens(4));
}catch(const std::exception& e){
std::cout << "CSVReader::stringsToOBE Bad float! " << tokens(3)<< std::endl;
std::cout << "CSVReader::stringsToOBE Bad float! " << tokens(4)<< std::endl;
throw;
}
OrderBookEntry obe{price,
amount,
tokens(0),
tokens(1),
OrderBookEntry::stringToOrderBookType(tokens(2))};
return obe;
}
OrderBookEntry CSVReader::stringsToOBE(std::string priceString,
std::string amountString,
std::string timestamp,
std::string product,
OrderBookType orderType)
{
double price, amount;
try {
price = std::stod(priceString);
amount = std::stod(amountString);
}catch(const std::exception& e){
std::cout << "CSVReader::stringsToOBE Bad float! " << priceString<< std::endl;
std::cout << "CSVReader::stringsToOBE Bad float! " << amountString<< std::endl;
throw;
}
OrderBookEntry obe{price,
amount,
timestamp,
product,
orderType};
return obe;
}
Some data from the csv. 1st part is the date, 2nd part is the product, 3rd part is the OrderBookType (asks or bids), 4th part is the price and 5th part is the amount. When placing bids or asks, users do not need to enter the date but need to key in a value for everything else.
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02187308,7.44564869
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02187307,3.467434
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02187305,6.85567013
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.0218732,1.
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02187163,0.03322569
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02187008,0.21
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02186299,0.1
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02186251,0.0091
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02186053,0.58
2020/03/17 17:01:24.884492,ETH/BTC,bid,0.02186052,0.05