In this assignment, you will build a very simple matching engine. A matching engine is a technology that lies at the core of any exchange. From a high-level perspective matching engine matches people (or organizations) who want to buy an asset with people who want to sell an asset. We want to support only one type of order for this exercise, namely limit order. A limit order is an order to buy an asset at no more than a specific price or sell an asset at no less than a specific price.
In real life, different algorithms can be used to match orders. Here we expect you to implement a continuous trading Price/Time algorithm (aka FIFO). This algorithm ensures that all orders at the same price level are filled according to time priority; the first order at a price level is the first order matched. For any new order, you prioritize the opposite order with the best price, and if you have multiple orders with the same price, the earliest takes precedence.
Usually, the term order book is used to list all active buy and sell orders. For example, consider such order book:
ID | Direction | Time | Amount | Price | Amount | Time | Direction |
---|---|---|---|---|---|---|---|
3 | 10.05 | 40 | 07:03 | SELL | |||
1 | 10.05 | 20 | 07:00 | SELL | |||
2 | 10.04 | 20 | 07:02 | SELL | |||
5 | BUY | 07:06 | 40 | 10.02 | |||
4 | BUY | 07:05 | 20 | 10.00 | |||
6 | BUY | 07:10 | 40 | 10.00 |
If a new limit order buy 55 shares at 10.06 price
comes in,
then it will be filled in this order:
- 20 shares at 10.04 (order 2)
- 20 shares at 10.05 (order 1)
- 15 shares at 10.05 (order 3)
This leaves the order book in the following state:
ID | Direction | Time | Amount | Price | Amount | Time | Direction |
---|---|---|---|---|---|---|---|
3 | 10.05 | 25 | 07:03 | SELL | |||
5 | BUY | 07:06 | 40 | 10.02 | |||
4 | BUY | 07:05 | 20 | 10.00 | |||
6 | BUY | 07:10 | 40 | 10.00 |
NB: order 3 is executed only partially.
You are provided with the starter for the Spring Boot project, and some classes are already created to save you some time, though feel free to adjust everything to your wish, except HTTP API. You will find a couple of TODO comments. These are the places where you need to hook your code into.
- Implement the following HTTP API.
- Don't use any database. Use core java to store everything in-memory.
- Don't implement any persistence. All state will be lost after restart. That's okay.
- Don't add support for different users.
- Don't add any security.
- Please keep your assignment in a private git repo and don't share it anywhere publicly.
- Feel free to use any 3rd party libraries, as long as they don't fully implement this assignment.
POST /orders
It is used to place a new limit order. It responds with the current state of the just created order.
It means that the order can be already filled, but it can still be pending.
Every order can be filled by one or many trades.
Trade is a fact of finding matching counterparty order,
order of the opposite direction with satisfying price and amount.
For example, taking our previous example, order buy 55 shares at 10.06 price
was filled by three trades:
- 20 shares at 10.04 (order 2)
- 20 shares at 10.05 (order 1)
- 15 shares at 10.05 (order 3)
Endpoint must accept the JSON with the following fields:
asset
- string, asset name, for simplicity this can be any textprice
- number, a price for limit orderamount
- number, amount of asset to fill by orderdirection
- string, can be either "BUY" or "SELL"
And respond with the JSON containing the following fields:
id
- number, you need to generate a unique order IDtimestamp
- string, date and time when the system registered orderprice
- number, the same as request bodyamount
- number, the same as request bodydirection
- string, the same as request bodypendingAmount
- number, amount still to be filledtrades
- an array of trade objects (see definition below), can be empty
Trade object:
orderId
- number, counterparty order IDamount
- number, amount filled from counterparty orderprice
- number, price used for this trade
{
"asset": "TST",
"price": 10.0,
"amount": 100.0,
"direction": "SELL"
}
{
"id": 2,
"timestamp": "2021-12-08T10:22:00.460575730Z",
"asset": "TST",
"price": 10.0,
"amount": 100.0,
"direction": "SELL",
"trades": [
{
"orderId": 0,
"amount": 10.0,
"price": 10.0
}
],
"pendingAmount": 90.0
}
GET /orders/{orderId}
Responds with the current state of the order with ID orderId
(see previous endpoint response body).
{
"id": 2,
"timestamp": "2021-12-08T10:22:00.460575730Z",
"asset": "TST",
"price": 10.0,
"amount": 100.0,
"direction": "SELL",
"trades": [
{
"orderId": 0,
"amount": 10.0,
"price": 10.0
}
],
"pendingAmount": 90.0
}
Imagine that we are going to use this system to emulate cryptocurrency exchange.
For example, let's first place a limit order selling 1 Bitcoin at the price no less than 43251.00 euro.
In order to do that we need to call the endpoint POST /orders
with the following payload:
{
"asset": "BTC",
"price": 43251.00,
"amount": 1.0,
"direction": "SELL"
}
Assuming that the Bitcoin order book is currently empty we will receive the following response:
{
"id": 0,
"timestamp": "2021-12-08T13:34:44.498770729Z",
"asset": "BTC",
"price": 43251.00,
"amount": 1.0,
"direction": "SELL",
"trades": [],
"pendingAmount": 1.0
}
Now let's place some buy orders. Let's first start with an order to buy 0.25 Bitcoin at the price of no more than 43250.00.
POST /orders
{
"asset": "BTC",
"price": 43250.00,
"amount": 0.25,
"direction": "BUY"
}
Since we currently don't have a sell order with price satisfying this buy order, it just ends up in our order book.
Now let's place an order to buy 0.35 Bitcoin at the price of no more than 43253.00.
POST /orders
{
"asset": "BTC",
"price": 43253.00,
"amount": 0.35,
"direction": "BUY"
}
In this case we have a matching order in our book, we can sell 0.35 Bitcoin for 43251.00, which is lower than 43253.00.
Current state of order #0:
GET /orders/0
{
"id": 0,
"timestamp": "2021-12-08T13:34:44.498770729Z",
"asset": "BTC",
"price": 43251.00,
"amount": 1.0,
"direction": "SELL",
"trades": [
{
"orderId": 2,
"amount": 0.35,
"price": 43251.00
}
],
"pendingAmount": 0.65
}
Current state of order #2:
GET /orders/2
{
"id": 2,
"timestamp": "2021-12-08T13:34:50.460317243Z",
"asset": "BTC",
"price": 43253.00,
"amount": 0.35,
"direction": "BUY",
"trades": [
{
"orderId": 0,
"amount": 0.35,
"price": 43251.00
}
],
"pendingAmount": 0.00
}
Let's place one last buy order to fully fill our initial sell order.
POST /orders
{
"asset": "BTC",
"price": 43251.00,
"amount": 0.65,
"direction": "BUY"
}
Now both orders #0 and #3 are fully executed.
GET /orders/0
{
"id": 0,
"timestamp": "2021-12-08T13:34:44.498770729Z",
"asset": "BTC",
"price": 43251.00,
"amount": 1.0,
"direction": "SELL",
"trades": [
{
"orderId": 2,
"amount": 0.35,
"price": 43251.00
},
{
"orderId": 3,
"amount": 0.65,
"price": 43251.00
}
],
"pendingAmount": 0.00
}
GET /orders/3
{
"id": 3,
"timestamp": "2021-12-08T13:40:07.153480698Z",
"asset": "BTC",
"price": 43251.00,
"amount": 0.65,
"direction": "BUY",
"trades": [
{
"orderId": 0,
"amount": 0.65,
"price": 43251.00
}
],
"pendingAmount": 0.00
}
- Most of all, your solution should be correct and clean.
- Your code has to be thread-safe and shouldn't have any mistakes.
- It must demonstrate the practical usage of available data structures and standard library.
- Don't do premature optimization. Try to maintain a good balance between maintainability and performance.
- Test coverage - at your discretion. Do as you usually do, be yourself.