For this coding challenge, the task is to create a price-time priority limit order book data structure [1]. This is a mouthful and sounds more complicated than it is -- for an overview, refer to BACKGROUND.md.
The assets we're buying and selling on this order book are scoops of ice cream, and the currency we're using to buy and sell those scoops is the US penny (market symbol ICEPEN
). No complicated decimals here -- integers only!
[1] The subject matter of this coding challenging is not necessarily indicative of this job's responsibilities. It's just a fun software challenge!
Your submission should include the ability to:
- run some kind of unit tests
- create limit buy/sell orders
- recover the state of the order book after a crash/restart
- retrieve the total quantity at each limit price
- retrieve the state of an old order
These features are listed in priority order, so if you feel like you won't have enough time to finish this challenge, start at 1 and work your way down.
Estimated completion time: 1.5-3 hours; please don't spend more time than this! Cut features and make tradeoffs instead.
For the purposes of this challenge, it is far more important to be bug-free than it is to be fast. If all operations run in O(n^2)
or faster, then your program's performance and asymptotic complexity will not at all be considered in this challenge.
Note: n
is the number of open orders (on the book) and m
is the number of closed orders (off the book, no more remaining quantity to trade with).
When you submit your program, be sure to describe any suboptimal design and implementation decisions you made but were unable to fix in the alloted time in your README. We don't expect your submission to be 100% perfect -- software rarely is -- and we would like to get a sense of your ability to self-critique and build products incrementally.
Prioritize 1) being feature complete, 2) including enough tests to convince yourself you don't have any big bugs, and 3) writing clean code that is ready for change. Final tip: be cautious about overthinking your solution. Our reference implementation is just 70 SLOCs (excluding tests).
Send me (anthony@namebase.io) a zip file with your source code (or a link to a github repo) with instructions on how to run your tests.
The ./lib/index.js
file should export a single class that implements the following functions:
Recover the state of the order book from a file. Please do not use a library like sqlite. Use node.js's built-in libraries to write and read to a file that allows you to recover your order book.
/**
* @param {string} fileName -- the file that contains whatever info you need to recover
*/
sync(fileName);
Add a new limit buy order to the order book. Generates an id so that the client can reference this order in the future.
/**
* @param {integer} quantity -- how many scoops of ice cream to buy
* @param {integer} price -- the maximum number of pennies to pay per scoop
*/
buy(quantity, price);
Returns:
{
id: ..., // generated by your code, type is up to you
isBuyOrder: true, // whether this order is a buy or a sell
quantity: ..., // the order's original quantity
price: ..., // the order's price
executedQuantity: ..., // the # of ice cream scoops that this order has purchased
}
Add a new limit sell order to the order book. Generates an id so that the client can reference this order in the future.
/**
* @param {integer} quantity -- how many scoops of ice cream to sell
* @param {integer} price -- the minimum number of pennies to receive per scoop
*/
sell(quantity, price);
Returns:
{
id: ..., // generated by your code, type is up to you
isBuyOrder: false, // whether this order is a buy or a sell
quantity: ..., // the order's original quantity
price: ..., // the order's price
executedQuantity: ..., // the # of ice cream scoops that this order has sold
}
Retrieve the total number of ice cream scoops being bought or sold at a given limit price.
/**
* @param {integer} price -- the limit price we're querying the quantity at
*/
getQuantityAtPrice(price);
Returns:
4
// integer >= 0; the number of ice cream scoops at this price point
// note: all of the orders at a limit price are always on the same side,
// they are either all BUYs or all SELLs
Retrieve the state of an old order.
/**
* @param {number|string|up to you} id -- the order's id
*/
getOrder(id);
Returns:
{
id: ..., // generated by your code, type is up to you
isBuyOrder: false, // whether this order is a buy or a sell
quantity: ..., // the order's original quantity
price: ..., // the order's price
executedQuantity: ..., // the # of ice cream scoops that this order has sold
}
- Clone the repository:
git clone https://github.com/nzediegwu1/namebase-orderbook-coding-challenge.git
- Install dependencies:
yarn
- Run tests:
yarn test
- Start the application:
yarn start
Go to http://localhost:8000 on postman to interact with the API
To create a sell order, use: POST /sell
with request body below:
{
"price": 61,
"quantity": 7
}
To create a buy order, use: POST /buy
with request body below:
{
"price": 50,
"quantity": 7
}
To sync the order book, use:
GET /sync
To get order by id, use:
GET /order/order-id
To get total quantity at a given price, use:
GET /quantity?price=60
-
I made use of array instead of Binary Search Tree data structure. Given a very large order book, this would require much computational resources. Binary Search Tree data structure would be a better shot with great boost to the speed of
getQuantityAtPrice
using a single lookup at the exact collection the data is stored. However I queried thebuy-orders
collection first, and when not found, I then queried thesell-orders
collection, which is not a very efficient approach. With Binary Search Tree, I do not need to search both tables since data is ordered and I can check the max price in each order table without scanning through them, hence determine whether a collection should be searched or not -
I implemented
getOrderById
functionality by invokingArray.find
method, which loops through the array while searching for the order. With a million record order book, this would be highly inefficient. Given more time, I would implement indexing for the id field, so thatgetOrderById
operation becomes super fast -
I had to change things & make use of synchronous
writeFileSync
node.jsfs
method for writing file to my JSON db, instead of thewriteFile
I initially used for asynchronous, faster & non-blocking I/O. This was because tests were crashing whenfs.writeFile
was used andfs.writeFile
implementation was difficult to mock/replace within test considering time constraints